FINERACT-1330: Pay Charge & withdrawals is violating Overdraft conditions on current and past Transactions

This commit is contained in:
rrpawar96
2022-08-23 11:00:10 +02:00
committed by Arnold Gálovics
parent ed80540a95
commit d75835170a
5 changed files with 99 additions and 6 deletions
@@ -1521,6 +1521,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions, final boolean backdatedTxnsAllowedTill) {
List<SavingsAccountTransaction> transactionsSortedByDate = null;
BigDecimal transactionAmount = null;
if (backdatedTxnsAllowedTill) {
transactionsSortedByDate = retrieveSortedTransactions();
} else {
@@ -1535,6 +1536,9 @@ public class SavingsAccount extends AbstractPersistableCustom {
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
LocalDate lastSavingsDate = null;
for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
transactionAmount = transaction.getAmount();
if (transaction.isNotReversed() && transaction.isCredit()) {
runningBalance = runningBalance.plus(transaction.getAmount(this.currency));
} else if (transaction.isNotReversed() && transaction.isDebit()) {
@@ -1573,14 +1577,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
}
lastSavingsDate = transaction.transactionLocalDate();
}
BigDecimal withdrawalFee = null;
BigDecimal transactionAmount = null;
if (isOverdraft()) {
if (runningBalance.minus(minRequiredBalance).isLessThanZero()) {
throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, transactionAmount);
@@ -1588,6 +1589,18 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
}
public void validateAccountBalanceDoesNotViolateOverdraft(final List<SavingsAccountTransaction> savingsAccountTransaction,
final BigDecimal amountPaid) {
if (savingsAccountTransaction != null) {
SavingsAccountTransaction savingsAccountTransactionFirst = savingsAccountTransaction.get(0);
if (!this.allowOverdraft) {
if (savingsAccountTransactionFirst.getRunningBalance(this.currency).minus(amountPaid).isLessThanZero()) {
throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), null, amountPaid);
}
}
}
}
protected boolean isAccountLocked(final LocalDate transactionDate) {
boolean isLocked = false;
final boolean accountHasLockedInSetting = this.lockedInUntilDate != null;
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.savings.domain;
import java.time.LocalDate;
import java.util.List;
import javax.persistence.LockModeType;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Lock;
@@ -47,6 +48,9 @@ public interface SavingsAccountTransactionRepository
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<SavingsAccountTransaction> findBySavingsAccount(@Param("savingsAccount") SavingsAccount savingsAccount);
@Query("select sat from SavingsAccountTransaction sat where sat.refNo = :refNo")
List<SavingsAccountTransaction> findAllTransactionByRefNo(@Param("refNo") String refNo);
List<SavingsAccountTransaction> findByRefNo(@Param("refNo") String refNo);
@Query("select sat from SavingsAccountTransaction sat where sat.savingsAccount.id = :savingsId and sat.dateOf <= :transactionDate and sat.reversed=false")
List<SavingsAccountTransaction> findBySavingsAccountIdAndLessThanDateOfAndReversedIsFalse(@Param("savingsId") Long savingsId,
@Param("transactionDate") LocalDate transactionDate, Pageable pageable);
}
@@ -130,6 +130,9 @@ import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
@@ -718,7 +721,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
List<SavingsAccountTransaction> savingsAccountTransactions = null;
if (isBulk) {
String transactionRefNo = savingsAccountTransaction.getRefNo();
savingsAccountTransactions = this.savingsAccountTransactionRepository.findAllTransactionByRefNo(transactionRefNo);
savingsAccountTransactions = this.savingsAccountTransactionRepository.findByRefNo(transactionRefNo);
reversal = this.savingsAccountDomainService.handleReversal(account, savingsAccountTransactions, backdatedTxnsAllowedTill);
} else {
reversal = this.savingsAccountDomainService.handleReversal(account, Collections.singletonList(savingsAccountTransaction),
@@ -1471,6 +1474,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingAccountAssembler.assignSavingAccountHelpers(account);
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
Pageable sortedByDateAndIdDesc = PageRequest.of(0, 1, Sort.by("dateOf", "id").descending());
List<SavingsAccountTransaction> savingsAccountTransaction = this.savingsAccountTransactionRepository
.findBySavingsAccountIdAndLessThanDateOfAndReversedIsFalse(account.getId(), transactionDate, sortedByDateAndIdDesc);
account.validateAccountBalanceDoesNotViolateOverdraft(savingsAccountTransaction, amountPaid);
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
SavingsAccountTransaction chargeTransaction = account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user,
backdatedTxnsAllowedTill, null);
@@ -1488,6 +1498,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
@@ -2869,6 +2869,64 @@ public class ClientSavingsIntegrationTest {
assertEquals(balance.toString(), requestedTransaction.get("runningBalance").toString(), "Equality check for Balance");
}
@Test
public void testSavingsAccountChargesBackDate() {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
SavingsAccountHelper savingsAccountHelperValidationError = new SavingsAccountHelper(this.requestSpec,
new ResponseSpecBuilder().build());
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final String minBalanceForInterestCalculation = null;
final String minRequiredBalance = null;
final String enforceMinRequiredBalance = "false";
final boolean allowOverdraft = false;
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, "0", minBalanceForInterestCalculation,
minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
Assertions.assertNotNull(savingsProductID);
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);
savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
final Integer chargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
ChargesHelper.getSavingsSpecifiedDueDateJSON());
Assertions.assertNotNull(chargeId);
ArrayList<HashMap> charges = this.savingsAccountHelper.getSavingsCharges(savingsId);
Assertions.assertTrue(charges == null || charges.size() == 0);
this.savingsAccountHelper.depositToSavingsAccount(savingsId, "100", "05 March 2013", CommonConstants.RESPONSE_RESOURCE_ID);
this.savingsAccountHelper.depositToSavingsAccount(savingsId, "100", "07 March 2013", CommonConstants.RESPONSE_RESOURCE_ID);
final Integer savingsChargeId = this.savingsAccountHelper.addChargesForSavingsWithDueDate(savingsId, chargeId, "07 March 2013",
200);
ArrayList<HashMap> savingsAccountErrorData = (ArrayList<HashMap>) savingsAccountHelperValidationError
.payChargeToSavingsAccount(savingsId, savingsChargeId, "200", "06 March 2013", CommonConstants.RESPONSE_ERROR);
assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance",
savingsAccountErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
final Integer payChargeId = this.savingsAccountHelper.payCharge(savingsChargeId, savingsId, "200", "07 March 2013");
Assertions.assertNotNull(payChargeId);
}
@Test
public void testRunningBalanceAfterWithdrawalWithBackdateConfigurationOn() {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
@@ -263,6 +263,13 @@ public class SavingsAccountHelper {
getSavingsTransactionPaymentTypeJSON(amount, date, paymentType), jsonAttributeToGetback);
}
public Object payChargeToSavingsAccount(final Integer savingsID, final Integer chargeId, final String amount, String date,
String jsonAttributeToGetback) {
LOG.info("--------------------------------- PAY SAVINGS CHARGE --------------------------------");
return performSavingActions(createChargesURL("paycharge", savingsID, chargeId), getSavingsPayChargeJSON(amount, date),
jsonAttributeToGetback);
}
public Integer updateSavingsAccountTransaction(final Integer savingsId, final Integer transactionId, final String amount) {
LOG.info("\n--------------------------------- MODIFY SAVINGS TRANSACTION --------------------------------");
return (Integer) performSavingActions(createAdjustTransactionURL(MODIFY_TRASACTION_COMMAND, savingsId, transactionId),