diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index 8ac2227c6..7a49c762b 100755 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -117,12 +117,11 @@ public class ClientSavingsIntegrationTest { this.savingsAccountHelper.postInterestForSavings(savingsId); summary = this.savingsAccountHelper.getSavingsSummary(savingsId); Assert.assertFalse(summaryBefore.equals(summary)); - + final Object savingsInterest = this.savingsAccountHelper.getSavingsInterest(savingsId); // verifySavingsInterest(savingsInterest); - } - + @Test public void testSavingsAccountWithMinBalanceForInterestCalculation() { this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); @@ -796,6 +795,1024 @@ public class ClientSavingsIntegrationTest { savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); } + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountPostInterestOnLastDayWithOverdraft() { + 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 String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = true; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, + this.responseSpec, zeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, + enforceMinRequiredBalance, allowOverdraft); + Assert.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper + .applyForSavingsApplication(clientID, savingsProductID, + ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount( + clientID, savingsProductID, savingsId, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertTrue(modifications.containsKey("submittedOnDate")); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings( + this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + /*** + * Approve the savings account + */ + savingsStatusHashMap = this.savingsAccountHelper + .approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -1); + + final String ACTIVATION_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); + + final Integer lastDayOfMonth = todaysDate + .getActualMaximum(Calendar.DAY_OF_MONTH); + todaysDate.set(Calendar.DAY_OF_MONTH, lastDayOfMonth); + final String WITHDRAWAL_DATE = dateFormat.format(todaysDate.getTime()); + Float balance = Float.valueOf(zeroOpeningBalance); + + DateFormat transactionDateFormat = new SimpleDateFormat("dd MMMM yyyy", + Locale.US); + Calendar transactionDate = Calendar.getInstance(); + transactionDate.set(Calendar.DAY_OF_MONTH, 2); + String TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + + /*** + * 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, + WITHDRAWAL_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")); + + /*** + * 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. + */ + this.savingsAccountHelper.postInterestForSavings(savingsId); + HashMap accountDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + Float accountDetailsPostInterestPosted = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + Float nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + HashMap interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + Integer daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + double interestRateInFraction = (nominalAnnualInterest / 100); + double perDay = (double) 1 / (daysInYear); + double interestPerDay = interestRateInFraction * perDay; + Float interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + DecimalFormat decimalFormat = new DecimalFormat("", + new DecimalFormatSymbols(Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + HashMap accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + Float accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + + transactionDate.set(Calendar.DAY_OF_MONTH, 3); + TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked-------"); + + DateFormat transactionFormat = new SimpleDateFormat("dd MMMM yyyy", + Locale.US); + Calendar transactionCalendarDateFormat = Calendar.getInstance(); + transactionCalendarDateFormat.add(Calendar.DAY_OF_MONTH, 0); + transactionDate.set(Calendar.DAY_OF_MONTH, 22); + TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + if (Calendar.DAY_OF_MONTH >= 22) { + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountTransactionDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get( + "id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 19); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + } + DateFormat lastTransactionDateFormat = new SimpleDateFormat( + "dd MMMM yyyy", Locale.US); + Calendar postedLastDate = Calendar.getInstance(); + int numberOfDateOfMonth = postedLastDate + .getActualMaximum(Calendar.DAY_OF_MONTH); + TRANSACTION_DATE = lastTransactionDateFormat.format(transactionDate + .getTime()); + + if (Calendar.DAY_OF_MONTH == numberOfDateOfMonth) { + + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountTransactionDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get( + "id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 8); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + + } + transactionDate.set(Calendar.DAY_OF_MONTH, 1); + TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountTransactionDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + + } + + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountPostInterestOnLastDayWithdrawalWithOverdraft() { + 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 String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = true; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, + this.responseSpec, zeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, + enforceMinRequiredBalance, allowOverdraft); + Assert.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper + .applyForSavingsApplication(clientID, savingsProductID, + ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount( + clientID, savingsProductID, savingsId, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertTrue(modifications.containsKey("submittedOnDate")); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings( + this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + /*** + * Approve the savings account + */ + savingsStatusHashMap = this.savingsAccountHelper + .approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -1); + + final String ACTIVATION_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); + + final Integer lastDayOfMonth = todaysDate + .getActualMaximum(Calendar.DAY_OF_MONTH); + todaysDate.set(Calendar.DAY_OF_MONTH, lastDayOfMonth); + final String WITHDRAWAL_DATE = dateFormat.format(todaysDate.getTime()); + Float balance = Float.valueOf(zeroOpeningBalance); + + DateFormat transactionDateFormat = new SimpleDateFormat("dd MMMM yyyy", + Locale.US); + Calendar transactionDate = Calendar.getInstance(); + transactionDate.set(Calendar.DAY_OF_MONTH, 2); + String TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + + /*** + * 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, + WITHDRAWAL_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")); + + /*** + * 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. + */ + this.savingsAccountHelper.postInterestForSavings(savingsId); + HashMap accountDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + Float accountDetailsPostInterestPosted = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + Float nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + HashMap interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + Integer daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + double interestRateInFraction = (nominalAnnualInterest / 100); + double perDay = (double) 1 / (daysInYear); + double interestPerDay = interestRateInFraction * perDay; + Float interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + DecimalFormat decimalFormat = new DecimalFormat("", + new DecimalFormatSymbols(Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + HashMap accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + Float accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + + transactionDate.set(Calendar.DAY_OF_MONTH, 3); + TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked-------"); + + DateFormat transactionFormat = new SimpleDateFormat("dd MMMM yyyy", + Locale.US); + Calendar transactionCalendarDateFormat = Calendar.getInstance(); + transactionCalendarDateFormat.add(Calendar.DAY_OF_MONTH, 0); + transactionDate.set(Calendar.DAY_OF_MONTH, 22); + TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + if (Calendar.DAY_OF_MONTH >= 22) { + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountTransactionDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get( + "id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 19); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + } + DateFormat lastTransactionDateFormat = new SimpleDateFormat( + "dd MMMM yyyy", Locale.US); + Calendar postedLastDate = Calendar.getInstance(); + int numberOfDateOfMonth = postedLastDate + .getActualMaximum(Calendar.DAY_OF_MONTH); + TRANSACTION_DATE = lastTransactionDateFormat.format(transactionDate + .getTime()); + + if (Calendar.DAY_OF_MONTH == numberOfDateOfMonth) { + + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountTransactionDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get( + "id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 8); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + + } + transactionDate.set(Calendar.DAY_OF_MONTH, 1); + TRANSACTION_DATE = dateFormat.format(transactionDate.getTime()); + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, + TRANSACTION_DATE); + accountTransactionDetails = this.savingsAccountHelper + .getSavingsDetails(savingsId); + summary = (HashMap) accountTransactionDetails.get("summary"); + accountDetailsPostInterest = Float.valueOf(summary.get( + "totalInterestPosted").toString()); + + nominalAnnualInterest = Float.valueOf(accountDetails.get( + "nominalAnnualInterestRate").toString()); + interestCalculationDaysInYearType = (HashMap) accountDetails + .get("interestCalculationDaysInYearType"); + daysInYear = Integer.valueOf(interestCalculationDaysInYearType + .get("id").toString()); + interestRateInFraction = (nominalAnnualInterest / 100); + perDay = (double) 1 / (daysInYear); + interestPerDay = interestRateInFraction * perDay; + interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + decimalFormat = new DecimalFormat("", new DecimalFormatSymbols( + Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + accountDetailsPostInterestPosted = new Float( + decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, + accountDetailsPostInterestPosted); + System.out + .println("-----Post Interest As on Successfully Worked----------"); + } + + @SuppressWarnings("unchecked") + @Test + public void testSavingsAccountPostInterestWithOverdraft() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); + + /*** + * Create a client to apply for savings account (overdraft account). + */ + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + + /*** + * Create savings product with zero opening balance and overdraft + * enabled + */ + final String zeroOpeningBalance = "0.0"; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = true; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, zeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assert.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertTrue(modifications.containsKey("submittedOnDate")); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + /*** + * Approve the savings account + */ + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -1); + todaysDate.set(Calendar.DAY_OF_MONTH, 1); + final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); + final Integer lastDayOfMonth = todaysDate.getActualMaximum(Calendar.DAY_OF_MONTH); + todaysDate.set(Calendar.DAY_OF_MONTH, lastDayOfMonth); + final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime()); + + Calendar postedDate = Calendar.getInstance(); + postedDate.set(Calendar.DAY_OF_MONTH, 2); + + final String POSTED_TRANSACTION_DATE = dateFormat.format(postedDate.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. + */ + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, POSTED_TRANSACTION_DATE); + HashMap accountDetailsPostInterest = this.savingsAccountHelper.getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + ArrayList interestPostingTransaction = (ArrayList) ((HashMap) ((ArrayList) accountDetails.get("transactions")).get(0)).get("date"); + Float accountDetailsPostInterestPosted = Float.valueOf(summary.get("totalInterestPosted").toString()); + + /*** + * Calculate expected interest to be posted, interest should be posted + * for one day only because deposit transaction happened on last day of + * month before this account balance is negative. + */ + final Float nominalAnnualInterest = Float.valueOf(accountDetails.get("nominalAnnualInterestRate").toString()); + final HashMap interestCalculationDaysInYearType = (HashMap) accountDetails.get("interestCalculationDaysInYearType"); + final Integer daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get("id").toString()); + double interestRateInFraction = (nominalAnnualInterest / 100); + double perDay = (double) 1 / (daysInYear); + double interestPerDay = interestRateInFraction * perDay; + Float interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + actualInterestPosted = new Float(decimalFormat.format(accountDetailsPostInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, accountDetailsPostInterestPosted); + System.out.println("------Post Interest As On After doing a post interest Successfully worked--------"); + + todaysDate = Calendar.getInstance(); + final String CLOSEDON_DATE = dateFormat.format(todaysDate.getTime()); + + Calendar interestPostingDate = Calendar.getInstance(); + Calendar todysDate = Calendar.getInstance(); + interestPostingDate.set((int) interestPostingTransaction.get(0), (int) interestPostingTransaction.get(1) - 1, + (int) interestPostingTransaction.get(2)); + final String INTEREST_POSTING_DATE = dateFormat.format(interestPostingDate.getTime()); + final String TODYS_POSTING_DATE = dateFormat.format(todysDate.getTime()); + String withdrawBalance = "true"; + + if (TODYS_POSTING_DATE.equalsIgnoreCase(INTEREST_POSTING_DATE)) { + final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); + validationErrorHelper.closeSavingsAccountPostInterestAndGetBackRequiredField(savingsId, withdrawBalance, + CommonConstants.RESPONSE_ERROR, CLOSEDON_DATE); + } else { + final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); + ArrayList savingsAccountErrorData = (ArrayList) validationErrorHelper + .closeSavingsAccountPostInterestAndGetBackRequiredField(savingsId, withdrawBalance, CommonConstants.RESPONSE_ERROR, + CLOSEDON_DATE); + assertEquals("error.msg.postInterest.notDone", savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + } + + } + + @SuppressWarnings("unchecked") + @Test + public void testPostInterestAsOnSavingsAccountWithOverdraft() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(400).build(); + final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); + + /*** + * Create a client to apply for savings account (overdraft account). + */ + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assert.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + + /*** + * Create savings product with zero opening balance and overdraft + * enabled + */ + final String zeroOpeningBalance = "0.0"; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = true; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, zeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assert.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assert.assertTrue(modifications.containsKey("submittedOnDate")); + + HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId); + SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap); + + /*** + * Approve the savings account + */ + savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.MONTH, -1); + todaysDate.set(Calendar.DAY_OF_MONTH, 1); + final String ACTIVATION_DATE = dateFormat.format(todaysDate.getTime()); + final Integer lastDayOfMonth = todaysDate.getActualMaximum(Calendar.DAY_OF_MONTH); + todaysDate.set(Calendar.DAY_OF_MONTH, lastDayOfMonth); + final String TRANSACTION_DATE = dateFormat.format(todaysDate.getTime()); + + Calendar postedDate = Calendar.getInstance(); + postedDate.set(Calendar.DAY_OF_MONTH, 1); + + final String POSTED_TRANSACTION_DATE = dateFormat.format(postedDate.getTime()); + Calendar postedLastDate = Calendar.getInstance(); + int countOfDate=postedDate.getActualMaximum(Calendar.DAY_OF_MONTH); + System.out.println("count Of Date---> "+countOfDate); + postedLastDate.set(Calendar.DAY_OF_MONTH,countOfDate); + final String POSTED_LAST_TRANSACTION_DATE = dateFormat.format(postedLastDate.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.postInterestAsOnSavings(savingsId, POSTED_TRANSACTION_DATE); + HashMap accountDetails = this.savingsAccountHelper.getSavingsDetails(savingsId); + summary = (HashMap) accountDetails.get("summary"); + Float actualInterestPosted = Float.valueOf(summary.get("totalInterestPosted").toString()); + + + /*** + * Calculate expected interest to be posted, interest should be posted + * for one day only because deposit transaction happened on last day of + * month before this account balance is negative. + */ + final Float nominalAnnualInterest = Float.valueOf(accountDetails.get("nominalAnnualInterestRate").toString()); + final HashMap interestCalculationDaysInYearType = (HashMap) accountDetails.get("interestCalculationDaysInYearType"); + final Integer daysInYear = Integer.valueOf(interestCalculationDaysInYearType.get("id").toString()); + double interestRateInFraction = (nominalAnnualInterest / 100); + double perDay = (double) 1 / (daysInYear); + double interestPerDay = interestRateInFraction * perDay; + Float interestPosted = (float) (interestPerDay * balance * 1); + + /*** + * Apply rounding on interestPosted, actualInterestPosted and verify + * both are same + */ + DecimalFormat decimalFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US)); + decimalFormat.applyPattern("#.###"); + interestPosted = new Float(decimalFormat.format(interestPosted)); + actualInterestPosted = new Float(decimalFormat.format(actualInterestPosted)); + assertEquals("Verifying interest posted", interestPosted, actualInterestPosted); + System.out.println("------Post Interest As On Successful Worked--------"); + + this.savingsAccountHelper.postInterestAsOnSavings(savingsId, POSTED_LAST_TRANSACTION_DATE); + HashMap accountLastDetails = this.savingsAccountHelper.getSavingsDetails(savingsId); + summary = (HashMap) accountLastDetails.get("summary"); + Float actualLastInterestPosted = Float.valueOf(summary.get("totalInterestPosted").toString()); + + final Float nominalLastAnnualInterest = Float.valueOf(accountDetails.get("nominalAnnualInterestRate").toString()); + final HashMap interestLastCalculationDaysInYearType = (HashMap) accountDetails.get("interestCalculationDaysInYearType"); + final Integer daysLastInYear = Integer.valueOf(interestCalculationDaysInYearType.get("id").toString()); + double interestLastRateInFraction = (nominalAnnualInterest / 100); + double perLastDay = (double) 1 / (daysInYear); + double interestLastPerDay = interestLastRateInFraction * perLastDay; + Float interestLastPosted = (float) (interestLastPerDay * balance * 1); + + DecimalFormat decimalLastFormat = new DecimalFormat("", new DecimalFormatSymbols(Locale.US)); + decimalLastFormat.applyPattern("#.###"); + interestLastPosted = new Float(decimalLastFormat.format(interestLastPosted)); + actualInterestPosted = new Float(decimalFormat.format(actualInterestPosted)); + assertEquals("Verifying interest posted", interestLastPosted, actualInterestPosted); + System.out.println("------Post Interest As On Successful Worked--------"); + + } + + + @Test public void testSavingsAccount_WITH_WITHHOLD_TAX() { this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index 07917d559..325d23ec4 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -47,6 +47,7 @@ public class SavingsAccountHelper { private static final String WITHDRAWN_BY_CLIENT_SAVINGS_COMMAND = "withdrawnByApplicant"; private static final String CALCULATE_INTEREST_SAVINGS_COMMAND = "calculateInterest"; private static final String POST_INTEREST_SAVINGS_COMMAND = "postInterest"; + private static final String POST_INTEREST_AS_ON_SAVINGS_COMMAND = "postInterestAsOn"; private static final String CLOSE_SAVINGS_COMMAND = "close"; private static final String UPDATE_WITHHOLD_TAX_STATUS = "updateWithHoldTax"; @@ -195,6 +196,12 @@ public class SavingsAccountHelper { performSavingActions(createSavingsCalculateInterestURL(POST_INTEREST_SAVINGS_COMMAND, savingsId), getCalculatedInterestForSavingsApplicationAsJSON(), ""); } + + public void postInterestAsOnSavings(final Integer savingsId, final String today) { + System.out.println("--------------------------------- POST INTEREST AS ON FOR SAVINGS --------------------------------"); + performSavingActions(createSavingsPostInterestAsOnURL(POST_INTEREST_AS_ON_SAVINGS_COMMAND, savingsId), + getCalculatedInterestForSavingsApplicationAsJSON(today), CommonConstants.RESPONSE_RESOURCE_ID); + } public Integer addChargesForSavings(final Integer savingsId, final Integer chargeId, boolean addDueDate) { System.out.println("--------------------------------- ADD CHARGES FOR SAVINGS --------------------------------"); @@ -286,6 +293,16 @@ public class SavingsAccountHelper { System.out.println(savingsAccountCalculatedInterestJson); return savingsAccountCalculatedInterestJson; } + + private String getCalculatedInterestForSavingsApplicationAsJSON(final String today) { + final HashMap map = new HashMap<>(); + map.put("locale", CommonConstants.locale); + map.put("dateFormat", CommonConstants.dateFormat); + map.put("transactionDate", today); + map.put("postInterestManualOrAutomatic", "true"); + String savingsAccountCalculatedInterestJson = new Gson().toJson(map); + return savingsAccountCalculatedInterestJson; + } private String getSavingsPayChargeJSON(final String amount, final String dueDate) { final HashMap map = new HashMap<>(); @@ -321,6 +338,20 @@ public class SavingsAccountHelper { map.put("closedOnDate", closedOnDate); map.put("withdrawBalance", withdrawBalance); map.put("note", "Close Test"); + + String josn = new Gson().toJson(map); + return josn; + } + + private String getCloseAccountPostInterestJSON(String withdrawBalance, String closedOnDate) { + final HashMap map = new HashMap<>(); + map.put("locale", CommonConstants.locale); + map.put("dateFormat", CommonConstants.dateFormat); + map.put("closedOnDate", closedOnDate); + map.put("withdrawBalance", withdrawBalance); + map.put("note", "Close Test"); + map.put("postInterestValidationOnClosure", "true"); + String josn = new Gson().toJson(map); return josn; } @@ -341,6 +372,10 @@ public class SavingsAccountHelper { private String createSavingsCalculateInterestURL(final String command, final Integer savingsID) { return SAVINGS_ACCOUNT_URL + "/" + savingsID + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER; } + + private String createSavingsPostInterestAsOnURL(final String command, final Integer savingsID) { + return SAVINGS_ACCOUNT_URL + "/" + savingsID + "/transactions/" + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER; + } private String createChargesURL(final String command, final Integer savingsID, final Integer chargeId) { return SAVINGS_ACCOUNT_URL + "/" + savingsID + "/charges/" + chargeId + "?command=" + command + "&" + Utils.TENANT_IDENTIFIER; @@ -418,6 +453,13 @@ public class SavingsAccountHelper { return performSavingActions(createSavingsOperationURL(CLOSE_SAVINGS_COMMAND, savingsID), getCloseAccountJSON(withdrawBalance, closedOnDate), jsonAttributeToGetBack); } + + public Object closeSavingsAccountPostInterestAndGetBackRequiredField(final Integer savingsID, String withdrawBalance, + final String jsonAttributeToGetBack, final String closedOnDate) { + System.out.println("---------------------------------- CLOSE SAVINGS APPLICATION ----------------------------------"); + return performSavingActions(createSavingsOperationURL(CLOSE_SAVINGS_COMMAND, savingsID), + getCloseAccountPostInterestJSON(withdrawBalance, closedOnDate), jsonAttributeToGetBack); + } private String getPeriodChargeRequestJSON(Integer chargeId, boolean addDueDate) { final HashMap map = new HashMap<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index fa408d69e..fb497c04c 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -2248,6 +2248,15 @@ public class CommandWrapperBuilder { this.href = "/savingsaccounts/" + accountId + "?commad=unassignSavingsOfficer"; return this; } + + public CommandWrapperBuilder SavingsInterestPostingAsOnDate(final Long accountId) { + this.actionName = "POSTINTERESTASONDATE"; + this.entityName = "SAVINGSACCOUNT"; + this.savingsId = accountId; + this.entityId = null; + this.href = "/savingsaccounts/" + accountId + "?command=postInterestAsOn"; + return this; + } public CommandWrapperBuilder createLoanRescheduleRequest(final String entityName) { this.actionName = "CREATE"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java index 3393f8e14..883f9adb3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java @@ -34,6 +34,7 @@ public class SavingsApiConstants { public static final String SAVINGS_ACCOUNT_CHARGE_RESOURCE_NAME = "savingsaccountcharge"; // actions + public static final String postInterestValidationOnClosure = "postInterestValidationOnClosure"; public static String summitalAction = ".summital"; public static String approvalAction = ".approval"; public static String undoApprovalAction = ".undoApproval"; @@ -244,13 +245,17 @@ public class SavingsApiConstants { public static final Set SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>( Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, runningBalanceParamName, reversedParamName)); + + public static final Set SAVINGS_ACCOUNT_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(idParamName, + accountNoParamName)); public static final Set SAVINGS_ACCOUNT_ACTIVATION_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName, activatedOnDateParamName)); public static final Set SAVINGS_ACCOUNT_CLOSE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName, closedOnDateParamName, noteParamName, paymentTypeIdParamName, withdrawBalanceParamName, - transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName)); + transactionAccountNumberParamName, checkNumberParamName, routingCodeParamName, receiptNumberParamName, bankNumberParamName, + postInterestValidationOnClosure)); public static final Set SAVINGS_ACCOUNT_CHARGES_RESPONSE_DATA_PARAMETERS = new HashSet<>(Arrays.asList(chargeIdParamName, savingsAccountChargeIdParamName, chargeNameParamName, penaltyParamName, chargeTimeTypeParamName, dueAsOfDateParamName, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index 235631909..db2bfd6b6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -91,8 +91,8 @@ public class SavingsAccountTransactionsApiResource { @Produces({ MediaType.APPLICATION_JSON }) public String retrieveTemplate(@PathParam("savingsId") final Long savingsId, // @QueryParam("command") final String commandParam, - @Context final UriInfo uriInfo) { - + @Context final UriInfo uriInfo) { + this.context.authenticatedUser().validateHasReadPermission(SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME); // FIXME - KW - for now just send back generic default information for @@ -104,7 +104,7 @@ public class SavingsAccountTransactionsApiResource { final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); return this.toApiJsonSerializer.serialize(settings, savingsAccount, - SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS); + SavingsApiConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS); } @GET @@ -142,6 +142,9 @@ public class SavingsAccountTransactionsApiResource { } else if (is(commandParam, "withdrawal")) { final CommandWrapper commandRequest = builder.savingsAccountWithdrawal(savingsId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); + } else if (is(commandParam, "postInterestAsOn")) { + final CommandWrapper commandRequest = builder.savingsAccountInterestPosting(savingsId).build(); + result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } if (result == null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java index 75b1cc942..d6424ba25 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java @@ -49,6 +49,7 @@ public class SavingsAccountTransactionData { private final boolean reversed; private final AccountTransferData transfer; private final LocalDate submittedOnDate; + private final boolean interestedPostedAsOn; // templates final Collection paymentTypeOptions; @@ -56,19 +57,19 @@ public class SavingsAccountTransactionData { public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, - final AccountTransferData transfer) { + final AccountTransferData transfer, final boolean interestedPostedAsOn) { final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, - amount, runningBalance, reversed, transfer, paymentTypeOptions); + amount, runningBalance, reversed, transfer, paymentTypeOptions, interestedPostedAsOn); } public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType, - final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, - final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, - final AccountTransferData transfer,final LocalDate submittedOnDate) { + final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, + final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, + final AccountTransferData transfer, final LocalDate submittedOnDate, final boolean interestedPostedAsOn) { final Collection paymentTypeOptions = null; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, - amount, runningBalance, reversed, transfer, paymentTypeOptions,submittedOnDate); + amount, runningBalance, reversed, transfer, paymentTypeOptions, submittedOnDate, interestedPostedAsOn); } public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo, @@ -80,8 +81,9 @@ public class SavingsAccountTransactionData { final boolean reversed = false; final PaymentDetailData paymentDetailData = null; final Collection paymentTypeOptions = null; + final boolean interestedPostedAsOn = false; return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate, - currency, amount, runningBalance, reversed, null, null); + currency, amount, runningBalance, reversed, null, null, interestedPostedAsOn); } public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData, @@ -90,24 +92,23 @@ public class SavingsAccountTransactionData { savingsAccountTransactionData.paymentDetailData, savingsAccountTransactionData.accountId, savingsAccountTransactionData.accountNo, savingsAccountTransactionData.date, savingsAccountTransactionData.currency, savingsAccountTransactionData.amount, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed, - savingsAccountTransactionData.transfer, paymentTypeOptions); + savingsAccountTransactionData.transfer, paymentTypeOptions, savingsAccountTransactionData.interestedPostedAsOn); } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, - final AccountTransferData transfer, final Collection paymentTypeOptions) { - - this(id,transactionType,paymentDetailData,savingsId, savingsAccountNo,date, - currency,amount,runningBalance, reversed, - transfer, paymentTypeOptions,null); + final AccountTransferData transfer, final Collection paymentTypeOptions, final boolean interestedPostedAsOn) { + this(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, runningBalance, reversed, + transfer, paymentTypeOptions, null, interestedPostedAsOn); } private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType, - final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, - final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, - final AccountTransferData transfer, final Collection paymentTypeOptions,final LocalDate submittedOnDate) { + final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date, + final CurrencyData currency, final BigDecimal amount, final BigDecimal runningBalance, final boolean reversed, + final AccountTransferData transfer, final Collection paymentTypeOptions, final LocalDate submittedOnDate, + final boolean interestedPostedAsOn) { this.id = id; this.transactionType = transactionType; this.paymentDetailData = paymentDetailData; @@ -121,6 +122,7 @@ public class SavingsAccountTransactionData { this.transfer = transfer; this.paymentTypeOptions = paymentTypeOptions; this.submittedOnDate = submittedOnDate; + this.interestedPostedAsOn = interestedPostedAsOn; } public static SavingsAccountTransactionData withWithDrawalTransactionDetails( @@ -135,6 +137,6 @@ public class SavingsAccountTransactionData { savingsAccountTransactionData.accountNo, currentDate, savingsAccountTransactionData.currency, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.runningBalance, savingsAccountTransactionData.reversed, savingsAccountTransactionData.transfer, - savingsAccountTransactionData.paymentTypeOptions); + savingsAccountTransactionData.paymentTypeOptions, savingsAccountTransactionData.interestedPostedAsOn); } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 3807d6e51..89efe2db2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -190,9 +190,10 @@ public class FixedDepositAccount extends SavingsAccount { public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext mc, final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { List allTransactions = new ArrayList<>(); + final boolean postInterestAsOn = false; final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, - this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null); // TODO: + this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null, postInterestAsOn); // TODO: // verify // if // it @@ -297,9 +298,11 @@ public class FixedDepositAccount extends SavingsAccount { final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType .fromInt(this.interestCalculationDaysInYearType); - + List postedAsOnTransactionDates= getManualPostingDates(); + final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods( - accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, financialYearBeginningMonth); + accountSubmittedOrActivationDate(), maturityDate, postingPeriodType, financialYearBeginningMonth, + postedAsOnTransactionDates); final List allPostingPeriods = new ArrayList<>(); @@ -311,11 +314,14 @@ public class FixedDepositAccount extends SavingsAccount { boolean isInterestTransfer = false; final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); for (final LocalDateInterval periodInterval : postingPeriodIntervals) { - + boolean isUserPosting = false; + if (postedAsOnTransactionDates.contains(periodInterval.endDate())) { + isUserPosting = true; + } final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, transactions, this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), maturityDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation, - isSavingsInterestPostingAtCurrentPeriodEnd); + isSavingsInterestPostingAtCurrentPeriodEnd, isUserPosting); periodStartingBalance = postingPeriod.closingBalance(); @@ -490,8 +496,9 @@ public class FixedDepositAccount extends SavingsAccount { final LocalDate interestPostingUpToDate = maturityDate(); final MathContext mc = MathContext.DECIMAL64; final boolean isInterestTransfer = false; + final boolean postInterestAsOn = false; final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestAsOn); Money interestPostedToDate = Money.zero(this.currency); @@ -511,7 +518,7 @@ public class FixedDepositAccount extends SavingsAccount { final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); if (postingTransaction == null) { final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, postInterestAsOn); this.transactions.add(newPostingTransaction); recalucateDailyBalanceDetails = true; } else { @@ -519,7 +526,7 @@ public class FixedDepositAccount extends SavingsAccount { if (correctionRequired) { postingTransaction.reverse(); final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, postInterestAsOn); this.transactions.add(newPostingTransaction); recalucateDailyBalanceDetails = true; } @@ -549,8 +556,9 @@ public class FixedDepositAccount extends SavingsAccount { // post remaining interest final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); if (!remainigInterestToBePosted.isZero()) { + final boolean postInterestAsOn = false; final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - accountCloseDate, remainigInterestToBePosted); + accountCloseDate, remainigInterestToBePosted, postInterestAsOn); this.transactions.add(newPostingTransaction); recalucateDailyBalance = true; } @@ -600,18 +608,21 @@ public class FixedDepositAccount extends SavingsAccount { @Override public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final boolean postAsInterestOn) { + final boolean postInterestAsOn=false; final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } @Override public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, + final boolean postAsInterestOn) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postAsInterestOn); } private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index 6ff9c99e9..733079e93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -331,9 +331,9 @@ public class RecurringDepositAccount extends SavingsAccount { final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType .fromInt(this.interestCalculationDaysInYearType); - + List PostedAsOnDates = getManualPostingDates(); final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(depositStartDate(), - maturityDate, postingPeriodType, financialYearBeginningMonth); + maturityDate, postingPeriodType, financialYearBeginningMonth, PostedAsOnDates); final List allPostingPeriods = new ArrayList<>(); @@ -345,10 +345,14 @@ public class RecurringDepositAccount extends SavingsAccount { boolean isInterestTransfer = false; final Money minBalanceForInterestCalculation = Money.of(getCurrency(), minBalanceForInterestCalculation()); for (final LocalDateInterval periodInterval : postingPeriodIntervals) { + boolean isUserPosting = false; + if (PostedAsOnDates.contains(periodInterval.endDate())) { + isUserPosting = true; + } final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, transactions, this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), maturityDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation, - isSavingsInterestPostingAtCurrentPeriodEnd); + isSavingsInterestPostingAtCurrentPeriodEnd, isUserPosting); periodStartingBalance = postingPeriod.closingBalance(); @@ -369,9 +373,10 @@ public class RecurringDepositAccount extends SavingsAccount { if (generateFutureTransactions) { for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) { if (installment.isPrincipalNotCompleted(getCurrency())) { + boolean postInterestAsOn = false; final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, installment.dueDate(), installment.getDepositAmountOutstanding(getCurrency()), installment.dueDate().toDate(), - null); + null, postInterestAsOn); allTransactions.add(transaction); } } @@ -598,8 +603,9 @@ public class RecurringDepositAccount extends SavingsAccount { } final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; + boolean postInterestAsOn =false; final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestAsOn); Money interestPostedToDate = Money.zero(this.currency); @@ -617,7 +623,7 @@ public class RecurringDepositAccount extends SavingsAccount { final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); if (postingTransaction == null) { final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, postInterestAsOn); addTransaction(newPostingTransaction); recalucateDailyBalanceDetails = true; } else { @@ -625,7 +631,7 @@ public class RecurringDepositAccount extends SavingsAccount { if (correctionRequired) { postingTransaction.reverse(); final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, postInterestAsOn); addTransaction(newPostingTransaction); recalucateDailyBalanceDetails = true; } @@ -656,8 +662,9 @@ public class RecurringDepositAccount extends SavingsAccount { // post remaining interest final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); if (!remainigInterestToBePosted.isZero()) { + final boolean postInterestAsOn = false; final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - accountCloseDate, remainigInterestToBePosted); + accountCloseDate, remainigInterestToBePosted, postInterestAsOn); addTransaction(newPostingTransaction); recalucateDailyBalance = true; } @@ -707,18 +714,18 @@ public class RecurringDepositAccount extends SavingsAccount { @Override public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,final boolean postInterestAson) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAson); } @Override public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,final boolean postAsInterestOn) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postAsInterestOn); } private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index bc97a6724..46b875ada 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -235,6 +235,7 @@ public class SavingsAccount extends AbstractPersistableCustom { @Column(name = "interest_calculation_type_enum", nullable = false) protected Integer interestCalculationType; + /** * A value from the {@link SavingsInterestCalculationDaysInYearType} * enumeration. @@ -462,13 +463,10 @@ public class SavingsAccount extends AbstractPersistableCustom { public boolean isClosed() { return SavingsAccountStatusType.fromInt(this.status).isClosed(); } - public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer, - final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { - + final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,final boolean postInterestAsOn) { final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestAsOn); Money interestPostedToDate = Money.zero(this.currency); boolean recalucateDailyBalanceDetails = false; @@ -477,23 +475,23 @@ public class SavingsAccount extends AbstractPersistableCustom { withholdTransactions.addAll(findWithHoldTransactions()); for (final PostingPeriod interestPostingPeriod : postingPeriods) { - + final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction(); final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned(); - + if (!interestPostingTransactionDate.isAfter(interestPostingUpToDate)) { - interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod); final SavingsAccountTransaction postingTransaction = findInterestPostingTransactionFor(interestPostingTransactionDate); if (postingTransaction == null) { SavingsAccountTransaction newPostingTransaction; if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(currency))) { + newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), interestPostingTransactionDate, - interestEarnedToBePostedForPeriod); + interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting()); } else { newPostingTransaction = SavingsAccountTransaction.overdraftInterest(this, office(), interestPostingTransactionDate, - interestEarnedToBePostedForPeriod.negated()); + interestEarnedToBePostedForPeriod.negated(), interestPostingPeriod.isUserPosting()); } addTransaction(newPostingTransaction); if (applyWithHoldTax) { @@ -519,10 +517,12 @@ public class SavingsAccount extends AbstractPersistableCustom { SavingsAccountTransaction newPostingTransaction; if (interestEarnedToBePostedForPeriod.isGreaterThanOrEqualTo(Money.zero(currency))) { newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod); + interestPostingTransactionDate, interestEarnedToBePostedForPeriod, + interestPostingPeriod.isUserPosting()); } else { newPostingTransaction = SavingsAccountTransaction.overdraftInterest(this, office(), - interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated()); + interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(), + interestPostingPeriod.isUserPosting()); } addTransaction(newPostingTransaction); if (applyWithHoldTaxForOldTransaction) { @@ -641,6 +641,16 @@ public class SavingsAccount extends AbstractPersistableCustom { return savingsTransaction; } + public List getManualPostingDates() { + List transactions = new ArrayList<>(); + for (SavingsAccountTransaction trans : this.transactions) { + if (trans.isManualTransaction()) { + transactions.add(trans.getTransactionLocalDate()); + } + } + return transactions; + } + /** * All interest calculation based on END-OF-DAY-BALANCE. * @@ -657,9 +667,10 @@ public class SavingsAccount extends AbstractPersistableCustom { * * @param isInterestTransfer * TODO - */ + */ + public List calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate, - boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { + boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,final boolean postInterestAsOn) { // no openingBalance concept supported yet but probably will to allow // for migrations. @@ -680,10 +691,14 @@ public class SavingsAccount extends AbstractPersistableCustom { .fromInt(this.interestCompoundingPeriodType); final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType - .fromInt(this.interestCalculationDaysInYearType); - + .fromInt(this.interestCalculationDaysInYearType); + List postedAsOnDates= getManualPostingDates(); + if(postInterestAsOn){ + postedAsOnDates.add(upToInterestCalculationDate); + } final List postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods( - getStartInterestCalculationDate(), upToInterestCalculationDate, postingPeriodType, financialYearBeginningMonth); + getStartInterestCalculationDate(), upToInterestCalculationDate, postingPeriodType, financialYearBeginningMonth, + postedAsOnDates); final List allPostingPeriods = new ArrayList<>(); @@ -719,12 +734,17 @@ public class SavingsAccount extends AbstractPersistableCustom { final Money minOverdraftForInterestCalculation = Money.of(getCurrency(), this.minOverdraftForInterestCalculation); for (final LocalDateInterval periodInterval : postingPeriodIntervals) { + + boolean isUserPosting = false; + if(postedAsOnDates.contains(periodInterval.endDate().plusDays(1))){ + isUserPosting = true; + } final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance, retreiveOrderedNonInterestPostingTransactions(), this.currency, compoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYearType.getValue(), upToInterestCalculationDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, - overdraftInterestRateAsFraction, minOverdraftForInterestCalculation); + overdraftInterestRateAsFraction, minOverdraftForInterestCalculation, isUserPosting); periodStartingBalance = postingPeriod.closingBalance(); @@ -2133,13 +2153,15 @@ public class SavingsAccount extends AbstractPersistableCustom { if (isSavingsChargeApplied) { final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; + boolean postInterestAsOnDate = false; if (this.isBeforeLastPostingPeriod(getActivationLocalDate())) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOnDate); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOnDate); } } } @@ -2781,10 +2803,10 @@ public class SavingsAccount extends AbstractPersistableCustom { this.sub_status = SavingsAccountSubStatusEnum.ESCHEAT.getValue(); this.closedOnDate = DateUtils.getDateOfTenant(); this.closedBy = appUser; - + boolean postInterestAsOnDate = false; LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); if(this.getSummary().getAccountBalance(this.getCurrency()).isGreaterThanZero()){ - SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser); + SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser, postInterestAsOnDate); this.transactions.add(transaction); } recalculateDailyBalances(Money.zero(this.currency), transactionDate); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index 53141c266..64260263e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -86,6 +86,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi if (transactionBooleanValues.isRegularTransaction() && !account.allowWithdrawal()) { throw new DepositAccountTransactionNotAllowedException( account.getId(), "withdraw", account.depositAccountType()); } final Set existingTransactionIds = new HashSet<>(); + final boolean interestPostAsOn = false; final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, @@ -96,11 +97,11 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi if (account.isBeforeLastPostingPeriod(transactionDate)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, interestPostAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(), - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, interestPostAsOn); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { @@ -154,15 +155,16 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, new Date(), user); final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType); - + final boolean interestPostAsOn = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + interestPostAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, interestPostAsOn); } saveTransactionToGenerateTransactionId(deposit); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index 166f6ed66..56e648579 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -114,6 +114,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom taxDetails) { final boolean isReversed = false; + final boolean isManualTransaction = false; SavingsAccountTransaction accountTransaction = new SavingsAccountTransaction(savingsAccount, office, - SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null); + SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null, isManualTransaction); updateTaxDetails(taxDetails, accountTransaction); return accountTransaction; } public static SavingsAccountTransaction escheat(final SavingsAccount savingsAccount, final LocalDate date, - final AppUser appUser) { + final AppUser appUser,final boolean accountTransaction) { final boolean isReversed = false; final PaymentDetail paymentDetail = null; return new SavingsAccountTransaction(savingsAccount, savingsAccount.office(), paymentDetail, SavingsAccountTransactionType.ESCHEAT.getValue(), date, new Date(), savingsAccount.getSummary() - .getAccountBalance(), isReversed, appUser); + .getAccountBalance(), isReversed, appUser,accountTransaction); } public static void updateTaxDetails(final Map taxDetails, final SavingsAccountTransaction accountTransaction) { @@ -245,23 +259,25 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom determineInterestPostingPeriods(final LocalDate startInterestCalculationLocalDate, final LocalDate interestPostingUpToDate, final SavingsPostingInterestPeriodType postingPeriodType, - final Integer financialYearBeginningMonth) { + final Integer financialYearBeginningMonth,List postInterestAsOn) { final List postingPeriods = new ArrayList<>(); - LocalDate periodStartDate = startInterestCalculationLocalDate; LocalDate periodEndDate = periodStartDate; - + LocalDate actualPeriodStartDate = periodStartDate; + while (!periodStartDate.isAfter(interestPostingUpToDate) && !periodEndDate.isAfter(interestPostingUpToDate)) { - - final LocalDate interestPostingLocalDate = determineInterestPostingPeriodEndDateFrom(periodStartDate, postingPeriodType, + + final LocalDate interestPostingLocalDate = determineInterestPostingPeriodEndDateFrom(periodStartDate, postingPeriodType, interestPostingUpToDate, financialYearBeginningMonth); + periodEndDate = interestPostingLocalDate.minusDays(1); - + + if (!postInterestAsOn.isEmpty()) { + for (LocalDate transactiondate : postInterestAsOn) { + if (periodStartDate.isBefore(transactiondate) + && (periodEndDate.isAfter(transactiondate) || periodEndDate + .isEqual(transactiondate))) { + periodEndDate = transactiondate.minusDays(1); + actualPeriodStartDate = periodEndDate; + break; + } + } + } + postingPeriods.add(LocalDateInterval.create(periodStartDate, periodEndDate)); - + + if (actualPeriodStartDate.isEqual(periodEndDate)) + { + periodEndDate = actualPeriodStartDate.plusDays(1); + periodStartDate = actualPeriodStartDate.plusDays(1); + }else{ periodEndDate = interestPostingLocalDate; periodStartDate = interestPostingLocalDate; + } } return postingPeriods; } private LocalDate determineInterestPostingPeriodEndDateFrom(final LocalDate periodStartDate, - final SavingsPostingInterestPeriodType interestPostingPeriodType, final LocalDate interestPostingUpToDate, + final SavingsPostingInterestPeriodType interestPostingPeriodType, final LocalDate interestPostingUpToDate, Integer financialYearBeginningMonth) { LocalDate periodEndDate = interestPostingUpToDate; @@ -90,7 +109,6 @@ public final class SavingsHelper { biannualDates.add(periodStartDate.withMonthOfYear(financialYearBeginningMonth).plusMonths(6).withYear(periodStartDate.getYear()) .dayOfMonth().withMaximumValue()); Collections.sort(biannualDates); - boolean isEndDateSet = false; switch (interestPostingPeriodType) { @@ -131,11 +149,9 @@ public final class SavingsHelper { } periodEndDate = periodEndDate.dayOfMonth().withMaximumValue(); break; - } - + } // interest posting always occurs on next day after the period end date. - periodEndDate = periodEndDate.plusDays(1); - + periodEndDate = periodEndDate.plusDays(1); return periodEndDate; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java index 26b731031..2d9f56020 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java @@ -54,6 +54,7 @@ public class PostingPeriod { // include in compounding interest private boolean interestTransfered = false; + private boolean isUserPosting = false; // minimum balance for interest calculation private final Money minBalanceForInterestCalculation; @@ -65,7 +66,7 @@ public class PostingPeriod { final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType, final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear, final LocalDate upToInterestCalculationDate, Collection interestPostTransactions, boolean isInterestTransfer, - final Money minBalanceForInterestCalculation, final boolean isSavingsInterestPostingAtCurrentPeriodEnd) { + final Money minBalanceForInterestCalculation, final boolean isSavingsInterestPostingAtCurrentPeriodEnd,final boolean isUserPosting) { final BigDecimal overdraftInterestRateAsFraction = BigDecimal.ZERO; final Money minOverdraftForInterestCalculation = Money.zero(currency); @@ -74,7 +75,7 @@ public class PostingPeriod { interestCompoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYear, upToInterestCalculationDate, interestPostTransactions, isInterestTransfer, minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, - overdraftInterestRateAsFraction, minOverdraftForInterestCalculation); + overdraftInterestRateAsFraction, minOverdraftForInterestCalculation, isUserPosting); } // isInterestTransfer boolean is to identify newly created transaction is @@ -85,7 +86,7 @@ public class PostingPeriod { final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear, final LocalDate upToInterestCalculationDate, Collection interestPostTransactions, boolean isInterestTransfer, final Money minBalanceForInterestCalculation, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, - final BigDecimal overdraftInterestRateAsFraction, final Money minOverdraftForInterestCalculation) { + final BigDecimal overdraftInterestRateAsFraction, final Money minOverdraftForInterestCalculation, boolean isUserPosting) { final List accountEndOfDayBalances = new ArrayList<>(); boolean interestTransfered = false; @@ -147,7 +148,7 @@ public class PostingPeriod { return new PostingPeriod(periodInterval, currency, periodStartingBalance, openingDayBalance, interestCompoundingPeriodType, interestCalculationType, interestRateAsFraction, daysInYear, compoundingPeriods, interestTransfered, minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, - overdraftInterestRateAsFraction, minOverdraftForInterestCalculation); + overdraftInterestRateAsFraction, minOverdraftForInterestCalculation, isUserPosting); } private PostingPeriod(final LocalDateInterval periodInterval, final MonetaryCurrency currency, final Money openingBalance, @@ -155,7 +156,7 @@ public class PostingPeriod { final SavingsInterestCalculationType interestCalculationType, final BigDecimal interestRateAsFraction, final long daysInYear, final List compoundingPeriods, boolean interestTransfered, final Money minBalanceForInterestCalculation, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final BigDecimal overdraftInterestRateAsFraction, - final Money minOverdraftForInterestCalculation) { + final Money minOverdraftForInterestCalculation, boolean isUserPosting) { this.periodInterval = periodInterval; this.currency = currency; this.openingBalance = openingBalance; @@ -174,6 +175,7 @@ public class PostingPeriod { this.minBalanceForInterestCalculation = minBalanceForInterestCalculation; this.overdraftInterestRateAsFraction = overdraftInterestRateAsFraction; this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation; + this.isUserPosting = isUserPosting; } public Money interest() { @@ -410,4 +412,12 @@ public class PostingPeriod { public boolean isInterestTransfered() { return this.interestTransfered; } + + public LocalDateInterval getPeriodInterval() { + return this.periodInterval; + } + + public boolean isUserPosting() { + return this.isUserPosting; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/PostInterestAsOnDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/PostInterestAsOnDateException.java new file mode 100644 index 000000000..76303682d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/PostInterestAsOnDateException.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.savings.exception; + + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + + +public class PostInterestAsOnDateException extends AbstractPlatformDomainRuleException{ + public static enum PostInterestAsOnException_TYPE { + FUTURE_DATE, VALID_DATE, ACTIVATION_DATE, LAST_TRANSACTION_DATE; + public String errorMessage() { + if (name().toString().equalsIgnoreCase("FUTURE_DATE")) { + return "Cannot Post Interest in future Dates"; + } else if (name().toString().equalsIgnoreCase("VALID_DATE")) { + return "Please Pass a valid date"; + } else if (name().toString().equalsIgnoreCase("ACTIVATION_DATE")) { + return "Post Interest Date must be after the Activation date"; + } else if (name().toString().equalsIgnoreCase("LAST_TRANSACTION_DATE")) { + return "Cannot Post Interest before last transaction date"; + } + return name().toString(); + } + + public String errorCode() { + if (name().toString().equalsIgnoreCase("FUTURE_DATE")) { + return "error.msg.futureDate"; + } else if (name().toString().equalsIgnoreCase("VALID_DATE")) { + return "error.msg.nullDatePassed"; + } else if (name().toString().equalsIgnoreCase("ACTIVATION_DATE")) { + return "error.msg.before activation date"; + } else if (name().toString().equalsIgnoreCase("LAST_TRANSACTION_DATE")) { + return "error.msg.countInterest"; + } + return name().toString(); + } + } + + + public PostInterestAsOnDateException(final PostInterestAsOnException_TYPE reason) { + super(reason.errorCode(), reason.errorMessage()); + } + + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/PostInterestClosingDateException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/PostInterestClosingDateException.java new file mode 100644 index 000000000..24a11de0f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/exception/PostInterestClosingDateException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.savings.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class PostInterestClosingDateException extends AbstractPlatformDomainRuleException { + + public PostInterestClosingDateException() { + super("error.msg.postInterest.notDone", "Please do a post interest on the closing date"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java index b48b215dc..7ff81d070 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostInterestSavingsAccountCommandHandler.java @@ -41,6 +41,6 @@ public class PostInterestSavingsAccountCommandHandler implements NewCommandSourc @Transactional @Override public CommandProcessingResult processCommand(final JsonCommand command) { - return this.writePlatformService.postInterest(command.getSavingsId()); + return this.writePlatformService.postInterest(command); } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostSavingsAccountInterestAsOnDateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostSavingsAccountInterestAsOnDateCommandHandler.java new file mode 100644 index 000000000..dc61644af --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/PostSavingsAccountInterestAsOnDateCommandHandler.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.savings.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.savings.service.SavingsAccountWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "SAVINGSACCOUNT", action = "POSTINTERESTASONDATE") +public class PostSavingsAccountInterestAsOnDateCommandHandler implements NewCommandSourceHandler { + + private final SavingsAccountWritePlatformService writePlatformService; + + @Autowired + public PostSavingsAccountInterestAsOnDateCommandHandler(final SavingsAccountWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.postInterest(command); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index 5853ddb5b..cf9496b62 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -1049,9 +1049,9 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate, toTransferDescription, toTransferReversed); } - + final boolean postInterestAsOn = false; return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, - amount, runningBalance, reversed, transfer); + amount, runningBalance, reversed, transfer, postInterestAsOn); } } @@ -1437,9 +1437,9 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead final PaymentDetailData paymentDetailData = null; final AccountTransferData transfer = null; final BigDecimal runningBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "runningBalance"); - ; + final boolean postInterestAsOn = false; return SavingsAccountTransactionData.create(savingsId, transactionType, paymentDetailData, savingsId, accountNo, duedate, - currency, dueamount, runningBalance, false, transfer); + currency, dueamount, runningBalance, false, transfer, postInterestAsOn); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index 51ffb4f58..d9f8b4eaa 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -143,7 +143,6 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo private final WorkingDaysRepositoryWrapper workingDaysRepository; private final DepositAccountOnHoldTransactionRepository depositAccountOnHoldTransactionRepository; - @Autowired public DepositAccountWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final SavingsAccountRepositoryWrapper savingAccountRepositoryWrapper, @@ -185,7 +184,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo this.depositAccountReadPlatformService = depositAccountReadPlatformService; this.calendarInstanceRepository = calendarInstanceRepository; this.configurationDomainService = configurationDomainService; - this.depositAccountOnHoldTransactionRepository =depositAccountOnHoldTransactionRepository; + this.depositAccountOnHoldTransactionRepository = depositAccountOnHoldTransactionRepository; } @Transactional @@ -235,14 +234,15 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } final boolean isInterestTransfer = false; + final boolean postInterestAsOn = false; if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -252,10 +252,12 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(), + depositAccountOnHoldTransactions); this.savingAccountRepositoryWrapper.saveAndFlush(account); } @@ -338,21 +340,24 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant(); account.updateOverduePayments(overdueUptoDate); final boolean isInterestTransfer = false; + final boolean postInterestAsOn = false; if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(), + depositAccountOnHoldTransactions); this.savingAccountRepositoryWrapper.saveAndFlush(account); } @@ -391,8 +396,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final LocalDate depositAmountUpdateEffectiveFromDate = command .localDateValueOfParameterNamed(DepositsApiConstants.effectiveDateParamName); - final RecurringDepositAccount recurringDepositAccount = (RecurringDepositAccount) this.depositAccountAssembler - .assembleFrom(savingsId, DepositAccountType.RECURRING_DEPOSIT); + final RecurringDepositAccount recurringDepositAccount = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom( + savingsId, DepositAccountType.RECURRING_DEPOSIT); DepositAccountRecurringDetail recurringDetail = recurringDepositAccount.getRecurringDetail(); Map changes = recurringDetail.updateMandatoryRecommendedDepositAmount(mandatoryRecommendedDepositAmount, depositAmountUpdateEffectiveFromDate, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); @@ -446,8 +451,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @Transactional @Override - public CommandProcessingResult withdrawal(final Long savingsId, final JsonCommand command, - final DepositAccountType depositAccountType) { + public CommandProcessingResult withdrawal(final Long savingsId, final JsonCommand command, final DepositAccountType depositAccountType) { boolean isRegularTransaction = true; @@ -493,9 +497,9 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; - + boolean postInterestAsOn = false; account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); this.savingAccountRepositoryWrapper.save(account); @@ -537,7 +541,9 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + boolean postInterestAsOn = false; + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOn); this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); @@ -588,11 +594,10 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo .findOneByIdAndSavingsAccountId(transactionId, savingsId); if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); } - if (!allowAccountTransferModification && this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, - PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( - "error.msg.recurring.deposit.account.transfer.transaction.update.not.allowed", - "Recurring deposit account transaction:" + transactionId + " update not allowed as it involves in account transfer", - transactionId); } + if (!allowAccountTransferModification + && this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( + "error.msg.recurring.deposit.account.transfer.transaction.update.not.allowed", "Recurring deposit account transaction:" + + transactionId + " update not allowed as it involves in account transfer", transactionId); } final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = MathContext.DECIMAL64; @@ -602,24 +607,26 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo } account.undoTransaction(transactionId); boolean isInterestTransfer = false; + boolean postInterestAsOn = false; checkClientOrGroupActive(account); if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOn); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction,depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions); // account.activateAccountBasedOnBalance(); final boolean isPreMatureClosure = false; - account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); final LocalDate overdueUptoDate = DateUtils.getLocalDateOfTenant(); @@ -663,14 +670,12 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo .findOneByIdAndSavingsAccountId(transactionId, savingsId); if (savingsAccountTransaction == null) { throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); } - if (!(savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) - || savingsAccountTransaction.isReversed()) { throw new TransactionUpdateNotAllowedException(savingsId, transactionId); } + if (!(savingsAccountTransaction.isDeposit() || savingsAccountTransaction.isWithdrawal()) || savingsAccountTransaction.isReversed()) { throw new TransactionUpdateNotAllowedException( + savingsId, transactionId); } - if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, - PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( - "error.msg.saving.account.transfer.transaction.update.not.allowed", - "Deposit account transaction:" + transactionId + " update not allowed as it involves in account transfer", - transactionId); } + if (this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) { throw new PlatformServiceUnavailableException( + "error.msg.saving.account.transfer.transaction.update.not.allowed", "Deposit account transaction:" + transactionId + + " update not allowed as it involves in account transfer", transactionId); } final LocalDate today = DateUtils.getLocalDateOfTenant(); @@ -705,19 +710,22 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo } final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); boolean isInterestTransfer = false; + final boolean postInterestAsOn = false; if (account.isBeforeLastPostingPeriod(transactionDate) || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOn); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction,depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions); account.activateAccountBasedOnBalance(); if (savingsAccountTransaction.isDeposit()) { @@ -913,7 +921,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(accountId, depositAccountType); - + final boolean postInterestAsOn = false; final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(savingsAccount, existingTransactionIds, existingReversedTransactionIds); @@ -925,7 +933,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); this.savingsAccountTransactionRepository.save(newTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -957,8 +965,9 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; + boolean postInterestAsOn = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); this.savingsAccountTransactionRepository.save(withdrawtransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -999,9 +1008,10 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo savingsAccount.reassignSavingsOfficer(fieldOfficer, transferDate); } boolean isInterestTransfer = false; + boolean postInterestAsOn = false; final MathContext mc = MathContext.DECIMAL64; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); this.savingsAccountTransactionRepository.save(acceptTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1131,8 +1141,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); // Get Savings account from savings charge final SavingsAccount account = savingsAccountCharge.savingsAccount(); @@ -1144,21 +1154,25 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo account.waiveCharge(savingsAccountChargeId, user); boolean isInterestTransfer = false; + boolean postInterestAsOn = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction, + depositAccountOnHoldTransactions); this.savingAccountRepositoryWrapper.saveAndFlush(account); @@ -1181,8 +1195,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final SavingsAccount savingsAccount = this.depositAccountAssembler.assembleFrom(savingsAccountId, depositAccountType); checkClientOrGroupActive(savingsAccount); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); savingsAccount.removeCharge(savingsAccountCharge); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1208,8 +1222,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final BigDecimal amountPaid = command.bigDecimalValueOfParameterNamed(amountParamName); final LocalDate transactionDate = command.localDateValueOfParameterNamed(dueAsOfDateParamName); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, savingsAccountId); final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -1247,8 +1261,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @SuppressWarnings("unused") final DepositAccountType depositAccountType) { // always use current date as transaction date for batch job final LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); - final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository - .findOneWithNotFoundDetection(savingsAccountChargeId, accountId); + final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection( + savingsAccountChargeId, accountId); final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd MM yyyy"); @@ -1275,21 +1289,25 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user); boolean isInterestTransfer = false; + boolean postInterestAsOn = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } List depositAccountOnHoldTransactions = null; - if(account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1){ - depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); + if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { + depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository + .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); } - account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),depositAccountOnHoldTransactions); + account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(), + depositAccountOnHoldTransactions); this.savingAccountRepositoryWrapper.saveAndFlush(account); @@ -1340,8 +1358,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo @Override public SavingsAccountTransaction mandatorySavingsAccountDeposit(final SavingsAccountTransactionDTO accountTransactionDTO) { boolean isRegularTransaction = false; - final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler - .assembleFrom(accountTransactionDTO.getSavingsAccountId(), DepositAccountType.RECURRING_DEPOSIT); + final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom( + accountTransactionDTO.getSavingsAccountId(), DepositAccountType.RECURRING_DEPOSIT); final PaymentDetail paymentDetail = accountTransactionDTO.getPaymentDetail(); if (paymentDetail != null && paymentDetail.getId() == null) { this.paymentDetailWritePlatformService.persistPaymentDetail(paymentDetail); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index fc6c5cb88..38099d113 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -767,7 +767,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead .append("sa.currency_code as currencyCode, sa.currency_digits as currencyDigits, sa.currency_multiplesof as inMultiplesOf, "); sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); - sqlBuilder.append("pt.value as paymentTypeName "); + sqlBuilder.append("pt.value as paymentTypeName, "); + sqlBuilder.append("tr.is_manual as postInterestAsOn "); sqlBuilder.append("from m_savings_account sa "); sqlBuilder.append("join m_savings_account_transaction tr on tr.savings_account_id = sa.id "); sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); @@ -797,6 +798,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final Long savingsId = rs.getLong("savingsId"); final String accountNo = rs.getString("accountNo"); + final boolean postInterestAsOn = rs.getBoolean("postInterestAsOn"); PaymentDetailData paymentDetailData = null; if (transactionType.isDepositOrWithdrawal()) { @@ -840,12 +842,14 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead final boolean toTransferReversed = rs.getBoolean("toTransferReversed"); final String toTransferDescription = rs.getString("toTransferDescription"); + + transfer = AccountTransferData.transferBasicDetails(toTransferId, currency, toTransferAmount, toTransferDate, toTransferDescription, toTransferReversed); } return SavingsAccountTransactionData.create(id, transactionType, paymentDetailData, savingsId, accountNo, date, currency, - amount, runningBalance, reversed, transfer, submittedOnDate); + amount, runningBalance, reversed, transfer, submittedOnDate, postInterestAsOn); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java index c8bf5789d..51e5383a2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -41,8 +41,6 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult calculateInterest(Long savingsId); - CommandProcessingResult postInterest(Long savingsId); - CommandProcessingResult undoTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification); CommandProcessingResult adjustSavingsTransaction(Long savingsId, Long transactionId, JsonCommand command); @@ -78,7 +76,6 @@ public interface SavingsAccountWritePlatformService { void processPostActiveActions(SavingsAccount account, DateTimeFormatter fmt, Set existingTransactionIds, Set existingReversedTransactionIds); - void postInterest(SavingsAccount account); CommandProcessingResult modifyWithHoldTax(Long savingsAccountId, JsonCommand command); @@ -87,4 +84,8 @@ public interface SavingsAccountWritePlatformService { void setSubStatusDormant(Long savingsId); void escheat(Long savingsId); + + CommandProcessingResult postInterest(JsonCommand command); + + void postInterest(SavingsAccount account, boolean postInterestAs, LocalDate transactionDate); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 4c1451ca4..88e4be992 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -39,6 +39,8 @@ import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; +import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException; +import org.apache.fineract.accounting.journalentry.exception.JournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -95,11 +97,14 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrap import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository; +import org.apache.fineract.portfolio.savings.exception.PostInterestAsOnDateException; +import org.apache.fineract.portfolio.savings.exception.PostInterestClosingDateException; import org.apache.fineract.portfolio.savings.exception.SavingsAccountClosingNotAllowedException; import org.apache.fineract.portfolio.savings.exception.SavingsAccountTransactionNotFoundException; import org.apache.fineract.portfolio.savings.exception.SavingsOfficerAssignmentException; import org.apache.fineract.portfolio.savings.exception.SavingsOfficerUnassignmentException; import org.apache.fineract.portfolio.savings.exception.TransactionUpdateNotAllowedException; +import org.apache.fineract.portfolio.savings.exception.PostInterestAsOnDateException.PostInterestAsOnException_TYPE; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper; import org.joda.time.LocalDate; @@ -255,7 +260,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); checkClientOrGroupActive(account); - final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); @@ -268,7 +272,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi boolean isRegularTransaction = true; final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction); - return new CommandProcessingResultBuilder() // .withEntityId(deposit.getId()) // .withOfficeId(account.officeId()) // @@ -277,6 +280,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .withSavingsId(savingsId) // .with(changes) // .build(); + } private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) { @@ -300,8 +304,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); - checkClientOrGroupActive(account); - + checkClientOrGroupActive(account); final boolean isAccountTransfer = false; final boolean isRegularTransaction = true; final boolean isApplyWithdrawFee = true; @@ -359,7 +362,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth,false); this.savingAccountRepositoryWrapper.save(account); @@ -371,13 +374,32 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi .withSavingsId(savingsId) // .build(); } - @Override - public CommandProcessingResult postInterest(final Long savingsId) { - + public CommandProcessingResult postInterest(final JsonCommand command) { + + Long savingsId=command.getSavingsId(); + final boolean postInterestAs =command.booleanPrimitiveValueOfParameterNamed("isPostInterestAsOn"); + final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); checkClientOrGroupActive(account); - postInterest(account); + if (postInterestAs == true) { + + if (transactionDate == null) { + + throw new PostInterestAsOnDateException(PostInterestAsOnException_TYPE.VALID_DATE); } + if (transactionDate.isBefore(account.accountSubmittedOrActivationDate())) { throw new PostInterestAsOnDateException( + PostInterestAsOnException_TYPE.ACTIVATION_DATE); } + List savingTransactions = account.getTransactions(); + for (SavingsAccountTransaction savingTransaction : savingTransactions) { + if (transactionDate.toDate().before(savingTransaction.getDateOf())) { throw new PostInterestAsOnDateException( + PostInterestAsOnException_TYPE.LAST_TRANSACTION_DATE); } + } + + LocalDate today = DateUtils.getLocalDateOfTenant(); + if (transactionDate.isAfter(today)) { throw new PostInterestAsOnDateException(PostInterestAsOnException_TYPE.FUTURE_DATE); } + + } + postInterest(account,postInterestAs,transactionDate); return new CommandProcessingResultBuilder() // .withEntityId(savingsId) // .withOfficeId(account.officeId()) // @@ -389,7 +411,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Transactional @Override - public void postInterest(final SavingsAccount account) { + public void postInterest(final SavingsAccount account,final boolean postInterestAs,final LocalDate transactionDate) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); @@ -403,14 +425,18 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); - + if(postInterestAs==false){ + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,postInterestAs); + } + else{ + account.postInterest(mc, transactionDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,postInterestAs); + } // for generating transaction id's List transactions = account.getTransactions(); for (SavingsAccountTransaction accountTransaction : transactions) { if (accountTransaction.getId() == null) { this.savingsAccountTransactionRepository.save(accountTransaction); - } + } } this.savingAccountRepositoryWrapper.saveAndFlush(account); @@ -418,7 +444,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); } } - + @Override public CommandProcessingResult undoTransaction(final Long savingsId, final Long transactionId, final boolean allowAccountTransferModification) { @@ -466,10 +492,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi checkClientOrGroupActive(account); if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,false); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth,false); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { @@ -555,10 +581,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (account.isBeforeLastPostingPeriod(transactionDate) || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,false); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth,false); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { @@ -604,9 +630,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult close(final Long savingsId, final JsonCommand command) { final AppUser user = this.context.authenticatedUser(); - + this.savingsAccountTransactionDataValidator.validateClosing(command); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); + final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId); if (isLinkedWithAnyActiveLoan) { @@ -614,18 +641,36 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi + " is not allowed, since it is linked with one of the active accounts"; throw new SavingsAccountClosingNotAllowedException("linked", defaultUserMessage, savingsId); } + + final boolean isWithdrawBalance = command.booleanPrimitiveValueOfParameterNamed(withdrawBalanceParamName); final Locale locale = command.extractLocale(); final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); - + final boolean isPostInterest = command.booleanPrimitiveValueOfParameterNamed(SavingsApiConstants.postInterestValidationOnClosure); + // postInterest(account,closedDate,flag); + if (isPostInterest) { + boolean postInterestOnClosingDate = false; + List savingTransactions = account.getTransactions(); + for (SavingsAccountTransaction savingTransaction : savingTransactions) { + if (closedDate.toDate().equals(savingTransaction.getDateOf())) if (savingTransaction.getTypeOf() == SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()) { + postInterestOnClosingDate = true; + } + } + if (postInterestOnClosingDate == false) { throw new PostInterestClosingDateException(); } + } + final Map changes = new LinkedHashMap<>(); if (isWithdrawBalance && account.getSummary().getAccountBalance(account.getCurrency()).isGreaterThanZero()) { + + + final BigDecimal transactionAmount = account.getSummary().getAccountBalance(); + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final boolean isAccountTransfer = false; @@ -685,8 +730,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue()); final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; + boolean postInterestAsOn = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); this.savingsAccountTransactionRepository.save(newTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -715,8 +761,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; + boolean postInterestAsOn =false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); this.savingsAccountTransactionRepository.save(withdrawtransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -756,8 +803,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } boolean isInterestTransfer = false; final MathContext mc = MathContext.DECIMAL64; + boolean postInterestAsOn = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); this.savingsAccountTransactionRepository.save(acceptTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -906,14 +954,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi account.waiveCharge(savingsAccountChargeId, user); boolean isInterestTransfer = false; + boolean postInterestAsOn = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { @@ -1038,14 +1088,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user); boolean isInterestTransfer = false; + boolean postInterestAsOn = false; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestAsOn); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth); + financialYearBeginningMonth, postInterestAsOn); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) == 1) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java index 7563f8524..e3f2f0c77 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularServiceImpl.java @@ -60,7 +60,9 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService { for (final SavingsAccount savingsAccount : savingsAccounts) { try { this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount); - this.savingsAccountWritePlatformService.postInterest(savingsAccount); + boolean postInterestAsOn = false; + LocalDate transactionDate = null; + this.savingsAccountWritePlatformService.postInterest(savingsAccount, postInterestAsOn, transactionDate); } catch (Exception e) { Throwable realCause = e; if (e.getCause() != null) { diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V321__boolean_field_As_Interest_PostedOn.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V321__boolean_field_As_Interest_PostedOn.sql new file mode 100644 index 000000000..19ba1da0c --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V321__boolean_field_As_Interest_PostedOn.sql @@ -0,0 +1,21 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. +-- + +ALTER TABLE m_savings_account_transaction +ADD is_manual boolean default 0 \ No newline at end of file