From 0389bde94e2bc7746fc1e26b8dfdd05dfd139783 Mon Sep 17 00:00:00 2001 From: Madhukar Date: Wed, 2 Jul 2014 09:52:18 +0530 Subject: [PATCH] MIFOSX-1394 overdraft account interest calculation fix --- .../ClientSavingsIntegrationTest.java | 139 ++++++++++++++++++ .../common/savings/SavingsAccountHelper.java | 19 +++ .../common/savings/SavingsProductHelper.java | 10 ++ .../domain/SavingsAccountAssembler.java | 9 +- .../domain/SavingsAccountTransaction.java | 2 + 5 files changed, 178 insertions(+), 1 deletion(-) diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java index 11326896d..30e457893 100755 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientSavingsIntegrationTest.java @@ -3,6 +3,8 @@ package org.mifosplatform.integrationtests; import static org.junit.Assert.assertEquals; import java.math.BigDecimal; +import java.text.DateFormat; +import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; @@ -479,15 +481,152 @@ public class ClientSavingsIntegrationTest { 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, final String minOpenningBalance, String minBalanceForInterestCalculation) { System.out.println("------------------------------CREATING NEW SAVINGS PRODUCT ---------------------------------------"); SavingsProductHelper savingsProductHelper = new SavingsProductHelper(); + final String overDraftLimit = "2000.0"; final String savingsProductJSON = savingsProductHelper // .withInterestCompoundingPeriodTypeAsDaily() // .withInterestPostingPeriodTypeAsMonthly() // .withInterestCalculationPeriodTypeAsDailyBalance() // .withMinBalanceForInterestCalculation(minBalanceForInterestCalculation)// + .withOverDraft(overDraftLimit) .withMinimumOpenningBalance(minOpenningBalance).build(); return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java index 6e98d58d1..6bed1ccf1 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsAccountHelper.java @@ -329,6 +329,12 @@ public class SavingsAccountHelper { final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, "summary"); return response; } + + public HashMap getSavingsDetails(final Integer savingsID) { + final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?associations=all&" + Utils.TENANT_IDENTIFIER; + final HashMap response = Utils.performServerGet(requestSpec, responseSpec, URL, ""); + return response; + } private HashMap performSavingApplicationActions(final String postURLForSavingsTransaction, final String jsonToBeSent) { HashMap status = null; @@ -358,4 +364,17 @@ public class SavingsAccountHelper { String json = new Gson().toJson(map); return json; } + + private String getAccountActivationJSON(final String activationDate) { + final HashMap 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)); + } } \ No newline at end of file diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsProductHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsProductHelper.java index 6a85d49f9..787f3d6c6 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsProductHelper.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/savings/SavingsProductHelper.java @@ -64,6 +64,8 @@ public class SavingsProductHelper { private final String interestCalculationDaysInYearType = DAYS_365; private Account[] accountList = null; private String minBalanceForInterestCalculation = null; + private String allowOverdraft = "false"; + private String overdraftLimit = null; public String build() { final HashMap map = new HashMap<>(); @@ -95,6 +97,8 @@ public class SavingsProductHelper { map.put("lockinPeriodFrequencyType", this.lockingPeriodFrequencyType); map.put("withdrawalFeeForTransfers", this.withdrawalFeeForTransfers); map.put("minBalanceForInterestCalculation", minBalanceForInterestCalculation); + map.put("allowOverdraft", this.allowOverdraft); + map.put("overdraftLimit", this.overdraftLimit); if (this.accountingRule.equals(CASH_BASED)) { map.putAll(getAccountMappingForCashBased()); @@ -165,6 +169,12 @@ public class SavingsProductHelper { return this; } + public SavingsProductHelper withOverDraft(final String overDraftLimit) { + this.allowOverdraft = "true"; + this.overdraftLimit = overDraftLimit; + return this; + } + private Map getAccountMappingForCashBased() { final Map map = new HashMap<>(); if (accountList != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java index 988257f03..2d858d051 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountAssembler.java @@ -212,9 +212,16 @@ public class SavingsAccountAssembler { boolean allowOverdraft = false; if (command.parameterExists(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; if (command.parameterExists(enforceMinRequiredBalanceParamName)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java index 116e90070..f03f326c2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/savings/domain/SavingsAccountTransaction.java @@ -421,6 +421,8 @@ public final class SavingsAccountTransaction extends AbstractPersistable { if (openingBalance.isGreaterThanZero()) { endOfDayBalance = openingBalance.minus(getAmount(currency)); + } else { + endOfDayBalance = Money.of(currency, this.runningBalance); } }