This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 583d73e7c FINERACT-2040: CBR journal entries when loan is charged off
fix
583d73e7c is described below
commit 583d73e7c99a66322c0a2255a0c2e51f6210d47c
Author: taskain7 <[email protected]>
AuthorDate: Mon Jan 15 12:19:41 2024 +0100
FINERACT-2040: CBR journal entries when loan is charged off fix
---
.../AccrualBasedAccountingProcessorForLoan.java | 29 ++++++-
.../LoanTransactionReverseReplayTest.java | 98 ++++++++++++++++++++++
2 files changed, 125 insertions(+), 2 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
index 49aaf44a5..6cd56dff8 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -1074,10 +1074,17 @@ public class AccrualBasedAccountingProcessorForLoan
implements AccountingProcess
private void createJournalEntriesForCreditBalanceRefund(final LoanDTO
loanDTO, final LoanTransactionDTO loanTransactionDTO,
final Office office) {
+ final boolean isMarkedChargeOff = loanDTO.isMarkedAsChargeOff();
+ createJournalEntriesForLoanCreditBalanceRefund(loanDTO,
loanTransactionDTO, office, isMarkedChargeOff);
+ }
+
+ private void createJournalEntriesForLoanCreditBalanceRefund(final LoanDTO
loanDTO, final LoanTransactionDTO loanTransactionDTO,
+ final Office office, boolean isMarkedChargeOff) {
// loan properties
final Long loanProductId = loanDTO.getLoanProductId();
final Long loanId = loanDTO.getLoanId();
final String currencyCode = loanDTO.getCurrencyCode();
+ final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
// transaction properties
final String transactionId = loanTransactionDTO.getTransactionId();
@@ -1093,11 +1100,13 @@ public class AccrualBasedAccountingProcessorForLoan
implements AccountingProcess
if (principalAmount != null &&
principalAmount.compareTo(BigDecimal.ZERO) > 0) {
totalAmount = totalAmount.add(principalAmount);
- journalAmountHolders.add(new
JournalAmountHolder(AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
principalAmount));
+ journalAmountHolders
+ .add(new
JournalAmountHolder(determineAccrualAccount(isMarkedChargeOff, isMarkedFraud,
false), principalAmount));
}
if (overpaymentAmount != null &&
overpaymentAmount.compareTo(BigDecimal.ZERO) > 0) {
totalAmount = totalAmount.add(overpaymentAmount);
- journalAmountHolders.add(new
JournalAmountHolder(AccrualAccountsForLoan.OVERPAYMENT.getValue(),
overpaymentAmount));
+ journalAmountHolders
+ .add(new
JournalAmountHolder(determineAccrualAccount(isMarkedChargeOff, isMarkedFraud,
true), overpaymentAmount));
}
JournalAmountHolder totalAmountHolder = new
JournalAmountHolder(AccrualAccountsForLoan.FUND_SOURCE.getValue(), totalAmount);
@@ -1106,6 +1115,22 @@ public class AccrualBasedAccountingProcessorForLoan
implements AccountingProcess
}
+ private Integer determineAccrualAccount(boolean isMarkedChargeOff, boolean
isMarkedFraud, boolean isOverpayment) {
+ if (isMarkedChargeOff) {
+ if (isMarkedFraud) {
+ return
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue();
+ } else {
+ return AccrualAccountsForLoan.CHARGE_OFF_EXPENSE.getValue();
+ }
+ } else {
+ if (isOverpayment) {
+ return AccrualAccountsForLoan.OVERPAYMENT.getValue();
+ } else {
+ return AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue();
+ }
+ }
+ }
+
private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO,
LoanTransactionDTO loanTransactionDTO, Office office) {
// TODO Auto-generated method stub
// loan properties
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionReverseReplayTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionReverseReplayTest.java
index 309a1c101..cd1236113 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionReverseReplayTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionReverseReplayTest.java
@@ -19,6 +19,7 @@
package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.builder.RequestSpecBuilder;
@@ -29,6 +30,7 @@ import io.restassured.specification.ResponseSpecification;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@@ -43,11 +45,15 @@ import
org.apache.fineract.integrationtests.common.BusinessDateHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import
org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import org.apache.fineract.integrationtests.common.system.CodeHelper;
import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -64,6 +70,8 @@ public class LoanTransactionReverseReplayTest {
private ClientHelper clientHelper;
private LoanTransactionHelper loanTransactionHelper;
private InlineLoanCOBHelper inlineLoanCOBHelper;
+ private JournalEntryHelper journalEntryHelper;
+ private AccountHelper accountHelper;
@BeforeEach
public void setup() {
@@ -75,6 +83,8 @@ public class LoanTransactionReverseReplayTest {
loanTransactionHelper = new LoanTransactionHelper(requestSpec,
responseSpec);
clientHelper = new ClientHelper(requestSpec, responseSpec);
inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec,
responseSpec);
+ journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
+ accountHelper = new AccountHelper(requestSpec, responseSpec);
}
/**
@@ -219,6 +229,94 @@ public class LoanTransactionReverseReplayTest {
}
}
+ @Test
+ public void loanTransactionReverseReplayWithChargeOffAndCBR() {
+ try {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.TRUE);
+ businessDateHelper.updateBusinessDate(new
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+ .date("04 October
2022").dateFormat(DATE_PATTERN).locale("en"));
+
+ final Account assetAccount = accountHelper.createAssetAccount();
+ final Account assetFeeAndPenaltyAccount =
accountHelper.createAssetAccount();
+ final Account incomeAccount = accountHelper.createIncomeAccount();
+ final Account expenseAccount =
accountHelper.createExpenseAccount();
+ final Account overpaymentAccount =
accountHelper.createLiabilityAccount();
+
+ // Loan ExternalId
+ String loanExternalIdStr = UUID.randomUUID().toString();
+
+ // Client and Loan account creation
+
+ final Integer clientId =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
+ final String loanProductJSON = new
LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
+
.withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
+
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat()
+ .withAccountingRulePeriodicAccrual(new Account[] {
assetAccount, incomeAccount, expenseAccount, overpaymentAccount })
+
.withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0")
+
.withFeeAndPenaltyAssetAccount(assetFeeAndPenaltyAccount).build(null);
+ final Integer loanProductID =
loanTransactionHelper.getLoanProductId(loanProductJSON);
+
+ final Integer loanId = createLoanAccount(clientId,
loanProductID.longValue(), loanExternalIdStr);
+
+ // set loan as chargeoff
+ String randomText = Utils.randomStringGenerator("en", 5) +
Utils.randomNumberGenerator(6)
+ + Utils.randomStringGenerator("is", 5);
+ Integer chargeOffReasonId =
CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
+ String transactionExternalId = UUID.randomUUID().toString();
+ PostLoansLoanIdTransactionsResponse chargeOffResponse =
loanTransactionHelper.chargeOffLoan(loanId.longValue(),
+ new
PostLoansLoanIdTransactionsRequest().transactionDate("03 October
2022").locale("en").dateFormat("dd MMMM yyyy")
+
.externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
+
+ final PostLoansLoanIdTransactionsResponse repaymentTransaction =
loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
+ new
PostLoansLoanIdTransactionsRequest().dateFormat(DATE_PATTERN).transactionDate("03
October 2022").locale("en")
+ .transactionAmount(1500.0));
+
+ inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue()));
+
+ businessDateHelper.updateBusinessDate(new
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+ .date("05 October
2022").dateFormat(DATE_PATTERN).locale("en"));
+
+ PostLoansLoanIdTransactionsResponse cbrTransactionResponse =
loanTransactionHelper.makeCreditBalanceRefund(loanExternalIdStr,
+ new
PostLoansLoanIdTransactionsRequest().dateFormat(DATE_PATTERN).transactionDate("05
October 2022").locale("en")
+ .transactionAmount(500.0));
+
+ businessDateHelper.updateBusinessDate(new
BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
+ .date("06 October
2022").dateFormat(DATE_PATTERN).locale("en"));
+
+ loanTransactionHelper.reverseLoanTransaction(loanExternalIdStr,
repaymentTransaction.getResourceId(),
+ new
PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate("06 October
2022").locale("en")
+ .dateFormat(DATE_PATTERN).transactionAmount(0.0));
+
+ inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue()));
+ GetLoansLoanIdResponse loansLoanIdResponse =
loanTransactionHelper.getLoanDetails(loanExternalIdStr);
+ int lastTransactionIndex =
loansLoanIdResponse.getTransactions().size() - 1;
+ assertEquals(500.0,
loansLoanIdResponse.getTransactions().get(lastTransactionIndex).getAmount());
+
+ ArrayList<HashMap> journalEntriesForCBR = journalEntryHelper
+ .getJournalEntriesByTransactionId("L" +
cbrTransactionResponse.getResourceId().toString());
+ ArrayList<HashMap> journalEntriesForChargeOff = journalEntryHelper
+ .getJournalEntriesByTransactionId("L" +
chargeOffResponse.getResourceId().toString());
+ assertNotNull(journalEntriesForCBR);
+ assertNotNull(journalEntriesForChargeOff);
+
+ String expenseGlAccountCode = (String)
journalEntriesForChargeOff.get(0).get("glAccountCode");
+ String assetGlAccountCode = (String)
journalEntriesForChargeOff.get(1).get("glAccountCode");
+
+ List<HashMap> cbrExpenseJournalEntries =
journalEntriesForCBR.stream() //
+ .filter(journalEntry ->
expenseGlAccountCode.equals(journalEntry.get("glAccountCode"))) //
+ .toList();
+
+ List<HashMap> cbrAssetJournalEntries =
journalEntriesForCBR.stream() //
+ .filter(journalEntry ->
assetGlAccountCode.equals(journalEntry.get("glAccountCode"))) //
+ .toList();
+
+ assertEquals(2, cbrExpenseJournalEntries.size());
+ assertEquals(2, cbrAssetJournalEntries.size());
+ } finally {
+ GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec,
responseSpec, Boolean.FALSE);
+ }
+ }
+
private GetLoanProductsProductIdResponse createLoanProduct(final
LoanTransactionHelper loanTransactionHelper) {
final HashMap<String, Object> loanProductMap = new
LoanProductTestBuilder().build(null, null);
final Integer loanProductId =
loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));