From c3b462919fed1206bd8a6d3255d3591f84970f2c Mon Sep 17 00:00:00 2001 From: Dhaval Maniyar Date: Sat, 30 Jul 2022 22:08:59 +0530 Subject: [PATCH] FINERACT-1646-backdated-transaction-available-balance-fix --- .../savings/data/SavingsAccountData.java | 10 ++++++++ .../data/SavingsAccountSummaryData.java | 11 ++++++-- .../domain/SavingsAccountAssembler.java | 25 +++++++++++++++---- ...ingsAccountInterestPostingServiceImpl.java | 8 ++++-- ...SavingsAccountReadPlatformServiceImpl.java | 2 +- .../service/SavingsSchedularServiceImpl.java | 2 +- .../ClientSavingsIntegrationTest.java | 12 +++++---- 7 files changed, 54 insertions(+), 16 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java index 8f66a23cf..befcbe8de 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java @@ -116,6 +116,8 @@ public final class SavingsAccountData implements Serializable { private final BigDecimal minOverdraftForInterestCalculation; private transient List savingsAccountTransactionData = new ArrayList<>(); + private transient SavingsAccountTransactionData lastSavingsAccountTransaction; + private List datatables = null; // import field @@ -1104,4 +1106,12 @@ public final class SavingsAccountData implements Serializable { public Set getExistingReversedTransactionIds() { return this.existingReversedTransactionIds; } + + public SavingsAccountTransactionData getLastSavingsAccountTransaction() { + return lastSavingsAccountTransaction; + } + + public void setLastSavingsAccountTransaction(SavingsAccountTransactionData lastSavingsAccountTransaction) { + this.lastSavingsAccountTransaction = lastSavingsAccountTransaction; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java index a7eea4d47..9335f8df6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java @@ -223,16 +223,23 @@ public class SavingsAccountSummaryData implements Serializable { Money interestTotal = Money.of(currency, this.totalInterestPosted); Money withHoldTaxTotal = Money.of(currency, this.totalWithholdTax); Money overdraftInterestTotal = Money.of(currency, this.totalOverdraftInterestDerived); + this.totalDeposits = wrapper.calculateTotalDeposits(currency, savingsAccountTransactions); + this.totalWithdrawals = wrapper.calculateTotalWithdrawals(currency, savingsAccountTransactions); final HashMap map = updateRunningBalanceAndPivotDate(true, savingsAccountTransactions, interestTotal, overdraftInterestTotal, withHoldTaxTotal, currency); interestTotal = map.get("interestTotal"); withHoldTaxTotal = map.get("withHoldTax"); overdraftInterestTotal = map.get("overdraftInterestTotal"); + BigDecimal deltaInterest = interestTotal.minus(this.totalInterestPosted).getAmountDefaultedToNullIfZero(); + BigDecimal deltaWithholdTax = withHoldTaxTotal.minus(this.totalWithholdTax).getAmountDefaultedToNullIfZero(); + BigDecimal deltaOverdraftInterestDerived = overdraftInterestTotal.minus(this.totalOverdraftInterestDerived) + .getAmountDefaultedToNullIfZero(); this.totalInterestPosted = interestTotal.getAmountDefaultedToNullIfZero(); this.totalWithholdTax = withHoldTaxTotal.getAmountDefaultedToNullIfZero(); this.totalOverdraftInterestDerived = overdraftInterestTotal.getAmountDefaultedToNullIfZero(); - this.accountBalance = Money.of(currency, this.accountBalance).plus(this.totalInterestPosted).minus(this.totalWithholdTax) - .minus(this.totalOverdraftInterestDerived).getAmount(); + this.accountBalance = getRunningBalanceOnPivotDate(); + this.accountBalance = Money.of(currency, this.accountBalance).plus(Money.of(currency, this.totalDeposits)).plus(deltaInterest) + .minus(this.totalWithdrawals).minus(deltaWithholdTax).minus(deltaOverdraftInterestDerived).getAmount(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index fa234fcaa..e45626574 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -48,6 +48,7 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdraw import com.google.gson.JsonElement; import java.math.BigDecimal; import java.time.LocalDate; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; @@ -74,6 +75,7 @@ import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType; import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType; import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; +import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException; import org.apache.fineract.useradministration.domain.AppUser; import org.slf4j.Logger; @@ -381,12 +383,25 @@ public class SavingsAccountAssembler { public SavingsAccountData assembleSavings(final SavingsAccountData account) { // Update last running balance on account level - if (account.getTransactions() != null && account.getTransactions().size() != 0 - && account.getSummary().getInterestPostedTillDate() != null) { - account.getSummary().setRunningBalanceOnPivotDate(account.getTransactions().get(account.getTransactions().size() - 1) - .getRunningBalance(account.getCurrency()).getAmount()); - } + final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig(); + if (backdatedTxnsAllowedTill && account.getTransactions() != null && account.getSummary().getInterestPostedTillDate() != null) { + List removalList = new ArrayList<>(); + for (int i = 0; i < account.getTransactions().size(); i++) { + SavingsAccountTransactionData savingsAccountTransaction = account.getTransactions().get(i); + removalList.add(savingsAccountTransaction); + if ((savingsAccountTransaction.isInterestPostingAndNotReversed() + || savingsAccountTransaction.isOverdraftInterestAndNotReversed()) + && !savingsAccountTransaction.isReversalTransaction()) { + account.getSummary().setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance()); + account.setLastSavingsAccountTransaction(savingsAccountTransaction); + break; + } + } + account.getTransactions().removeAll(removalList); + } else { + account.getSummary().setRunningBalanceOnPivotDate(BigDecimal.ZERO); + } account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); return account; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java index dc2a87309..0ee01f2ba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java @@ -364,8 +364,12 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI } final List listOfTransactionsSorted = new ArrayList<>(); listOfTransactionsSorted.addAll(savingsAccountData.getTransactions()); - final SavingsAccountTransactionDataComparator transactionComparator = new SavingsAccountTransactionDataComparator(); - Collections.sort(listOfTransactionsSorted, transactionComparator); + if (!listOfTransactionsSorted.isEmpty()) { + final SavingsAccountTransactionDataComparator transactionComparator = new SavingsAccountTransactionDataComparator(); + Collections.sort(listOfTransactionsSorted, transactionComparator); + } else { + listOfTransactionsSorted.add(savingsAccountData.getLastSavingsAccountTransaction()); + } return listOfTransactionsSorted.get(0); } 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 b53a1bfed..31e8e3f0e 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 @@ -272,7 +272,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead + "where (CASE WHEN sa.interest_posted_till_date is not null THEN tr.transaction_date >= sa.interest_posted_till_date ELSE tr.transaction_date >= sa.activatedon_date END) "; } - sql = sql + " and (sa.interest_posted_till_date is null or sa.interest_posted_till_date < ? ) "; + sql = sql + " and (sa.interest_posted_till_date is null or sa.interest_posted_till_date <= ? ) "; sql = sql + " order by sa.id, tr.transaction_date, tr.created_date, tr.id"; List savingsAccountDataList = this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting, // NOSONAR 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 018fc5b98..5234e5932 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 @@ -76,7 +76,7 @@ public class SavingsSchedularServiceImpl implements SavingsSchedularService { Long maxSavingsIdInList = 0L; // initialise the executor service with fetched configurations final ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize); - final boolean backdatedTxnsAllowedTill = false; + final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig(); long start = System.currentTimeMillis(); 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 a98a796a2..3e778bba3 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 @@ -2972,18 +2972,20 @@ public class ClientSavingsIntegrationTest { configurationForBackdatedTransaction(); - String transactionDate = "1 July 2022"; + LocalDate transactionDate = LocalDate.now(Utils.getZoneIdOfTenant()).minusDays(10); + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy"); + String startDate = formatter.format(transactionDate); - final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, transactionDate); + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate); Assertions.assertNotNull(clientID); - final Integer savingsId = createSavingsAccountDailyPostingOverdraft(clientID, transactionDate); - this.savingsAccountHelper.depositToSavingsAccount(savingsId, "200", transactionDate, CommonConstants.RESPONSE_RESOURCE_ID); + final Integer savingsId = createSavingsAccountDailyPostingOverdraft(clientID, startDate); + this.savingsAccountHelper.depositToSavingsAccount(savingsId, "200", startDate, CommonConstants.RESPONSE_RESOURCE_ID); final String jobName = "Post Interest For Savings"; this.scheduleJobHelper.executeAndAwaitJob(jobName); final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build(); final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse); - List error = (List) validationErrorHelper.depositToSavingsAccount(savingsId, "3000", transactionDate, + List error = (List) validationErrorHelper.depositToSavingsAccount(savingsId, "300", startDate, CommonConstants.RESPONSE_ERROR); assertEquals("error.msg.savings.transaction.is.not.allowed", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));