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));

Reply via email to