MIFOSX-1394 overdraft account interest calculation fix
This commit is contained in:
+139
@@ -3,6 +3,8 @@ package org.mifosplatform.integrationtests;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@@ -479,15 +481,152 @@ public class ClientSavingsIntegrationTest {
|
|||||||
assertEquals(expectedNextDueDate, nextDueDate);
|
assertEquals(expectedNextDueDate, nextDueDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Test case for overdraft account functionality. Open account with zero
|
||||||
|
* balance, perform transactions then post interest and verify posted
|
||||||
|
* interest
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSavingsAccountWithOverdraft() {
|
||||||
|
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Create a client to apply for savings account (overdraft account).
|
||||||
|
*/
|
||||||
|
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||||
|
Assert.assertNotNull(clientID);
|
||||||
|
final String minBalanceForInterestCalculation = null;
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Create savings product with zero opening balance and overdraft
|
||||||
|
* enabled
|
||||||
|
*/
|
||||||
|
final String zeroOpeningBalance = "0.0";
|
||||||
|
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, zeroOpeningBalance,
|
||||||
|
minBalanceForInterestCalculation);
|
||||||
|
Assert.assertNotNull(savingsProductID);
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Apply for Savings account
|
||||||
|
*/
|
||||||
|
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
|
||||||
|
Assert.assertNotNull(savingsProductID);
|
||||||
|
|
||||||
|
HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
|
||||||
|
ACCOUNT_TYPE_INDIVIDUAL);
|
||||||
|
Assert.assertTrue(modifications.containsKey("submittedOnDate"));
|
||||||
|
|
||||||
|
HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
|
||||||
|
SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Approve the savings account
|
||||||
|
*/
|
||||||
|
savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
|
||||||
|
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
|
||||||
|
|
||||||
|
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
|
||||||
|
Calendar todaysDate = Calendar.getInstance();
|
||||||
|
todaysDate.add(Calendar.MONTH, -1);
|
||||||
|
todaysDate.set(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime());
|
||||||
|
final Integer lastDayOfMonth = todaysDate.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||||
|
todaysDate.set(Calendar.DAY_OF_MONTH, lastDayOfMonth);
|
||||||
|
final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime());
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Activate the application and verify account status
|
||||||
|
*
|
||||||
|
* @param activationDate
|
||||||
|
* this value is every time first day of previous month
|
||||||
|
*/
|
||||||
|
savingsStatusHashMap = activateSavingsAccount(savingsId, ACTIVATION_DATE);
|
||||||
|
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Verify the account summary
|
||||||
|
*/
|
||||||
|
final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
this.savingsAccountHelper.calculateInterestForSavings(savingsId);
|
||||||
|
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||||
|
assertEquals(summaryBefore, summary);
|
||||||
|
|
||||||
|
Float balance = Float.valueOf(zeroOpeningBalance);
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Perform withdraw transaction, verify account balance(account balance
|
||||||
|
* will go to negative as no deposits are there prior to this
|
||||||
|
* transaction)
|
||||||
|
*/
|
||||||
|
Integer withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, WITHDRAW_AMOUNT,
|
||||||
|
ACTIVATION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
HashMap withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId);
|
||||||
|
balance -= new Float(WITHDRAW_AMOUNT);
|
||||||
|
assertEquals("Verifying Withdrawal Amount", new Float(WITHDRAW_AMOUNT), withdrawTransaction.get("amount"));
|
||||||
|
assertEquals("Verifying Balance after Withdrawal", balance, withdrawTransaction.get("runningBalance"));
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Perform Deposit transaction on last day of month and verify account
|
||||||
|
* balance.
|
||||||
|
*
|
||||||
|
* @param transactionDate
|
||||||
|
* this value is every time last day of previous month
|
||||||
|
*/
|
||||||
|
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT,
|
||||||
|
TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||||
|
HashMap depositTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, depositTransactionId);
|
||||||
|
balance += new Float(DEPOSIT_AMOUNT);
|
||||||
|
assertEquals("Verifying Deposit Amount", new Float(DEPOSIT_AMOUNT), depositTransaction.get("amount"));
|
||||||
|
assertEquals("Verifying Balance after Deposit", balance, depositTransaction.get("runningBalance"));
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Perform Post interest transaction and verify the posted amount
|
||||||
|
*/
|
||||||
|
this.savingsAccountHelper.postInterestForSavings(savingsId);
|
||||||
|
HashMap accountDetails = this.savingsAccountHelper.getSavingsDetails(savingsId);
|
||||||
|
summary = (HashMap) accountDetails.get("summary");
|
||||||
|
Float actualInterestPosted = Float.valueOf(summary.get("totalInterestPosted").toString());
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Calculate expected interest to be posted, interest should be posted
|
||||||
|
* for one day only because deposit transaction happened on last day of
|
||||||
|
* month before this account balance is negative.
|
||||||
|
*/
|
||||||
|
final Float nominalAnnualInterest = Float.valueOf(accountDetails.get("nominalAnnualInterestRate").toString());
|
||||||
|
final HashMap interestCalculationDaysInYearType = (HashMap) accountDetails.get("interestCalculationDaysInYearType");
|
||||||
|
final Integer daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get("id").toString());
|
||||||
|
double interestRateInFraction = (nominalAnnualInterest / 100);
|
||||||
|
double perDay = (double) 1 / (daysInYear);
|
||||||
|
double interestPerDay = interestRateInFraction * perDay;
|
||||||
|
Float interestPosted = (float) (interestPerDay * balance * 1);
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Apply rounding on interestPosted, actualInterestPosted and verify
|
||||||
|
* both are same
|
||||||
|
*/
|
||||||
|
DecimalFormat decimalFormat = new DecimalFormat();
|
||||||
|
decimalFormat.applyPattern("#.###");
|
||||||
|
interestPosted = new Float(decimalFormat.format(interestPosted));
|
||||||
|
actualInterestPosted = new Float(decimalFormat.format(actualInterestPosted));
|
||||||
|
assertEquals("Verifying interest posted", interestPosted, actualInterestPosted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashMap activateSavingsAccount(final Integer savingsId, final String activationDate) {
|
||||||
|
final HashMap status = this.savingsAccountHelper.activateSavingsAccount(savingsId, activationDate);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
private Integer createSavingsProduct(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||||
final String minOpenningBalance, String minBalanceForInterestCalculation) {
|
final String minOpenningBalance, String minBalanceForInterestCalculation) {
|
||||||
System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
|
System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------");
|
||||||
SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
|
SavingsProductHelper savingsProductHelper = new SavingsProductHelper();
|
||||||
|
final String overDraftLimit = "2000.0";
|
||||||
final String savingsProductJSON = savingsProductHelper //
|
final String savingsProductJSON = savingsProductHelper //
|
||||||
.withInterestCompoundingPeriodTypeAsDaily() //
|
.withInterestCompoundingPeriodTypeAsDaily() //
|
||||||
.withInterestPostingPeriodTypeAsMonthly() //
|
.withInterestPostingPeriodTypeAsMonthly() //
|
||||||
.withInterestCalculationPeriodTypeAsDailyBalance() //
|
.withInterestCalculationPeriodTypeAsDailyBalance() //
|
||||||
.withMinBalanceForInterestCalculation(minBalanceForInterestCalculation)//
|
.withMinBalanceForInterestCalculation(minBalanceForInterestCalculation)//
|
||||||
|
.withOverDraft(overDraftLimit)
|
||||||
.withMinimumOpenningBalance(minOpenningBalance).build();
|
.withMinimumOpenningBalance(minOpenningBalance).build();
|
||||||
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
|
return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
@@ -330,6 +330,12 @@ public class SavingsAccountHelper {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashMap getSavingsDetails(final Integer savingsID) {
|
||||||
|
final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=all&" + Utils.TENANT_IDENTIFIER;
|
||||||
|
final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private HashMap performSavingApplicationActions(final String postURLForSavingsTransaction, final String jsonToBeSent) {
|
private HashMap performSavingApplicationActions(final String postURLForSavingsTransaction, final String jsonToBeSent) {
|
||||||
HashMap status = null;
|
HashMap status = null;
|
||||||
final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForSavingsTransaction, jsonToBeSent,
|
final HashMap response = Utils.performServerPost(this.requestSpec, this.responseSpec, postURLForSavingsTransaction, jsonToBeSent,
|
||||||
@@ -358,4 +364,17 @@ public class SavingsAccountHelper {
|
|||||||
String json = new Gson().toJson(map);
|
String json = new Gson().toJson(map);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getAccountActivationJSON(final String activationDate) {
|
||||||
|
final HashMap<String, Object> map = new HashMap<>();
|
||||||
|
map.put("locale", CommonConstants.locale);
|
||||||
|
map.put("dateFormat", CommonConstants.dateFormat);
|
||||||
|
map.put("activatedOnDate", activationDate);
|
||||||
|
String savingsAccountActivateJson = new Gson().toJson(map);
|
||||||
|
return savingsAccountActivateJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap activateSavingsAccount(Integer savingsID, String activationDate) {
|
||||||
|
return performSavingApplicationActions(createSavingsOperationURL(ACTIVATE_SAVINGS_COMMAND, savingsID), getAccountActivationJSON(activationDate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+10
@@ -64,6 +64,8 @@ public class SavingsProductHelper {
|
|||||||
private final String interestCalculationDaysInYearType = DAYS_365;
|
private final String interestCalculationDaysInYearType = DAYS_365;
|
||||||
private Account[] accountList = null;
|
private Account[] accountList = null;
|
||||||
private String minBalanceForInterestCalculation = null;
|
private String minBalanceForInterestCalculation = null;
|
||||||
|
private String allowOverdraft = "false";
|
||||||
|
private String overdraftLimit = null;
|
||||||
|
|
||||||
public String build() {
|
public String build() {
|
||||||
final HashMap<String, String> map = new HashMap<>();
|
final HashMap<String, String> map = new HashMap<>();
|
||||||
@@ -95,6 +97,8 @@ public class SavingsProductHelper {
|
|||||||
map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType);
|
map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType);
|
||||||
map.put("withdrawalFeeForTransfers", this.withdrawalFeeForTransfers);
|
map.put("withdrawalFeeForTransfers", this.withdrawalFeeForTransfers);
|
||||||
map.put("minBalanceForInterestCalculation", minBalanceForInterestCalculation);
|
map.put("minBalanceForInterestCalculation", minBalanceForInterestCalculation);
|
||||||
|
map.put("allowOverdraft", this.allowOverdraft);
|
||||||
|
map.put("overdraftLimit", this.overdraftLimit);
|
||||||
|
|
||||||
if (this.accountingRule.equals(CASH_BASED)) {
|
if (this.accountingRule.equals(CASH_BASED)) {
|
||||||
map.putAll(getAccountMappingForCashBased());
|
map.putAll(getAccountMappingForCashBased());
|
||||||
@@ -165,6 +169,12 @@ public class SavingsProductHelper {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SavingsProductHelper withOverDraft(final String overDraftLimit) {
|
||||||
|
this.allowOverdraft = "true";
|
||||||
|
this.overdraftLimit = overDraftLimit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, String> getAccountMappingForCashBased() {
|
private Map<String, String> getAccountMappingForCashBased() {
|
||||||
final Map<String, String> map = new HashMap<>();
|
final Map<String, String> map = new HashMap<>();
|
||||||
if (accountList != null) {
|
if (accountList != null) {
|
||||||
|
|||||||
+8
-1
@@ -212,9 +212,16 @@ public class SavingsAccountAssembler {
|
|||||||
boolean allowOverdraft = false;
|
boolean allowOverdraft = false;
|
||||||
if (command.parameterExists(allowOverdraftParamName)) {
|
if (command.parameterExists(allowOverdraftParamName)) {
|
||||||
allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
|
allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
|
||||||
|
} else {
|
||||||
|
allowOverdraft = product.isAllowOverdraft();
|
||||||
}
|
}
|
||||||
|
|
||||||
final BigDecimal overdraftLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName);
|
BigDecimal overdraftLimit = null;
|
||||||
|
if (command.parameterExists(overdraftLimitParamName)) {
|
||||||
|
overdraftLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName);
|
||||||
|
} else {
|
||||||
|
overdraftLimit = product.overdraftLimit();
|
||||||
|
}
|
||||||
|
|
||||||
boolean enforceMinRequiredBalance = false;
|
boolean enforceMinRequiredBalance = false;
|
||||||
if (command.parameterExists(enforceMinRequiredBalanceParamName)) {
|
if (command.parameterExists(enforceMinRequiredBalanceParamName)) {
|
||||||
|
|||||||
+2
@@ -421,6 +421,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable<Long> {
|
|||||||
|
|
||||||
if (openingBalance.isGreaterThanZero()) {
|
if (openingBalance.isGreaterThanZero()) {
|
||||||
endOfDayBalance = openingBalance.minus(getAmount(currency));
|
endOfDayBalance = openingBalance.minus(getAmount(currency));
|
||||||
|
} else {
|
||||||
|
endOfDayBalance = Money.of(currency, this.runningBalance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user