support fee configuration for savings

This commit is contained in:
ashok-conflux
2013-09-19 23:47:20 +05:30
parent 4a8a0d42b7
commit be1f90ec5c
57 changed files with 3529 additions and 89 deletions
+569 -2
View File
@@ -11062,6 +11062,11 @@ GET https://DomainName/api/v1/charges/template
"id": 1,
"code": "chargeAppliesTo.loan",
"value": "Loan"
},
{
"id": 2,
"code": "chargeAppliesTo.savings",
"value": "Savings"
}
],
"chargeTimeTypeOptions":
@@ -12324,7 +12329,7 @@ Content-Type: application/json
<br/>
<table class=matrixHeading>
<tr class="matrixHeadingBG"><td><div class="mifosXHeading2">Optional Fields</div></td></tr>
<tr class=alt><td>minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeForTransfers, annualFeeAmount, annualFeeOnMonthDay, monthDayFormat, paymentChannelToFundSourceMappings</tr>
<tr class=alt><td>minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeForTransfers, annualFeeAmount, annualFeeOnMonthDay, monthDayFormat, paymentChannelToFundSourceMappings, charges</tr>
</table>
</div>
<div class="method-example">
@@ -12344,7 +12349,8 @@ Request Body:
"interestPostingPeriodType":4,
"interestCalculationType": 1,
"interestCalculationDaysInYearType": "365",
"accountingRule":"1"
"accountingRule":"1",
"charges":[{"id":"1"}]
}
</code>
<code class="method-response">
@@ -12890,6 +12896,78 @@ Content-Type: application/json
"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"
}
}
]
}
</code>
@@ -13395,6 +13473,7 @@ Request Body:
<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>
@@ -13812,6 +13891,462 @@ No Request Body:
</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="mifosXHeading2">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 Loan Charges</h4>
<p>Example Requests:</p>
<div class=apiClick>savingsaccounts/1/charges</div>
<br>
<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,
"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,
"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
}
]
</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"
}
}
],
"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="mifosXHeading2">Mandatory Fields
for Savings account 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/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>
</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>
A 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:
{
"locale": "en",
"amount": "60",
}
</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>
<!-- End of Savings account charges -->
<!-- account transfers -->
<a id="accounttransfers" name="accounttransfers" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
@@ -19949,6 +20484,38 @@ GET https://DomainName/api/v1/search?query=000000111
<td></td>
<td></td>
</tr>
<tr class="alt">
<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></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 class="alt">
<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 class="alt">
<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 class="alt">
<td><a href="#accounttransfers">Account Transfers</a></td>
<td>accounttransfers/</td>
@@ -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;
@@ -445,7 +445,7 @@ public class CommandWrapper {
public boolean isSavingsAccountResource() {
return this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
public boolean isRejectionOfSavingsAccountApplication() {
return this.actionName.equalsIgnoreCase("REJECT") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
@@ -498,6 +498,30 @@ public class CommandWrapper {
return this.actionName.equalsIgnoreCase("ADJUSTTRANSACTION") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNT");
}
public boolean isSavingsAccountChargeResource() {
return this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
public boolean isAddSavingsAccountCharge() {
return this.actionName.equalsIgnoreCase("CREATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
public boolean isDeleteSavingsAccountCharge() {
return isDeleteOperation() && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
public boolean isUpdateSavingsAccountCharge() {
return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
public boolean isWaiveSavingsAccountCharge() {
return this.actionName.equalsIgnoreCase("WAIVE") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
public boolean isPaySavingsAccountCharge() {
return this.actionName.equalsIgnoreCase("PAY") && this.entityName.equalsIgnoreCase("SAVINGSACCOUNTCHARGE");
}
public boolean isCalendarResource() {
return this.entityName.equalsIgnoreCase("CALENDAR");
}
@@ -941,6 +941,52 @@ public class CommandWrapperBuilder {
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 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 createCalendar(final String supportedEntityType, final Long supportedEntityId) {
this.actionName = "CREATE";
@@ -429,6 +429,20 @@ public class SynchronousCommandProcessingService implements CommandProcessingSer
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isSavingsAccountChargeResource()) {
if (wrapper.isAddSavingsAccountCharge()) {
handler = this.applicationContext.getBean("addSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isDeleteSavingsAccountCharge()) {
handler = this.applicationContext.getBean("deleteSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isUpdateSavingsAccountCharge()) {
handler = this.applicationContext.getBean("updateSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isWaiveSavingsAccountCharge()) {
handler = this.applicationContext.getBean("waiveSavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else if (wrapper.isPaySavingsAccountCharge()) {
handler = this.applicationContext.getBean("paySavingsAccountChargeCommandHandler", NewCommandSourceHandler.class);
} else {
throw new UnsupportedCommandException(wrapper.commandName());
}
} else if (wrapper.isCalendarResource()) {
if (wrapper.isCreate()) {
handler = this.applicationContext.getBean("createCalendarCommandHandler", NewCommandSourceHandler.class);
@@ -10,9 +10,11 @@ import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
/**
* Immutable data object for charge data.
@@ -125,4 +127,21 @@ public class ChargeData implements Comparable<ChargeData>, Serializable {
return LoanChargeData.newLoanChargeDetails(this.id, this.name, this.currency, this.amount, percentage, this.chargeTimeType,
this.chargeCalculationType, this.penalty,this.chargePaymentMode);
}
public SavingsAccountChargeData toSavingsAccountChargeData(){
final Long savingsChargeId = 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;
return SavingsAccountChargeData.instance(savingsChargeId, this.id, this.name, this.currency, this.amount, amountPaid, amountWaived,
amountWrittenOff, amountOutstanding, this.chargeTimeType, dueAsOfDate, this.chargeCalculationType, percentage,
amountPercentageAppliedTo, chargeOptions, this.penalty);
}
}
@@ -6,7 +6,9 @@
package org.mifosplatform.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;
@@ -17,10 +19,14 @@ import javax.persistence.UniqueConstraint;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.exception.ChargeDueAtDisbursementCannotBePenaltyException;
import org.mifosplatform.portfolio.charge.exception.ChargeParameterUpdateNotSupportedException;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.springframework.data.jpa.domain.AbstractPersistable;
@@ -93,7 +99,33 @@ public class Charge extends AbstractPersistable<Long> {
this.penalty = penalty;
this.active = active;
this.chargePaymentMode = paymentMode.getValue();
if (penalty && chargeTime.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); }
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
if (isSavingsCharge()) {
if (!isAllowedSavingsChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
}
if (!isAllowedSavingsChargeCalculationType()) {
baseDataValidator.reset().parameter("chargeCalculationType").value(this.chargeCalculation)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.calculation.type.for.savings");
}
} else if (isLoanCharge()) {
if (penalty && chargeTime.isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(name); }
if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@Override
@@ -145,12 +177,35 @@ public class Charge extends AbstractPersistable<Long> {
public boolean isDeleted() {
return deleted;
}
public boolean isLoanCharge(){
return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isLoanCharge();
}
public boolean isAllowedLoanChargeTime(){
return ChargeTimeType.fromInt(this.chargeTime).isAllowedLoanChargeTime();
}
public boolean isSavingsCharge(){
return ChargeAppliesTo.fromInt(this.chargeAppliesTo).isSavingsCharge();
}
public boolean isAllowedSavingsChargeTime(){
return ChargeTimeType.fromInt(this.chargeTime).isAllowedSavingsChargeTime();
}
public boolean isAllowedSavingsChargeCalculationType(){
return ChargeCalculationType.fromInt(this.chargeCalculation).isAllowedSavingsChargeCalculationType();
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<String, Object>(7);
final String localeAsInput = command.locale();
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charges");
final String nameParamName = "name";
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
@@ -180,14 +235,31 @@ public class Charge extends AbstractPersistable<Long> {
actualChanges.put(chargeTimeParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.chargeTime = ChargeTimeType.fromInt(newValue).getValue();
if (isSavingsCharge()) {
if (!isAllowedSavingsChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.savings");
}
} else if (isLoanCharge()) {
if (!isAllowedLoanChargeTime()) {
baseDataValidator.reset().parameter("chargeTimeType").value(this.chargeTime)
.failWithCodeNoParameterAddedToErrorCode("not.allowed.charge.time.for.loan");
}
}
}
final String chargeAppliesToParamName = "chargeAppliesTo";
if (command.isChangeInIntegerParameterNamed(chargeAppliesToParamName, this.chargeAppliesTo)) {
final Integer newValue = command.integerValueOfParameterNamed(chargeAppliesToParamName);
/*final Integer newValue = command.integerValueOfParameterNamed(chargeAppliesToParamName);
actualChanges.put(chargeAppliesToParamName, newValue);
actualChanges.put("locale", localeAsInput);
this.chargeAppliesTo = ChargeAppliesTo.fromInt(newValue).getValue();
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";
@@ -196,6 +268,13 @@ public class Charge extends AbstractPersistable<Long> {
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");
}
}
}
final String paymentModeParamName = "chargePaymentMode";
@@ -223,7 +302,9 @@ public class Charge extends AbstractPersistable<Long> {
if (this.penalty && ChargeTimeType.fromInt(this.chargeTime).isTimeOfDisbursement()) { throw new ChargeDueAtDisbursementCannotBePenaltyException(
name); }
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
return actualChanges;
}
@@ -8,7 +8,8 @@ package org.mifosplatform.portfolio.charge.domain;
public enum ChargeAppliesTo {
INVALID(0, "chargeAppliesTo.invalid"), //
LOAN(1, "chargeAppliesTo.loan");
LOAN(1, "chargeAppliesTo.loan"),
SAVINGS(2, "chargeAppliesTo.savings");
private final Integer value;
private final String code;
@@ -26,16 +27,35 @@ public enum ChargeAppliesTo {
return code;
}
public static int minValue(){
return 1;
}
public static int maxValue(){
return 2;//modify whenever a new value added
}
public static ChargeAppliesTo fromInt(final Integer chargeAppliesTo) {
ChargeAppliesTo chargeAppliesToType = ChargeAppliesTo.INVALID;
switch (chargeAppliesTo) {
case 1:
chargeAppliesToType = LOAN;
break;
case 2:
chargeAppliesToType = SAVINGS;
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());
}
}
@@ -47,4 +47,16 @@ public enum ChargeCalculationType {
}
return chargeCalculationType;
}
public boolean isPercentageOfAmount(){
return this.value.equals(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue());
}
public boolean isFlat(){
return this.value.equals(ChargeCalculationType.FLAT.getValue());
}
public boolean isAllowedSavingsChargeCalculationType(){
return this.isFlat();
}
}
@@ -5,11 +5,14 @@
*/
package org.mifosplatform.portfolio.charge.domain;
public enum ChargeTimeType {
INVALID(0, "chargeTimeType.invalid"), //
DISBURSEMENT(1, "chargeTimeType.disbursement"), //
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate");
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate"),
MONTHLY(3, "chargeTimeType.monthly"),
YEARLY(4, "chargeTimeType.yearly");
private final Integer value;
private final String code;
@@ -27,6 +30,14 @@ public enum ChargeTimeType {
return code;
}
public static int minValue(){
return 1;
}
public static int maxValue(){
return 4;
}
public static ChargeTimeType fromInt(final Integer chargeTime) {
ChargeTimeType chargeTimeType = ChargeTimeType.INVALID;
switch (chargeTime) {
@@ -36,6 +47,12 @@ public enum ChargeTimeType {
case 2:
chargeTimeType = SPECIFIED_DUE_DATE;
break;
case 3:
chargeTimeType = MONTHLY;
break;
case 4:
chargeTimeType = YEARLY;
break;
default:
chargeTimeType = INVALID;
break;
@@ -46,4 +63,24 @@ public enum 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 isMonthly(){
return this.value.equals(ChargeTimeType.MONTHLY.getValue());
}
public boolean isYearly(){
return this.value.equals(ChargeTimeType.YEARLY.getValue());
}
public boolean isAllowedLoanChargeTime(){
return this.isTimeOfDisbursement() || this.isOnSpecifiedDueDate();
}
public boolean isAllowedSavingsChargeTime(){
return this.isOnSpecifiedDueDate() || this.isMonthly() || this.isYearly();
}
}
@@ -0,0 +1,13 @@
package org.mifosplatform.portfolio.charge.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class ChargeCannotBeAppliedToException extends AbstractPlatformDomainRuleException {
public ChargeCannotBeAppliedToException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg.charge.cannot.be.applied.to" + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,18 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.charge.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when Calendar resources are not found.
*/
public class ChargeParameterUpdateNotSupportedException extends AbstractPlatformDomainRuleException {
public ChargeParameterUpdateNotSupportedException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg.charge.update.of." + postFix + ".is.not.supported", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,43 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.charge.exception;
import org.mifosplatform.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);
}
}
@@ -0,0 +1,25 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.charge.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
import org.mifosplatform.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);
}
}
@@ -0,0 +1,19 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.charge.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when currency mismatch occurs
*/
public class SavingsAccountChargeWithoutMandatoryFieldException extends AbstractPlatformDomainRuleException {
public SavingsAccountChargeWithoutMandatoryFieldException(final String entity, final String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg."+entity+"."+postFix+".cannot.be.blank", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -21,6 +21,8 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.charge.domain.ChargeAppliesTo;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -65,10 +67,10 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("amount").value(amount).notNull().positiveAmount();
final Integer chargeAppliesTo = fromApiJsonHelper.extractIntegerNamed("chargeAppliesTo", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(ChargeAppliesTo.minValue(), ChargeAppliesTo.maxValue());
final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(1, 2);
baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(ChargeTimeType.minValue(), ChargeTimeType.maxValue());
final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed("chargeCalculationType", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType).notNull().inMinMaxRange(1, 4);
@@ -116,12 +118,12 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
if (fromApiJsonHelper.parameterExists("chargeAppliesTo", element)) {
final Integer chargeAppliesTo = fromApiJsonHelper.extractIntegerNamed("chargeAppliesTo", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeAppliesTo").value(chargeAppliesTo).notNull().inMinMaxRange(ChargeAppliesTo.minValue(), ChargeAppliesTo.maxValue());
}
if (fromApiJsonHelper.parameterExists("chargeTimeType", element)) {
final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed("chargeTimeType", element, Locale.getDefault());
baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(1, 2);
baseDataValidator.reset().parameter("chargeTimeType").value(chargeTimeType).notNull().inMinMaxRange(ChargeTimeType.minValue(), ChargeTimeType.maxValue());
}
if (fromApiJsonHelper.parameterExists("chargeCalculationType", element)) {
@@ -5,11 +5,10 @@
*/
package org.mifosplatform.portfolio.charge.service;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeAppliesTo;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeCalculationType;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargeTimeType;
import static org.mifosplatform.portfolio.charge.service.ChargeEnumerations.chargePaymentMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -35,12 +34,25 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
@Override
public List<EnumOptionData> retrieveApplicableToTypes() {
return Arrays.asList(chargeAppliesTo(ChargeAppliesTo.LOAN));
List<EnumOptionData> chargeAppliesToTypes = new ArrayList<EnumOptionData>();
for (ChargeAppliesTo chargeAppliesTo : ChargeAppliesTo.values()) {
if(ChargeAppliesTo.INVALID.equals(chargeAppliesTo)) continue;
chargeAppliesToTypes.add(ChargeEnumerations.chargeAppliesTo(chargeAppliesTo));
}
return chargeAppliesToTypes;
}
@Override
public List<EnumOptionData> retrieveCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE));
List<EnumOptionData> chargeTimeTypes = new ArrayList<EnumOptionData>();
for (ChargeTimeType chargeTimeType : ChargeTimeType.values()) {
if (ChargeTimeType.INVALID.equals(chargeTimeType)
|| ChargeTimeType.MONTHLY.equals(chargeTimeType) // To be implemented for Savings
|| ChargeTimeType.YEARLY.equals(chargeTimeType)) // To be implemented for Savings
continue;
chargeTimeTypes.add(ChargeEnumerations.chargeTimeType(chargeTimeType));
}
return chargeTimeTypes;
}
@Override
@@ -28,6 +28,14 @@ public class ChargeEnumerations {
optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(),
ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date");
break;
case MONTHLY:
optionData = new EnumOptionData(ChargeTimeType.MONTHLY.getValue().longValue(),
ChargeTimeType.MONTHLY.getCode(), "Monthly");
break;
case YEARLY:
optionData = new EnumOptionData(ChargeTimeType.YEARLY.getValue().longValue(),
ChargeTimeType.YEARLY.getCode(), "Yearly");
break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break;
@@ -45,6 +53,9 @@ public class ChargeEnumerations {
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;
default:
optionData = new EnumOptionData(ChargeAppliesTo.INVALID.getValue().longValue(), ChargeAppliesTo.INVALID.getCode(),
"Invalid");
@@ -22,4 +22,10 @@ public interface ChargeReadPlatformService {
Collection<ChargeData> retrieveLoanApplicablePenalties();
Collection<ChargeData> retrieveLoanProductCharges(Long loanProductId);
Collection<ChargeData> retrieveSavingsAccountApplicableCharges(boolean feeChargesOnly);
Collection<ChargeData> retrieveSavingsAccountApplicablePenalties();
Collection<ChargeData> retrieveSavingsProductCharges(Long savingsProductId);
}
@@ -141,6 +141,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
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 {
@@ -179,4 +183,42 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
}
}
@Override
public Collection<ChargeData> retrieveSavingsAccountApplicableCharges(boolean feeChargesOnly) {
this.context.authenticatedUser();
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=? order by c.name ";
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=? order by c.name ";
}
return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.SAVINGS.getValue() });
}
@Override
public Collection<ChargeData> retrieveSavingsAccountApplicablePenalties() {
this.context.authenticatedUser();
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=? order by c.name ";
return this.jdbcTemplate.query(sql, rm, new Object[] { ChargeAppliesTo.SAVINGS.getValue() });
}
@Override
public Collection<ChargeData> retrieveSavingsProductCharges(Long savingsProductId) {
this.context.authenticatedUser();
final ChargeMapper rm = new ChargeMapper();
final String sql = "select " + rm.savingsProductChargeSchema() + " where c.is_deleted=0 and c.is_active=1 and spc.savings_product_id=?";
return this.jdbcTemplate.query(sql, rm, new Object[] { savingsProductId });
}
}
@@ -19,6 +19,8 @@ public enum SavingsAccountTransactionType {
INTEREST_POSTING(3, "savingsAccountTransactionType.interestPosting"), //
WITHDRAWAL_FEE(4, "savingsAccountTransactionType.withdrawalFee"), //
ANNUAL_FEE(5, "savingsAccountTransactionType.annualFee"), //
WAIVE_CHARGES(6, "savingsAccountTransactionType.waiveCharges"),
APPLY_CHARGES(7, "savingsAccountTransactionType.applyCharges"),
INITIATE_TRANSFER(12, "savingsAccountTransactionType.initiateTransfer"), //
APPROVE_TRANSFER(13, "savingsAccountTransactionType.approveTransfer"), //
WITHDRAW_TRANSFER(14, "savingsAccountTransactionType.withdrawTransfer"), //
@@ -44,37 +46,43 @@ public enum SavingsAccountTransactionType {
if (transactionType == null) { return SavingsAccountTransactionType.INVALID; }
SavingsAccountTransactionType loanTransactionType = SavingsAccountTransactionType.INVALID;
SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.INVALID;
switch (transactionType) {
case 1:
loanTransactionType = SavingsAccountTransactionType.DEPOSIT;
savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT;
break;
case 2:
loanTransactionType = SavingsAccountTransactionType.WITHDRAWAL;
savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAWAL;
break;
case 3:
loanTransactionType = SavingsAccountTransactionType.INTEREST_POSTING;
savingsAccountTransactionType = SavingsAccountTransactionType.INTEREST_POSTING;
break;
case 4:
loanTransactionType = SavingsAccountTransactionType.WITHDRAWAL_FEE;
savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAWAL_FEE;
break;
case 5:
loanTransactionType = SavingsAccountTransactionType.ANNUAL_FEE;
savingsAccountTransactionType = SavingsAccountTransactionType.ANNUAL_FEE;
break;
case 6:
savingsAccountTransactionType = SavingsAccountTransactionType.WAIVE_CHARGES;
break;
case 7:
savingsAccountTransactionType = SavingsAccountTransactionType.APPLY_CHARGES;
break;
case 12:
loanTransactionType = SavingsAccountTransactionType.INITIATE_TRANSFER;
savingsAccountTransactionType = SavingsAccountTransactionType.INITIATE_TRANSFER;
break;
case 13:
loanTransactionType = SavingsAccountTransactionType.APPROVE_TRANSFER;
savingsAccountTransactionType = SavingsAccountTransactionType.APPROVE_TRANSFER;
break;
case 14:
loanTransactionType = SavingsAccountTransactionType.WITHDRAW_TRANSFER;
savingsAccountTransactionType = SavingsAccountTransactionType.WITHDRAW_TRANSFER;
break;
case 15:
loanTransactionType = SavingsAccountTransactionType.REJECT_TRANSFER;
savingsAccountTransactionType = SavingsAccountTransactionType.REJECT_TRANSFER;
break;
}
return loanTransactionType;
return savingsAccountTransactionType;
}
public boolean isDeposit() {
@@ -96,9 +104,17 @@ public enum SavingsAccountTransactionType {
public boolean isAnnualFee() {
return this.value.equals(SavingsAccountTransactionType.ANNUAL_FEE.getValue());
}
public boolean isCharge() {
return this.value.equals(SavingsAccountTransactionType.APPLY_CHARGES.getValue());
}
public boolean isWaiveCharge() {
return this.value.equals(SavingsAccountTransactionType.WAIVE_CHARGES.getValue());
}
public boolean isDebit() {
return isWithdrawal() || isWithdrawalFee() || isAnnualFee();
return isWithdrawal() || isWithdrawalFee() || isAnnualFee() || isCharge();
}
public boolean isCredit() {
@@ -18,6 +18,8 @@ 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";
@@ -32,10 +34,13 @@ public class SavingsApiConstants {
public static String applyAnnualFeeTransactionAction = ".applyannualfee";
public static String adjustTransactionAction = ".adjusttransaction";
public static String closeAction = ".close";
public static String payChargeTransactionAction = ".paycharge";
// 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";
// general
public static final String localeParamName = "locale";
@@ -93,6 +98,26 @@ public class SavingsApiConstants {
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 noteParamName = "note";
@@ -102,7 +127,8 @@ public class SavingsApiConstants {
interestPostingPeriodTypeParamName, interestCalculationTypeParamName, interestCalculationDaysInYearTypeParamName,
minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
withdrawalFeeAmountParamName, withdrawalFeeTypeParamName, withdrawalFeeForTransfersParamName, annualFeeAmountParamName,
annualFeeOnMonthDayParamName, accountingRuleParamName, SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(),
annualFeeOnMonthDayParamName, accountingRuleParamName, chargesParamName,
SAVINGS_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.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(),
@@ -128,7 +154,7 @@ public class SavingsApiConstants {
interestCompoundingPeriodTypeParamName, interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName, lockinPeriodFrequencyParamName,
lockinPeriodFrequencyTypeParamName, withdrawalFeeAmountParamName, withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName));
withdrawalFeeForTransfersParamName, annualFeeAmountParamName, annualFeeOnMonthDayParamName, chargesParamName));
/**
* These parameters will match the class level parameters of
@@ -158,4 +184,13 @@ public class SavingsApiConstants {
public static final Set<String> SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<String>(Arrays.asList(localeParamName,
dateFormatParamName, closedOnDateParamName, noteParamName));
public static final Set<String> SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<String>(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<String>(Arrays.asList(
chargeIdParamName, amountParamName, dueAsOfDateParamName, dateFormatParamName, localeParamName));
}
@@ -0,0 +1,198 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.api;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.COMMAND_WAIVE_CHARGE;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.COMMAND_PAY_CHARGE;
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.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
import org.mifosplatform.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,
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
final Collection<SavingsAccountChargeData> savingsAccountCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(savingsAccountId);
final ApiRequestJsonSerializationSettings settings = 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(@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
final boolean feeChargesOnly = false;
final Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicableCharges(feeChargesOnly);
final SavingsAccountChargeData savingsAccountChargeTemplate = SavingsAccountChargeData.template(chargeOptions);
final ApiRequestJsonSerializationSettings settings = 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) {
context.authenticatedUser().validateHasReadPermission(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
final SavingsAccountChargeData savingsAccountCharge = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountChargeDetails(savingsAccountChargeId, savingsAccountId);
final ApiRequestJsonSerializationSettings settings = 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 waiveSavingsAccountCharge(@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 {
throw new UnrecognizedQueryParamException("command", commandParam, COMMAND_PAY_CHARGE, COMMAND_WAIVE_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);
}
}
@@ -38,8 +38,10 @@ import org.mifosplatform.infrastructure.core.service.Page;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.group.service.SearchParameters;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionData;
import org.mifosplatform.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
import org.mifosplatform.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
@@ -56,17 +58,19 @@ public class SavingsAccountsApiResource {
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 ApiRequestParameterHelper apiRequestParameterHelper, final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService) {
this.savingsAccountReadPlatformService = savingsAccountReadPlatformService;
this.context = context;
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
this.savingsAccountChargeReadPlatformService = savingsAccountChargeReadPlatformService;
}
@GET
@@ -143,12 +147,13 @@ public class SavingsAccountsApiResource {
final boolean staffInSelectedOfficeOnly, 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("transactions"));
associationParameters.addAll(Arrays.asList("transactions", "charges"));
}
if (associationParameters.contains("transactions")) {
@@ -159,6 +164,15 @@ public class SavingsAccountsApiResource {
transactions = currentTransactions;
}
}
if (associationParameters.contains("charges")) {
mandatoryResponseParameters.add("charges");
final Collection<SavingsAccountChargeData> currentCharges = this.savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId);
if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges;
}
}
}
SavingsAccountData templateData = null;
@@ -168,7 +182,7 @@ public class SavingsAccountsApiResource {
savingsAccount.productId(), staffInSelectedOfficeOnly);
}
return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions);
return SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges);
}
@PUT
@@ -41,6 +41,8 @@ import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSeria
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.paymentdetail.PaymentDetailConstants;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType;
@@ -54,6 +56,7 @@ import org.mifosplatform.portfolio.savings.service.SavingsProductReadPlatformSer
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
@@ -70,6 +73,7 @@ public class SavingsProductsApiResource {
private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService;
private final ChargeReadPlatformService chargeReadPlatformService;
@Autowired
public SavingsProductsApiResource(final SavingsProductReadPlatformService savingProductReadPlatformService,
@@ -79,7 +83,8 @@ public class SavingsProductsApiResource {
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper, final CodeValueReadPlatformService codeValueReadPlatformService,
final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService) {
final ProductToGLAccountMappingReadPlatformService accountMappingReadPlatformService,
final ChargeReadPlatformService chargeReadPlatformService) {
this.savingProductReadPlatformService = savingProductReadPlatformService;
this.dropdownReadPlatformService = dropdownReadPlatformService;
this.currencyReadPlatformService = currencyReadPlatformService;
@@ -90,6 +95,7 @@ public class SavingsProductsApiResource {
this.codeValueReadPlatformService = codeValueReadPlatformService;
this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
this.accountMappingReadPlatformService = accountMappingReadPlatformService;
this.chargeReadPlatformService = chargeReadPlatformService;
}
@POST
@@ -141,6 +147,10 @@ public class SavingsProductsApiResource {
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 = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
@@ -220,19 +230,27 @@ public class SavingsProductsApiResource {
.retrieveAccountingRuleTypeOptions();
final Map<String, List<GLAccountData>> accountingMappingOptions = accountingDropdownReadPlatformService
.retrieveAccountMappingOptionsForSavingsProducts();
//charges
final boolean feeChargesOnly = true;
Collection<ChargeData> chargeOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicableCharges(feeChargesOnly);
chargeOptions = CollectionUtils.isEmpty(chargeOptions) ? null : chargeOptions;
Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsAccountApplicablePenalties();
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);
accountingMappingOptions, chargeOptions, penaltyOptions);
} else {
savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions);
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions);
}
return savingsProductToReturn;
@@ -0,0 +1,127 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.data;
import java.math.BigDecimal;
import java.util.Collection;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
/**
* Immutable data object for Savings Account charge data.
*/
public class SavingsAccountChargeData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final Long chargeId;
@SuppressWarnings("unused")
private final String name;
@SuppressWarnings("unused")
private final EnumOptionData chargeTimeType;
@SuppressWarnings("unused")
private final LocalDate dueDate;
private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage;
@SuppressWarnings("unused")
private final BigDecimal amountPercentageAppliedTo;
@SuppressWarnings("unused")
private final CurrencyData currency;
private final BigDecimal amount;
@SuppressWarnings("unused")
private final BigDecimal amountPaid;
@SuppressWarnings("unused")
private final BigDecimal amountWaived;
@SuppressWarnings("unused")
private final BigDecimal amountWrittenOff;
@SuppressWarnings("unused")
private final BigDecimal amountOutstanding;
@SuppressWarnings("unused")
private final BigDecimal amountOrPercentage;
@SuppressWarnings("unused")
private final Collection<ChargeData> chargeOptions;
@SuppressWarnings("unused")
private final boolean penalty;
public static SavingsAccountChargeData template(final Collection<ChargeData> chargeOptions) {
final Long id = null;
final Long chargeId = 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;
return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage, amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeOptions, penalty);
}
public static SavingsAccountChargeData instance(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 dueAsOfDate,
final EnumOptionData chargeCalculationType, final BigDecimal percentage, final BigDecimal amountPercentageAppliedTo,
final Collection<ChargeData> chargeOptions, final boolean penalty) {
return new SavingsAccountChargeData(id, chargeId, name, chargeTimeType, dueAsOfDate, chargeCalculationType, percentage,
amountPercentageAppliedTo, currency, amount, amountPaid, amountWaived, amountWrittenOff,
amountOutstanding, chargeOptions, penalty);
}
private SavingsAccountChargeData(Long id, Long chargeId, String name, EnumOptionData chargeTimeType, LocalDate dueAsOfDate,
EnumOptionData chargeCalculationType, BigDecimal percentage, BigDecimal amountPercentageAppliedTo, CurrencyData currency,
BigDecimal amount, BigDecimal amountPaid, BigDecimal amountWaived, BigDecimal amountWrittenOff, BigDecimal amountOutstanding,
Collection<ChargeData> chargeOptions, boolean penalty) {
this.id = id;
this.chargeId = chargeId;
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 = this.getAmountOrPercentage();
this.chargeOptions = chargeOptions;
this.penalty = penalty;
}
private BigDecimal getAmountOrPercentage() {
return (this.chargeCalculationType != null) && (chargeCalculationType.getId().intValue() > 1) ? this.percentage : this.amount;
}
}
@@ -0,0 +1,89 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.data;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
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.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.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 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();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
final JsonElement element = fromApiJsonHelper.parse(json);
final Long chargeId = fromApiJsonHelper.extractLongNamed(chargeIdParamName, element);
baseDataValidator.reset().parameter(chargeIdParamName).value(chargeId).notNull().integerGreaterThanZero();
final BigDecimal amount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
if (fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) {
fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateUpdate(final String json) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SAVINGS_ACCOUNT_CHARGES_ADD_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME);
final JsonElement element = fromApiJsonHelper.parse(json);
final BigDecimal amount = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(amountParamName, element);
baseDataValidator.reset().parameter(amountParamName).value(amount).notNull().positiveAmount();
if (fromApiJsonHelper.parameterExists(dueAsOfDateParamName, element)) {
fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, element);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -15,6 +15,7 @@ import org.joda.time.MonthDay;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
/**
* Immutable data object representing a savings account.
@@ -54,6 +55,7 @@ public class SavingsAccountData {
private final SavingsAccountSummaryData summary;
@SuppressWarnings("unused")
private final Collection<SavingsAccountTransactionData> transactions;
private final Collection<SavingsAccountChargeData> charges;
// template
private final Collection<SavingsProductData> productOptions;
@@ -64,6 +66,7 @@ public class SavingsAccountData {
private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
private final Collection<EnumOptionData> withdrawalFeeTypeOptions;
private final Collection<ChargeData> chargeOptions;
public static SavingsAccountData 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,
@@ -86,6 +89,8 @@ public class SavingsAccountData {
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, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, interestRate, interestCompoundingPeriodType,
@@ -93,7 +98,7 @@ public class SavingsAccountData {
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeForTransfers,
annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions);
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions);
}
public static SavingsAccountData lookup(final Long accountId, final String accountNo) {
@@ -108,16 +113,18 @@ public class SavingsAccountData {
final Collection<EnumOptionData> withdrawalFeeTypeOptions = null;
final Collection<SavingsAccountTransactionData> transactions = null;
final Collection<SavingsAccountChargeData> charges = null;
final Collection<ChargeData> chargeOptions = null;
return new SavingsAccountData(accountId, accountNo, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, false, null, null, null, null, transactions, productOptions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions);
withdrawalFeeTypeOptions, charges, chargeOptions);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template,
final Collection<SavingsAccountTransactionData> transactions) {
final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges) {
if (template == null) {
final Collection<SavingsProductData> productOptions = null;
@@ -128,10 +135,11 @@ public class SavingsAccountData {
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);
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions);
}
return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
@@ -143,7 +151,7 @@ public class SavingsAccountData {
account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate, account.summary, transactions, template.productOptions,
template.fieldOfficerOptions, template.interestCompoundingPeriodTypeOptions, template.interestPostingPeriodTypeOptions,
template.interestCalculationTypeOptions, template.interestCalculationDaysInYearTypeOptions,
template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions);
template.lockinPeriodFrequencyTypeOptions, template.withdrawalFeeTypeOptions, charges, template.chargeOptions);
}
public static SavingsAccountData withTemplateOptions(final SavingsAccountData account,
@@ -153,7 +161,7 @@ public class SavingsAccountData {
final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<SavingsAccountTransactionData> transactions) {
final Collection<SavingsAccountTransactionData> transactions, final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions) {
return new SavingsAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName,
account.clientId, account.clientName, account.savingsProductId, account.savingsProductName, account.fieldOfficerId,
@@ -164,7 +172,7 @@ public class SavingsAccountData {
account.annualFeeAmount, account.annualFeeOnMonthDay, account.annualFeeNextDueDate, account.summary, transactions, productOptions,
fieldOfficerOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions,
interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions,
withdrawalFeeTypeOptions);
withdrawalFeeTypeOptions, charges, chargeOptions);
}
public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId,
@@ -207,13 +215,16 @@ public class SavingsAccountData {
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, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeForTransfers,
annualFeeAmount, annualFeeOnMonthDay, annualFeeNextDueDate, summary, transactions, productOptions, fieldOfficerOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions);
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions);
}
private SavingsAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, final String groupName,
@@ -230,7 +241,7 @@ public class SavingsAccountData {
final Collection<EnumOptionData> interestPostingPeriodTypeOptions,
final Collection<EnumOptionData> interestCalculationTypeOptions,
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions) {
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions, final Collection<SavingsAccountChargeData> charges, final Collection<ChargeData> chargeOptions) {
this.id = id;
this.accountNo = accountNo;
this.externalId = externalId;
@@ -271,6 +282,9 @@ public class SavingsAccountData {
this.interestCalculationDaysInYearTypeOptions = interestCalculationDaysInYearTypeOptions;
this.lockinPeriodFrequencyTypeOptions = lockinPeriodFrequencyTypeOptions;
this.withdrawalFeeTypeOptions = withdrawalFeeTypeOptions;
this.charges = charges;//charges associated with Savings account
this.chargeOptions = chargeOptions;//charges available for adding to Savings account
}
public Long id() {
@@ -10,7 +10,7 @@ import java.math.BigDecimal;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
/**
* Immutable data object representing loan summary information.
* Immutable data object representing Savings Account summary information.
*/
@SuppressWarnings("unused")
public class SavingsAccountSummaryData {
@@ -23,10 +23,13 @@ public class SavingsAccountSummaryData {
private final BigDecimal totalInterestEarned;
private final BigDecimal totalInterestPosted;
private final BigDecimal accountBalance;
private final BigDecimal totalFeeCharge;
private final BigDecimal totalPenaltyCharge;
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 totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge,
final BigDecimal totalPenaltyCharge) {
this.currency = currency;
this.totalDeposits = totalDeposits;
this.totalWithdrawals = totalWithdrawals;
@@ -35,5 +38,7 @@ public class SavingsAccountSummaryData {
this.totalInterestEarned = totalInterestEarned;
this.totalInterestPosted = totalInterestPosted;
this.accountBalance = accountBalance;
this.totalFeeCharge = totalFeeCharge;
this.totalPenaltyCharge = totalPenaltyCharge;
}
}
@@ -17,6 +17,7 @@ import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGL
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
/**
* Immutable data object represent a savings product.
@@ -46,28 +47,23 @@ public class SavingsProductData {
private final Map<String, Object> accountingMappings;
private final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings;
// charges
private final Collection<ChargeData> charges;
// template
@SuppressWarnings("unused")
private final Collection<CurrencyData> currencyOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestCompoundingPeriodTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestPostingPeriodTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestCalculationTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> withdrawalFeeTypeOptions;
@SuppressWarnings("unused")
private final Collection<CodeValueData> paymentTypeOptions;
@SuppressWarnings("unused")
private final Collection<EnumOptionData> accountingRuleOptions;
@SuppressWarnings("unused")
private final Map<String, List<GLAccountData>> accountingMappingOptions;
private final Collection<ChargeData> chargeOptions;
private final Collection<ChargeData> penaltyOptions;
public static SavingsProductData template(final CurrencyData currency, final EnumOptionData interestCompoundingPeriodType,
final EnumOptionData interestPostingPeriodType, final EnumOptionData interestCalculationType,
final EnumOptionData interestCalculationDaysInYearType, final EnumOptionData accountingRule,
@@ -77,7 +73,7 @@ public class SavingsProductData {
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<CodeValueData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions) {
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions) {
final Long id = null;
final String name = null;
@@ -93,6 +89,7 @@ public class SavingsProductData {
final MonthDay annualFeeMonthDay = null;
final Map<String, Object> accountingMappings = null;
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings = null;
final Collection<ChargeData> charges = null;
return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
@@ -100,9 +97,22 @@ public class SavingsProductData {
accountingRule, annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions);
accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions);
}
public static SavingsProductData withCharges(final SavingsProductData product, final Collection<ChargeData> charges) {
return new SavingsProductData(product.id, product.name, product.description, product.currency, product.nominalAnnualInterestRate,
product.interestCompoundingPeriodType, product.interestPostingPeriodType, product.interestCalculationType,
product.interestCalculationDaysInYearType, product.minRequiredOpeningBalance, product.lockinPeriodFrequency,
product.lockinPeriodFrequencyType, product.withdrawalFeeAmount, product.withdrawalFeeType,
product.withdrawalFeeForTransfers, product.accountingRule, product.annualFeeAmount, product.annualFeeOnMonthDay,
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);
}
/**
* Returns a {@link SavingsProductData} that contains and exist
* {@link SavingsProductData} data with further template data for dropdowns.
@@ -114,7 +124,7 @@ public class SavingsProductData {
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<CodeValueData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions) {
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions) {
return new SavingsProductData(existingProduct.id, existingProduct.name, existingProduct.description, existingProduct.currency,
existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
@@ -126,7 +136,7 @@ public class SavingsProductData {
existingProduct.paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
accountingMappingOptions);
accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions);
}
public static SavingsProductData withAccountingDetails(final SavingsProductData existingProduct,
@@ -142,6 +152,8 @@ public class SavingsProductData {
final Collection<CodeValueData> 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.description, existingProduct.currency,
existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType,
@@ -152,7 +164,7 @@ public class SavingsProductData {
existingProduct.annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions);
accountingRuleOptions, accountingMappingOptions, existingProduct.charges, chargeOptions, penaltyOptions);
}
public static SavingsProductData instance(final Long id, final String name, final String description, final CurrencyData currency,
@@ -176,6 +188,9 @@ public class SavingsProductData {
final Collection<CodeValueData> 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;
return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
@@ -183,7 +198,7 @@ public class SavingsProductData {
annualFeeAmount, annualFeeOnMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions);
accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions);
}
public static SavingsProductData lookup(final Long id, final String name) {
@@ -217,6 +232,9 @@ public class SavingsProductData {
final Collection<CodeValueData> 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;
return new SavingsProductData(id, name, description, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
@@ -224,7 +242,7 @@ public class SavingsProductData {
annualFeeAmount, annualFeeMonthDay, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions);
accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, penaltyOptions);
}
private SavingsProductData(final Long id, final String name, final String description, final CurrencyData currency,
@@ -241,7 +259,7 @@ public class SavingsProductData {
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions,
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions, final Collection<EnumOptionData> withdrawalFeeTypeOptions,
final Collection<CodeValueData> paymentTypeOptions, final Collection<EnumOptionData> accountingRuleOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions) {
final Map<String, List<GLAccountData>> accountingMappingOptions, final Collection<ChargeData> charges, final Collection<ChargeData> chargeOptions, final Collection<ChargeData> penaltyOptions) {
this.id = id;
this.name = name;
this.description = description;
@@ -278,6 +296,10 @@ public class SavingsProductData {
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
}
public boolean hasAccountingEnabled() {
@@ -22,10 +22,12 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
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;
@@ -62,6 +64,8 @@ import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
@@ -78,6 +82,9 @@ import org.mifosplatform.portfolio.savings.exception.InsufficientAccountBalanceE
import org.mifosplatform.portfolio.savings.service.SavingsEnumerations;
import org.mifosplatform.useradministration.domain.AppUser;
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"),
@@ -242,6 +249,10 @@ public class SavingsAccount extends AbstractPersistable<Long> {
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true)
private final List<SavingsAccountTransaction> transactions = new ArrayList<SavingsAccountTransaction>();
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true)
private Set<SavingsAccountCharge> charges = new HashSet<SavingsAccountCharge>();
@Transient
private boolean accountNumberRequiresAutoGeneration = false;
@@ -262,13 +273,15 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType,
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay) {
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay,
final Set<SavingsAccountCharge> savingsAccountCharges) {
final SavingsAccountStatusType status = SavingsAccountStatusType.SUBMITTED_AND_PENDING_APPROVAL;
return new SavingsAccount(client, group, product, fieldOfficer, accountNo, externalId, status, accountType, submittedOnDate,
interestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount, annualFeeOnMonthDay);
withdrawalFeeAmount, withdrawalFeeType, withdrawalFeeApplicableForTransfer, annualFeeAmount, annualFeeOnMonthDay,
savingsAccountCharges);
}
private SavingsAccount(final Client client, final Group group, final SavingsProduct product, final Staff fieldOfficer,
@@ -279,7 +292,8 @@ public class SavingsAccount extends AbstractPersistable<Long> {
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType,
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay) {
final boolean withdrawalFeeApplicableForTransfer, final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay,
final Set<SavingsAccountCharge> savingsAccountCharges) {
this.client = client;
this.group = group;
this.product = product;
@@ -317,6 +331,12 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.annualFeeOnDay = annualFeeOnMonthDay.getDayOfMonth();
}
if (!CollectionUtils.isEmpty(savingsAccountCharges)) {
this.charges = associateChargesWithThisSavingsAccount(savingsAccountCharges);
//TODO:Add charges details to savings summary
//this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
this.summary = new SavingsAccountSummary();
}
@@ -1074,12 +1094,23 @@ public class SavingsAccount extends AbstractPersistable<Long> {
this.annualFeeOnMonth = monthOfYear;
}
}
//charges
final String chargesParamName = "charges";
if (command.hasParameter(chargesParamName)) {
JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
if (jsonArray != null) {
actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName));
}
}
// set period type to null if frequency is null
if (this.annualFeeAmount == null) {
this.annualFeeOnDay = null;
this.annualFeeOnMonth = null;
}
validateLockinDetails();
validateWithdrawalFeeDetails();
@@ -1457,6 +1488,13 @@ public class SavingsAccount extends AbstractPersistable<Long> {
.withMonthOfYear(this.annualFeeOnMonth).withDayOfMonth(this.annualFeeOnDay).plusYears(1);
this.annualFeeNextDueDate = newAnnualFeeNextDueDate.toDate();
}
} else if (transactionToUndo.isCharge()) {
//undo charge
final Set<SavingsAccountChargePaidBy> chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid();
for (SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) {
final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge();
chargeToUndo.resetPaidAmount(currency);
}
}
}
@@ -1809,5 +1847,193 @@ public class SavingsAccount extends AbstractPersistable<Long> {
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<SavingsAccountCharge>();
this.charges.clear();
this.charges.addAll(associateChargesWithThisSavingsAccount(newSavingsAccountCharges));
return true;
}
public boolean hasCurrencyCodeOf(final String matchingCurrencyCode) {
//TODO AA: revisit
if(this.currency == null) return false;
return this.currency.getCode().equalsIgnoreCase(matchingCurrencyCode);
}
public void removeCharge(final SavingsAccountCharge charge){
this.charges.remove(charge);
}
public void waiveCharge(final Long savingsAccountChargeId, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate");
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 = this.getCharge(savingsAccountChargeId);
// 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); }
}
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
//waive charge
final Money amountWaived = savingsAccountCharge.waive(this.getCurrency());
//TODO : AA revisit the code
/*Money feeChargesWaived = amountWaived;
Money penaltyChargesWaived = Money.zero(this.getCurrency());
if (savingsAccountCharge.isPenaltyCharge()) {
penaltyChargesWaived = amountWaived;
feeChargesWaived = Money.zero(this.getCurrency());
}*/
//persist an entry in transaction details
SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, this.office(), savingsAccountCharge.getDueLocalDate(), amountWaived);
this.transactions.add(chargeTransaction);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
final MathContext mc = MathContext.DECIMAL64;
calculateInterestUsing(mc, savingsAccountCharge.getDueLocalDate());
}
public void addCharge(final DateTimeFormatter formatter, final SavingsAccountCharge savingsAccountCharge, final Charge chargeDefinition){
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate");
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 (!this.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 (chargeDueDate != null && chargeDueDate.isBefore(getActivationLocalDate())) {
baseDataValidator.reset().parameter("dueDate").value(getActivationLocalDate().toString(formatter))
.failWithCodeNoParameterAddedToErrorCode("before.activationDate");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
//add new charge to savings account
getCharges().add(savingsAccountCharge);
}
public void payCharge(final Long savingsAccountChargeId, final BigDecimal amountPaid, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(SAVINGS_ACCOUNT_RESOURCE_NAME + "dueDate");
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 = this.getCharge(savingsAccountChargeId);
// 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); }
}
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final Money chargePaid = savingsAccountCharge.pay(this.getCurrency(), amountPaid);
final LocalDate transactionDate = (savingsAccountCharge.getDueLocalDate() == null) ? DateUtils.getLocalDateOfTenant()
: savingsAccountCharge.getDueLocalDate();//If null reset to current date
//persist an entry in transaction details
SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.charge(this, this.office(), transactionDate, chargePaid);
final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(chargeTransaction, savingsAccountCharge, amountPaid);
chargeTransaction.getSavingsAccountChargesPaid().add(chargePaidBy);
this.transactions.add(chargeTransaction);
final MathContext mc = MathContext.DECIMAL64;
if (isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
postInterest(mc, today, existingTransactionIds, existingReversedTransactionIds);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
calculateInterestUsing(mc, today);
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.payChargeTransactionAction);
}
private SavingsAccountCharge getCharge(Long savingsAccountChargeId) {
SavingsAccountCharge charge = null;
for (SavingsAccountCharge existingCharge : this.charges) {
if(existingCharge.getId().equals(savingsAccountChargeId)) {
charge = existingCharge;
break;
}
}
if(charge == null){
throw new SavingsAccountChargeNotFoundException(savingsAccountChargeId, getId());
}
return charge;
}
private Set<SavingsAccountCharge> getCharges(){
return (this.charges == null) ? new HashSet<SavingsAccountCharge>() : this.charges;
}
}
@@ -27,6 +27,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawal
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import java.math.BigDecimal;
import java.util.Set;
import org.joda.time.LocalDate;
import org.joda.time.MonthDay;
@@ -66,19 +67,22 @@ public class SavingsAccountAssembler {
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 FromJsonHelper fromApiJsonHelper) {
final SavingsAccountRepositoryWrapper savingsAccountRepository,
final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper) {
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
this.clientRepository = clientRepository;
this.groupRepository = groupRepository;
this.staffRepository = staffRepository;
this.savingProductRepository = savingProductRepository;
this.savingsAccountRepository = savingsAccountRepository;
this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
this.fromApiJsonHelper = fromApiJsonHelper;
}
@@ -231,12 +235,14 @@ public class SavingsAccountAssembler {
} else {
monthDayOfAnnualFee = product.monthDayOfAnnualFee();
}
final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(element);
final SavingsAccount account = SavingsAccount.createNewApplicationForSubmittal(client, group, product, fieldOfficer, accountNo,
externalId, accountType, submittedOnDate, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, iswithdrawalFeeApplicableForTransfer, annualFeeAmount,
monthDayOfAnnualFee);
monthDayOfAnnualFee, charges);
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant());
@@ -0,0 +1,503 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dateFormatParamName;
import static org.mifosplatform.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.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeWithoutMandatoryFieldException;
import org.springframework.data.jpa.domain.AbstractPersistable;
@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 = "due_for_collection_as_of_date")
private Date dueDate;
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@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 = "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;
public static SavingsAccountCharge createNewFromJson(final SavingsAccount savingsAccount, final Charge chargeDefinition, final JsonCommand command) {
final BigDecimal amount = command.bigDecimalValueOfParameterNamed(amountParamName);
final LocalDate dueDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName);
final ChargeTimeType chargeTime = null;
final ChargeCalculationType chargeCalculation = null;
return new SavingsAccountCharge(savingsAccount, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
}
//TODO AA: refactor method signature
public static SavingsAccountCharge createNewWithoutSavingsAccount(final Charge chargeDefinition, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
return new SavingsAccountCharge(null, chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
}
protected SavingsAccountCharge() {
//
}
public SavingsAccountCharge(final SavingsAccount savingsAccount, final Charge chargeDefinition, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate) {
this.savingsAccount = savingsAccount;
this.charge = chargeDefinition;
this.penaltyCharge = chargeDefinition.isPenalty();
this.chargeTime = (chargeTime == null) ? chargeDefinition.getChargeTime() : 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());
}
}
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);//percentage based fee is not yet supported for savings
populateDerivedFields(transactionAmount, chargeAmount);
this.paid = determineIfFullyPaid();
}
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 resetPaidAmount(final MonetaryCurrency currency) {
this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = calculateAmountOutstanding(currency);
this.paid = false;
}
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 pay(final MonetaryCurrency currency, final BigDecimal chargePaid) {
Money amountPaidToDate = Money.of(currency, this.amountPaid);
Money amountPaid = Money.of(currency, chargePaid);
Money amountOutstanding = Money.of(currency, this.amountOutstanding);
amountPaidToDate = amountPaidToDate.plus(amountPaid);
amountOutstanding = amountOutstanding.minus(chargePaid);
this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = amountOutstanding.getAmount();
this.paid = determineIfFullyPaid();
return amountPaid;
}
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 BigDecimal transactionAmount = BigDecimal.ZERO;//percentage based fee calculation is not yet supported for Savings
if (dueDate != null) {
this.dueDate = dueDate.toDate();
}
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<String, Object>(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 (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;
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;
}
public boolean isSpecifiedDueDate() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE);
}
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, RoundingMode.HALF_EVEN);
BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100l), mc);
percentageOf = value.multiply(multiplicand, mc);
}
return percentageOf;
}
public BigDecimal amount() {
return this.amount;
}
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);
}
/**
* @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);
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();
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 boolean isOnSpecifiedDueDate(){
return ChargeTimeType.fromInt(this.chargeTime).isOnSpecifiedDueDate();
}
@Override
public boolean equals(final Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) { return false; }
SavingsAccountCharge rhs = (SavingsAccountCharge) obj;
return new EqualsBuilder().appendSuper(super.equals(obj)) //
.append(this.getId(), rhs.getId()) //
.append(this.charge.getId(), rhs.charge.getId()) //
.append(this.amount, rhs.amount) //
.append(this.getDueLocalDate(), rhs.getDueLocalDate()) //
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(3, 5) //
.append(this.getId()) //
.append(this.charge.getId()) //
.append(this.amount).append(this.getDueLocalDate()) //
.toHashCode();
}
}
@@ -0,0 +1,107 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.ChargeCannotBeAppliedToException;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
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 Set<SavingsAccountCharge> savingsAccountCharges = new HashSet<SavingsAccountCharge>();
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = 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 = fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement);
final Long chargeId = fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement);
final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed(chargeTimeTypeParamName, savingsChargeElement, locale);
final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed(chargeCalculationTypeParamName, savingsChargeElement,
locale);
final LocalDate dueDate = fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
dateFormat, locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if(!chargeDefinition.isSavingsCharge()){
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) {
ChargeTimeType.fromInt(chargeTimeType);
}
ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) {
ChargeCalculationType.fromInt(chargeCalculationType);
}
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
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);
savingsAccountCharges.add(savingsAccountCharge);
}
}
}
}
return savingsAccountCharges;
}
}
@@ -0,0 +1,73 @@
package org.mifosplatform.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(SavingsAccountTransaction savingsAccountTransaction, SavingsAccountCharge savingsAccountCharge, BigDecimal amount) {
this.savingsAccountTransaction = savingsAccountTransaction;
this.savingsAccountCharge = savingsAccountCharge;
this.amount = amount;
}
public SavingsAccountTransaction getSavingsAccountTransaction() {
return this.savingsAccountTransaction;
}
public void setSavingsAccountTransaction(SavingsAccountTransaction savingsAccountTransaction) {
this.savingsAccountTransaction = savingsAccountTransaction;
}
public SavingsAccountCharge getSavingsAccountCharge() {
return this.savingsAccountCharge;
}
public void setSavingsAccountCharge(SavingsAccountCharge savingsAccountCharge) {
this.savingsAccountCharge = savingsAccountCharge;
}
public BigDecimal getAmount() {
return this.amount;
}
public void setAmount(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();
}
}
@@ -0,0 +1,13 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface SavingsAccountChargeRepository extends JpaRepository<SavingsAccountCharge, Long>, JpaSpecificationExecutor<SavingsAccountCharge> {
SavingsAccountCharge findByIdAndSavingsAccountId(Long id, Long savingsAccountId);
}
@@ -0,0 +1,51 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.domain;
import org.mifosplatform.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 void save(final SavingsAccountCharge savingsAccountCharge) {
this.repository.save(savingsAccountCharge);
}
public void saveAndFlush(final SavingsAccountCharge savingsAccountCharge) {
this.repository.saveAndFlush(savingsAccountCharge);
}
public void delete(final SavingsAccountCharge savingsAccountCharge) {
this.repository.delete(savingsAccountCharge);
}
}
@@ -36,6 +36,12 @@ public final class SavingsAccountSummary {
@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;
@@ -55,9 +61,11 @@ public final class SavingsAccountSummary {
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.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals)
.minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).getAmount();
.minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(totalFeeCharge).minus(totalPenaltyCharge).getAmount();
}
public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List<PostingPeriod> allPostingPeriods) {
@@ -7,17 +7,23 @@ package org.mifosplatform.portfolio.savings.domain;
import java.math.BigDecimal;
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.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.domain.LocalDateInterval;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
@@ -30,6 +36,7 @@ import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionEnumDat
import org.mifosplatform.portfolio.savings.domain.interest.EndOfDayBalance;
import org.mifosplatform.portfolio.savings.service.SavingsEnumerations;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.springframework.util.CollectionUtils;
/**
* All monetary transactions against a savings account are modelled through this
@@ -76,7 +83,11 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
@Column(name = "balance_number_of_days_derived", nullable = false)
private Integer balanceNumberOfDays;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "savingsAccountTransaction", orphanRemoval = true)
private Set<SavingsAccountChargePaidBy> savingsAccountChargesPaid = new HashSet<SavingsAccountChargePaidBy>();
protected SavingsAccountTransaction() {
this.dateOf = null;
this.typeOf = null;
@@ -116,6 +127,20 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount,
isReversed);
}
public static SavingsAccountTransaction charge(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
final Money amount) {
final boolean isReversed = false;
return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.APPLY_CHARGES.getValue(), date, amount,
isReversed);
}
public static SavingsAccountTransaction waiver(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
final Money amount) {
final boolean isReversed = false;
return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WAIVE_CHARGES.getValue(), date, amount,
isReversed);
}
public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date) {
final boolean isReversed = false;
@@ -386,6 +411,40 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
}
public boolean isDebit() {
return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed();
return isWithdrawal() || isWithdrawalFeeAndNotReversed() || isAnnualFeeAndNotReversed() || isCharge();
}
public boolean isCharge() {
return SavingsAccountTransactionType.fromInt(this.typeOf).isCharge();
}
public boolean isFeeCharge() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isCharge() && chargePaidBy != null) ? chargePaidBy.isFeeCharge() : false;
}
public boolean isPenaltyCharge() {
final SavingsAccountChargePaidBy chargePaidBy = getSavingsAccountChargePaidBy();
return (isCharge() && chargePaidBy != null) ? chargePaidBy.isPenaltyCharge() : false;
}
public boolean isFeeChargeAndNotReversed(){
return isFeeCharge() && isNotReversed();
}
public boolean isPenaltyChargeAndNotReversed(){
return isPenaltyCharge() && isNotReversed();
}
private SavingsAccountChargePaidBy getSavingsAccountChargePaidBy(){
if(!CollectionUtils.isEmpty(this.savingsAccountChargesPaid)){
return this.savingsAccountChargesPaid.iterator().next();
}
return null;
}
public Set<SavingsAccountChargePaidBy> getSavingsAccountChargesPaid() {
return this.savingsAccountChargesPaid;
}
}
@@ -68,4 +68,25 @@ public final class SavingsAccountTransactionSummaryWrapper {
}
return total.getAmountDefaultedToNullIfZero();
}
public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency);
for (SavingsAccountTransaction transaction : transactions) {
if (transaction.isFeeChargeAndNotReversed() && transaction.isNotReversed()) {
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 (SavingsAccountTransaction transaction : transactions) {
if (transaction.isPenaltyChargeAndNotReversed() && transaction.isNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
return total.getAmountDefaultedToNullIfZero();
}
}
@@ -10,6 +10,7 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_PR
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
@@ -30,13 +31,18 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawal
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.Embedded;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.joda.time.MonthDay;
@@ -47,6 +53,7 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
@@ -55,6 +62,8 @@ import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
import org.springframework.data.jpa.domain.AbstractPersistable;
import com.google.gson.JsonArray;
@Entity
@Table(name = "m_savings_product")
public class SavingsProduct extends AbstractPersistable<Long> {
@@ -132,6 +141,10 @@ public class SavingsProduct extends AbstractPersistable<Long> {
@Column(name = "annual_fee_on_day", nullable = true)
private Integer annualFeeOnDay;
@ManyToMany
@JoinTable(name = "m_savings_product_charge", joinColumns = @JoinColumn(name = "savings_product_id"), inverseJoinColumns = @JoinColumn(name = "charge_id"))
private Set<Charge> charges;
public static SavingsProduct createNew(final String name, final String description, final MonetaryCurrency currency,
final BigDecimal interestRate, final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
@@ -139,12 +152,12 @@ public class SavingsProduct extends AbstractPersistable<Long> {
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final boolean withdrawalFeeApplicableForTransfer,
final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType) {
final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType, final Set<Charge> charges) {
return new SavingsProduct(name, description, currency, interestRate, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType,withdrawalFeeApplicableForTransfer, annualFeeAmount,
annualFeeOnMonthDay, accountingRuleType);
annualFeeOnMonthDay, accountingRuleType, charges);
}
protected SavingsProduct() {
@@ -158,7 +171,7 @@ public class SavingsProduct extends AbstractPersistable<Long> {
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType, final BigDecimal minRequiredOpeningBalance,
final Integer lockinPeriodFrequency, final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final BigDecimal withdrawalFeeAmount, final SavingsWithdrawalFeesType withdrawalFeeType, final boolean withdrawalFeeApplicableForTransfer,
final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType) {
final BigDecimal annualFeeAmount, final MonthDay annualFeeOnMonthDay, final AccountingRuleType accountingRuleType, final Set<Charge> charges) {
this.name = name;
this.description = description;
@@ -194,6 +207,10 @@ public class SavingsProduct extends AbstractPersistable<Long> {
this.accountingRule = accountingRuleType.getValue();
}
if (charges != null) {
this.charges = charges;
}
validateLockinDetails();
validateWithdrawalFeeDetails();
validateAnnualFeeDetails();
@@ -424,6 +441,14 @@ public class SavingsProduct extends AbstractPersistable<Long> {
actualChanges.put(accountingRuleParamName, newValue);
this.accountingRule = newValue;
}
//charges
if (command.hasParameter(chargesParamName)) {
JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
if (jsonArray != null) {
actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName));
}
}
validateLockinDetails();
validateWithdrawalFeeDetails();
@@ -512,4 +537,23 @@ public class SavingsProduct extends AbstractPersistable<Long> {
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<Charge>(this.charges);
final Set<Charge> newSetOfCharges = new HashSet<Charge>(newSavingsProductCharges);
if (!(currentSetOfCharges.equals(newSetOfCharges))) {
updated = true;
this.charges = newSavingsProductCharges;
}
} else {
updated = true;
this.charges = newSavingsProductCharges;
}
return updated;
}
}
@@ -7,6 +7,9 @@ package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeAmountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.annualFeeOnMonthDayParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
@@ -25,22 +28,39 @@ import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawal
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.withdrawalFeeTypeParamName;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import org.joda.time.MonthDay;
import org.mifosplatform.accounting.common.AccountingRuleType;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.exception.ChargeCannotBeAppliedToException;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
import org.mifosplatform.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.mifosplatform.portfolio.savings.SavingsInterestCalculationType;
import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.SavingsWithdrawalFeesType;
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);
@@ -103,10 +123,46 @@ public class SavingsProductAssembler {
final BigDecimal annualFeeAmount = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(annualFeeAmountParamName);
final MonthDay monthDayOfAnnualFee = command.extractMonthDayNamed(annualFeeOnMonthDayParamName);
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
//Savings product charges
final Set<Charge> charges = this.assembleListOfSavingsProductCharges(command, currencyCode);
return SavingsProduct.createNew(name, description, currency, interestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance,
lockinPeriodFrequency, lockinPeriodFrequencyType, withdrawalFeeAmount, withdrawalFeeType, iswithdrawalFeeApplicableForTransfer,
annualFeeAmount, monthDayOfAnnualFee, accountingRuleType);
annualFeeAmount, monthDayOfAnnualFee, accountingRuleType, charges);
}
public Set<Charge> assembleListOfSavingsProductCharges(final JsonCommand command, final String savingsProductCurrencyCode) {
final Set<Charge> charges = new HashSet<Charge>();
if (command.parameterExists(chargesParamName)) {
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()){
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())) {
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;
}
}
@@ -0,0 +1,101 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.domain;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeCalculationTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeTimeTypeParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.dueAsOfDateParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.idParamName;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
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<SavingsAccountCharge>();
if (element.isJsonObject()) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = 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 = fromApiJsonHelper.extractLongNamed(idParamName, savingsChargeElement);
final Long chargeId = fromApiJsonHelper.extractLongNamed(chargeIdParamName, savingsChargeElement);
final BigDecimal amount = fromApiJsonHelper.extractBigDecimalNamed(amountParamName, savingsChargeElement, locale);
final Integer chargeTimeType = fromApiJsonHelper.extractIntegerNamed(chargeTimeTypeParamName, savingsChargeElement, locale);
final Integer chargeCalculationType = fromApiJsonHelper.extractIntegerNamed(chargeCalculationTypeParamName, savingsChargeElement,
locale);
final LocalDate dueDate = fromApiJsonHelper.extractLocalDateNamed(dueAsOfDateParamName, savingsChargeElement,
dateFormat, locale);
if (id == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
ChargeTimeType.fromInt(chargeTimeType);
}
ChargeCalculationType chargeCalculation = null;
if (chargeCalculationType != null) {
ChargeCalculationType.fromInt(chargeCalculationType);
}
final SavingsAccountCharge savingsAccountCharge = SavingsAccountCharge.createNewWithoutSavingsAccount(chargeDefinition, amount, chargeTime, chargeCalculation, dueDate);
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);
savingsAccountCharges.add(savingsAccountCharge);
}
}
}
}
return savingsAccountCharges;
}
}
@@ -0,0 +1,32 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
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);
}
}
@@ -0,0 +1,32 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
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);
}
}
@@ -0,0 +1,32 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
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);
}
}
@@ -0,0 +1,32 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
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);
}
}
@@ -0,0 +1,32 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.handler;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.savings.service.SavingsAccountWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
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(), command);
}
}
@@ -0,0 +1,20 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.service;
import java.util.Collection;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
public interface SavingsAccountChargeReadPlatformService {
ChargeData retrieveSavingsAccountChargeTemplate();
Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(Long savingsAccountId);
SavingsAccountChargeData retrieveSavingsAccountChargeDetails(Long savingsAccountChargeId, Long savingsAccountId);
}
@@ -0,0 +1,144 @@
/**
* 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/.
*/
package org.mifosplatform.portfolio.savings.service;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.exception.SavingsAccountChargeNotFoundException;
import org.mifosplatform.portfolio.charge.service.ChargeDropdownReadPlatformService;
import org.mifosplatform.portfolio.charge.service.ChargeEnumerations;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
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;
@Autowired
public SavingsAccountChargeReadPlatformServiceImpl(final PlatformSecurityContext context,
final ChargeDropdownReadPlatformService chargeDropdownReadPlatformService, final RoutingDataSource dataSource) {
this.context = context;
this.chargeDropdownReadPlatformService = chargeDropdownReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class SavingsAccountChargeMapper implements RowMapper<SavingsAccountChargeData> {
public String schema() {
return "sc.id as id, c.id as chargeId, 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.due_for_collection_as_of_date as dueAsOfDate, "
+ "sc.charge_calculation_enum as chargeCalculation, "
+ "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 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 int chargeCalculation = rs.getInt("chargeCalculation");
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
final boolean penalty = rs.getBoolean("penalty");
final Collection<ChargeData> chargeOptions = null;
return SavingsAccountChargeData.instance(id, chargeId, name, currency, amount, amountPaid, amountWaived, amountWrittenOff, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, chargeOptions, penalty);
}
}
@Override
public ChargeData retrieveSavingsAccountChargeTemplate() {
this.context.authenticatedUser();
final List<EnumOptionData> allowedChargeCalculationTypeOptions = this.chargeDropdownReadPlatformService.retrieveCalculationTypes();
final List<EnumOptionData> allowedChargeTimeOptions = this.chargeDropdownReadPlatformService.retrieveCollectionTimeTypes();
//TODO AA : revisit for merge conflict - Not sure method signature
return ChargeData.template(null, allowedChargeCalculationTypeOptions, null, allowedChargeTimeOptions, null);
}
@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 (EmptyResultDataAccessException e) {
throw new SavingsAccountChargeNotFoundException(savingsAccountId);
}
}
@Override
public Collection<SavingsAccountChargeData> retrieveSavingsAccountCharges(final Long loanId) {
this.context.authenticatedUser();
final SavingsAccountChargeMapper rm = new SavingsAccountChargeMapper();
final String sql = "select " + rm.schema() + " where sc.savings_account_id=? "
+ " order by sc.charge_time_enum ASC, sc.due_for_collection_as_of_date ASC, sc.is_penalty ASC";
return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
}
}
@@ -27,6 +27,8 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.organisation.staff.service.StaffReadPlatformService;
import org.mifosplatform.portfolio.account.data.AccountTransferData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.client.data.ClientData;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
@@ -40,6 +42,7 @@ import org.mifosplatform.portfolio.savings.SavingsPeriodFrequencyType;
import org.mifosplatform.portfolio.savings.SavingsPostingInterestPeriodType;
import org.mifosplatform.portfolio.savings.data.SavingsAccountAnnualFeeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountApplicationTimelineData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountStatusEnumData;
import org.mifosplatform.portfolio.savings.data.SavingsAccountSummaryData;
@@ -65,6 +68,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
private final SavingsProductReadPlatformService savingsProductReadPlatformService;
private final StaffReadPlatformService staffReadPlatformService;
private final SavingsDropdownReadPlatformService dropdownReadPlatformService;
private final ChargeReadPlatformService chargeReadPlatformService;
// mappers
private final SavingsAccountTransactionTemplateMapper transactionTemplateMapper;
@@ -79,7 +83,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
public SavingsAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
final SavingsProductReadPlatformService savingProductReadPlatformService,
final StaffReadPlatformService staffReadPlatformService, final SavingsDropdownReadPlatformService dropdownReadPlatformService) {
final StaffReadPlatformService staffReadPlatformService, final SavingsDropdownReadPlatformService dropdownReadPlatformService,
final ChargeReadPlatformService chargeReadPlatformService) {
this.context = context;
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.clientReadPlatformService = clientReadPlatformService;
@@ -91,6 +96,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
this.transactionsMapper = new SavingsAccountTransactionsMapper();
this.savingAccountMapper = new SavingAccountMapper();
this.annualFeeMapper = new SavingsAccountAnnualFeeMapper();
this.chargeReadPlatformService = chargeReadPlatformService;
}
@Override
@@ -229,7 +235,9 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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.account_balance_derived as accountBalance ");
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("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 ");
@@ -372,9 +380,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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 SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance);
totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
totalPenaltyCharge);
return SavingsAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
@@ -430,6 +441,12 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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.retrieveSavingsAccountApplicableCharges(feeChargesOnly);
Collection<StaffData> fieldOfficerOptions = null;
@@ -459,7 +476,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions);
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions);
} else {
String clientName = null;
@@ -483,15 +500,28 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
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.retrieveSavingsAccountApplicableCharges(feeChargesOnly);
template = SavingsAccountData.withTemplateOptions(template, productOptions, fieldOfficerOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions);
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, transactions, charges, chargeOptions);
}
return template;
}
private Collection<SavingsAccountChargeData> fromChargesToSavingsCharges(Collection<ChargeData> productCharges) {
final Collection<SavingsAccountChargeData> savingsCharges = new ArrayList<SavingsAccountChargeData>();
for (ChargeData chargeData : productCharges) {
final SavingsAccountChargeData savingsCharge = chargeData.toSavingsAccountChargeData();
savingsCharges.add(savingsCharge);
}
return savingsCharges;
}
@Override
public SavingsAccountTransactionData retrieveDepositTransactionTemplate(final Long savingsId) {
@@ -41,4 +41,14 @@ public interface SavingsAccountWritePlatformService {
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, JsonCommand command);
CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command);
}
@@ -6,6 +6,8 @@
package org.mifosplatform.portfolio.savings.service;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.amountParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargeIdParamName;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -40,6 +42,8 @@ import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.portfolio.account.PortfolioAccountType;
import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.mifosplatform.portfolio.account.service.AccountTransfersReadPlatformService;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.group.domain.Group;
@@ -49,10 +53,13 @@ import org.mifosplatform.portfolio.note.domain.NoteRepository;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
import org.mifosplatform.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.data.SavingsAccountChargeDataValidator;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDTO;
import org.mifosplatform.portfolio.savings.data.SavingsAccountTransactionDataValidator;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountCharge;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountChargeRepositoryWrapper;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountDomainService;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepository;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountStatusType;
@@ -74,6 +81,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
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;
@@ -81,6 +89,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
private final NoteRepository noteRepository;
private final AccountTransfersReadPlatformService accountTransfersReadPlatformService;
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
private final ChargeRepositoryWrapper chargeRepository;
private final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository;
@Autowired
public SavingsAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -88,17 +98,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
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 AccountAssociationsReadPlatformService accountAssociationsReadPlatformService) {
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
final ChargeRepositoryWrapper chargeRepository, final SavingsAccountChargeRepositoryWrapper savingsAccountChargeRepository) {
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;
@@ -106,6 +119,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.noteRepository = noteRepository;
this.accountTransfersReadPlatformService = accountTransfersReadPlatformService;
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
this.chargeRepository = chargeRepository;
this.savingsAccountChargeRepository = savingsAccountChargeRepository;
}
@Transactional
@@ -553,4 +568,148 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
return acceptTransferTransaction;
}
@Transactional
@Override
public CommandProcessingResult addSavingsAccountCharge(final JsonCommand command) {
this.context.authenticatedUser();
final Long savingsAccountId = command.getSavingsId();
this.savingsAccountChargeDataValidator.validateAdd(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
final DateTimeFormatter fmt = 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);
savingsAccount.addCharge(fmt, savingsAccountCharge, chargeDefinition);
this.savingAccountRepository.saveAndFlush(savingsAccount);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsAccountCharge.getId()) //
.withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) //
.withLoanId(savingsAccountId) //
.build();
}
@Transactional
@Override
public CommandProcessingResult updateSavingsAccountCharge(JsonCommand command) {
this.context.authenticatedUser();
this.savingsAccountChargeDataValidator.validateUpdate(command.json());
final Long savingsAccountId = command.getSavingsId();
final Long savingsChargeId = command.entityId();//SavingsAccount Charge entity
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);
this.savingsAccountChargeRepository.saveAndFlush(savingsAccountCharge);
//TODO AA: revisit the code
/*
// 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()); }
*/
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsChargeId) //
//.withOfficeId(savings) //
//.withClientId(loan.getClientId()) //
//.withGroupId(loan.getGroupId()) //
//.withLoanId(savingsAccountId) //
.with(changes) //
.build();
}
@Transactional
@Override
public CommandProcessingResult waiveCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command) {
this.context.authenticatedUser();
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
savingsAccount.waiveCharge(savingsAccountChargeId, existingTransactionIds, existingReversedTransactionIds);
this.savingAccountRepository.saveAndFlush(savingsAccount);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsAccountChargeId) //
.withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) //
.withSavingsId(savingsAccountId) //
.build();
}
@Transactional
@Override
public CommandProcessingResult deleteSavingsAccountCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command) {
this.context.authenticatedUser();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
//TODO AA: validate before deleting a charge
savingsAccount.removeCharge(savingsAccountCharge);
this.savingAccountRepository.saveAndFlush(savingsAccount);
return new CommandProcessingResultBuilder() //
.withEntityId(savingsAccountChargeId) //
.withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) //
.withSavingsId(savingsAccountId) //
.build();
}
@Transactional
@Override
public CommandProcessingResult payCharge(Long savingsAccountId, Long savingsAccountChargeId, JsonCommand command) {
this.context.authenticatedUser();
final List<Long> existingTransactionIds = new ArrayList<Long>();
final List<Long> existingReversedTransactionIds = new ArrayList<Long>();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
checkClientOrGroupActive(savingsAccount);
final BigDecimal chargeAmount = command.bigDecimalValueOfParameterNamed(amountParamName);
savingsAccount.payCharge(savingsAccountChargeId, chargeAmount, existingTransactionIds, existingReversedTransactionIds);
this.savingAccountRepository.saveAndFlush(savingsAccount);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(savingsAccountChargeId) //
.withOfficeId(savingsAccount.officeId()) //
.withClientId(savingsAccount.clientId()) //
.withGroupId(savingsAccount.groupId()) //
.withSavingsId(savingsAccountId) //
.build();
}
}
@@ -11,6 +11,7 @@ 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.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService;
@@ -28,6 +29,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyReposit
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.staff.domain.StaffRepositoryWrapper;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory;
import org.mifosplatform.portfolio.client.domain.Client;
@@ -44,6 +46,8 @@ import org.mifosplatform.portfolio.savings.SavingsApiConstants;
import org.mifosplatform.portfolio.savings.data.SavingsAccountDataValidator;
import org.mifosplatform.portfolio.savings.domain.SavingsAccount;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountCharge;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountChargeAssembler;
import org.mifosplatform.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
import org.mifosplatform.portfolio.savings.domain.SavingsProduct;
import org.mifosplatform.portfolio.savings.domain.SavingsProductRepository;
@@ -74,6 +78,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
private final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper;
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
@Autowired
public SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -84,7 +89,8 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final NoteRepository noteRepository, final StaffRepositoryWrapper staffRepository,
final SavingsAccountApplicationTransitionApiJsonValidator savingsAccountApplicationTransitionApiJsonValidator,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
final JournalEntryWritePlatformService journalEntryWritePlatformService) {
final JournalEntryWritePlatformService journalEntryWritePlatformService,
final SavingsAccountChargeAssembler savingsAccountChargeAssembler) {
this.context = context;
this.savingAccountRepository = savingAccountRepository;
this.savingAccountAssembler = savingAccountAssembler;
@@ -98,6 +104,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator = savingsAccountApplicationTransitionApiJsonValidator;
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
}
/*
@@ -225,6 +232,15 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
}
account.update(fieldOfficer);
}
if (changes.containsKey("charges")) {
//final Set<Charge> savingsProductCharges = this.savi.assembleListOfSavingsProductCharges(command, product.currency().getCode());
final Set<SavingsAccountCharge> charges = this.savingsAccountChargeAssembler.fromParsedJson(command.parsedJson());
boolean updated = account.update(charges);
if (!updated) {
changes.remove("charges");
}
}
this.savingAccountRepository.saveAndFlush(account);
}
@@ -101,6 +101,13 @@ public class SavingsEnumerations {
SavingsAccountTransactionType.WITHDRAW_TRANSFER.getCode(), "Transfer Withdrawn");
break;
default:
case APPLY_CHARGES:
optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.APPLY_CHARGES.getValue().longValue(),
SavingsAccountTransactionType.APPLY_CHARGES.getCode(), "Apply Charge");
break;
case WAIVE_CHARGES:
optionData = new SavingsAccountTransactionEnumData(SavingsAccountTransactionType.WAIVE_CHARGES.getValue().longValue(),
SavingsAccountTransactionType.WAIVE_CHARGES.getCode(), "Waive Charge");
break;
}
return optionData;
@@ -17,6 +17,7 @@ import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.RoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.savings.data.SavingsProductData;
import org.mifosplatform.portfolio.savings.exception.SavingsProductNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
@@ -6,8 +6,10 @@
package org.mifosplatform.portfolio.savings.service;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.mifosplatform.portfolio.savings.SavingsApiConstants.chargesParamName;
import java.util.Map;
import java.util.Set;
import org.mifosplatform.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
@@ -15,6 +17,7 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.savings.data.SavingsProductDataValidator;
import org.mifosplatform.portfolio.savings.domain.SavingsProduct;
import org.mifosplatform.portfolio.savings.domain.SavingsProductAssembler;
@@ -109,6 +112,14 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
final Map<String, Object> changes = product.update(command);
if (changes.containsKey(chargesParamName)) {
final Set<Charge> savingsProductCharges = this.savingsProductAssembler.assembleListOfSavingsProductCharges(command, product.currency().getCode());
boolean updated = product.update(savingsProductCharges);
if (!updated) {
changes.remove(chargesParamName);
}
}
// accounting related changes
boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
final Map<String, Object> accountingMappingChanges = accountMappingWritePlatformService.updateSavingsProductToGLAccountMapping(
@@ -142,4 +153,5 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
.withEntityId(product.getId()) //
.build();
}
}