diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index bfd94a1ea..59bba2100 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -1593,13 +1593,18 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom public void validateAccountBalanceDoesNotViolateOverdraft(final List savingsAccountTransaction, final BigDecimal amountPaid) { - if (savingsAccountTransaction != null) { + if (savingsAccountTransaction != null && savingsAccountTransaction.size() > 0) { SavingsAccountTransaction savingsAccountTransactionFirst = savingsAccountTransaction.get(0); if (!this.allowOverdraft) { if (savingsAccountTransactionFirst.getRunningBalance(this.currency).minus(amountPaid).isLessThanZero()) { throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), null, amountPaid); } } + + } else { + if (!this.allowOverdraft) { + throw new InsufficientAccountBalanceException("transactionAmount", BigDecimal.ZERO, null, amountPaid); + } } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index 115ce14b4..15d6655c4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -3184,6 +3184,320 @@ public class ClientSavingsIntegrationTest { error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); } + @Test + public void testSavingsAccountWithdrawalWithoutPriorTransactionsWithoutOverdraft() { + final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US); + + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).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); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + // Assertions.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + + /*** + * Create savings product with zero opening balance and overdraft disabled + */ + final String zeroOpeningBalance = "0.0"; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, zeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assertions.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assertions.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); + + LocalDate todaysDate = Utils.getLocalDateOfTenant(); + todaysDate = todaysDate.minusMonths(1); + todaysDate = todaysDate.withDayOfMonth(1); + final String ACTIVATION_DATE = dateFormat.format(todaysDate); + final Integer lastDayOfMonth = todaysDate.lengthOfMonth(); + todaysDate = todaysDate.withDayOfMonth(lastDayOfMonth); + final String TRANSACTION_DATE = dateFormat.format(todaysDate); + + /*** + * 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); + + Float balance = Float.parseFloat(zeroOpeningBalance); + + /*** + * Attempt a withdrawal transaction, verify an exception will be thrown since there are no prior transactions to + * this transaction + */ + ArrayList savingsAccountErrorData = (ArrayList) validationErrorHelper.withdrawalFromSavingsAccount(savingsId, + WITHDRAW_AMOUNT, TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance", + savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); + } + + @Test + public void testWithdrawalWithPriorTransactionsWithOverdraft_AMT_GT_Balance() { + final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US); + + 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); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + // Assertions.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + + /*** + * Create savings product with zero opening balance and overdraft enabled + */ + final String nonZeroOpeningBalance = "500.00"; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = true; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, nonZeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assertions.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assertions.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); + + LocalDate todaysDate = Utils.getLocalDateOfTenant(); + todaysDate = todaysDate.minusMonths(1); + todaysDate = todaysDate.withDayOfMonth(1); + final String ACTIVATION_DATE = dateFormat.format(todaysDate); + final Integer lastDayOfMonth = todaysDate.lengthOfMonth(); + todaysDate = todaysDate.withDayOfMonth(lastDayOfMonth); + + /*** + * 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); + + Float balance = Float.parseFloat(nonZeroOpeningBalance); + + /*** + * Attempt a withdrawal transaction, verify an exception will be thrown since there are no prior transactions to + * this transaction + */ + /*** + * 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 -= Float.parseFloat(WITHDRAW_AMOUNT); + assertEquals(Float.parseFloat(WITHDRAW_AMOUNT), withdrawTransaction.get("amount"), "Verifying Withdrawal Amount"); + assertEquals(balance, withdrawTransaction.get("runningBalance"), "Verifying Balance after Withdrawal"); + + } + + @Test + public void testWithdrawalWithPriorTransactionsWithOverdraft_AMT_LT_Balance() { + final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US); + + 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); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + // Assertions.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + + /*** + * Create savings product with zero opening balance and overdraft enabled + */ + final String nonZeroOpeningBalance = "2000"; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, nonZeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assertions.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assertions.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); + + LocalDate todaysDate = Utils.getLocalDateOfTenant(); + todaysDate = todaysDate.minusMonths(1); + todaysDate = todaysDate.withDayOfMonth(1); + final String ACTIVATION_DATE = dateFormat.format(todaysDate); + + /*** + * 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); + + Float balance = Float.parseFloat(nonZeroOpeningBalance); + + /*** + * Attempt a withdrawal transaction, verify an exception will be thrown since there are no prior transactions to + * this transaction + */ + /*** + * 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 -= Float.parseFloat(WITHDRAW_AMOUNT); + assertEquals(Float.parseFloat(WITHDRAW_AMOUNT), withdrawTransaction.get("amount"), "Verifying Withdrawal Amount"); + assertEquals(balance, withdrawTransaction.get("runningBalance"), "Verifying Balance after Withdrawal"); + + } + + @Test + public void testWithdrawalWithPriorTransactionsWithOverdraft_AMT_EQ_Balance() { + final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US); + + 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); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + // Assertions.assertNotNull(clientID); + final String minBalanceForInterestCalculation = null; + + /*** + * Create savings product with zero opening balance and overdraft enabled + */ + final String nonZeroOpeningBalance = "1000"; + final String minRequiredBalance = null; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, nonZeroOpeningBalance, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assertions.assertNotNull(savingsProductID); + + /*** + * Apply for Savings account + */ + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsProductID); + + HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId, + ACCOUNT_TYPE_INDIVIDUAL); + Assertions.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); + + LocalDate todaysDate = Utils.getLocalDateOfTenant(); + todaysDate = todaysDate.minusMonths(1); + todaysDate = todaysDate.withDayOfMonth(1); + final String ACTIVATION_DATE = dateFormat.format(todaysDate); + + /*** + * 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); + + Float balance = Float.parseFloat(nonZeroOpeningBalance); + + /*** + * Attempt a withdrawal transaction, verify an exception will be thrown since there are no prior transactions to + * this transaction + */ + /*** + * 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 -= Float.parseFloat(WITHDRAW_AMOUNT); + assertEquals(Float.parseFloat(WITHDRAW_AMOUNT), withdrawTransaction.get("amount"), "Verifying Withdrawal Amount"); + assertEquals(balance, withdrawTransaction.get("runningBalance"), "Verifying Balance after Withdrawal"); + + } + private Integer createSavingsAccountDailyPostingOverdraft(final Integer clientID, final String startDate) { final Integer savingsProductID = createSavingsProductDailyPostingOverdraft(); Assertions.assertNotNull(savingsProductID);