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 journalEntriesForCBR = journalEntryHelper + .getJournalEntriesByTransactionId("L" + cbrTransactionResponse.getResourceId().toString()); + ArrayList 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 cbrExpenseJournalEntries = journalEntriesForCBR.stream() // + .filter(journalEntry -> expenseGlAccountCode.equals(journalEntry.get("glAccountCode"))) // + .toList(); + + List 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 loanProductMap = new LoanProductTestBuilder().build(null, null); final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));