From f3606bb2d1e08b52e56ceafe0e50f22d2742b7d7 Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Thu, 27 Mar 2025 12:04:20 +0200 Subject: [PATCH] FINERACT-2225: Save journal entries for accrual transaction created by charge adjustment --- .../resources/features/LoanCharge.feature | 25 +++++++++++++++++++ .../LoanAccrualsProcessingService.java | 3 ++- .../service/LoanAccrualEventService.java | 16 ++++++++++-- .../LoanAccrualsProcessingServiceImpl.java | 5 ++-- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature index a7a0ddf2c..dc5381664 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature @@ -2722,3 +2722,28 @@ Feature: LoanCharge | 01 March 2024 | Accrual | 1.16 | 0.0 | 1.16 | 0.0 | 0.0 | 0.0 | false | false | | 01 March 2024 | Charge-off | 107.14 | 100.0 | 2.14 | 5.0 | 0.0 | 0.0 | false | false | | 01 March 2024 | Charge Adjustment | 5.0 | 5.0 | 0.0 | 0.0 | 0.0 | 95.0 | false | false | + + @TestRailId:C3571 + Scenario: Charge adjustment on account with zero principal balance should not create accrual transactions without journal entries + When Admin sets the business date to "25 March 2025" + When Admin creates a client with random data + And Admin successfully creates a new customised Loan submitted on date: "25 March 2025", with Principal: "800", a loanTermFrequency: 1 months, and numberOfRepayments: 1 + And Admin successfully approves the loan on "25 March 2025" with "800" amount and expected disbursement date on "25 March 2025" + When Admin successfully disburse the loan on "25 March 2025" with "800" EUR transaction amount + And Customer makes "AUTOPAY" repayment on "25 March 2025" with 800 EUR transaction amount + And Admin adds an NSF fee because of payment bounce with "25 March 2025" transaction date + When Admin makes a charge adjustment for the last "LOAN_NSF_FEE" type charge which is due on "25 March 2025" with 10 EUR transaction amount and externalId "" + Then Loan Transactions tab has a "CHARGE_ADJUSTMENT" transaction with date "25 March 2025" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 10.0 | + | INCOME | 404007 | Fee Income | 10.0 | | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 25 March 2025 | Disbursement | 800.0 | 0.0 | 0.0 | 0.0 | 0.0 | 800.0 | + | 25 March 2025 | Repayment | 800.0 | 790.0 | 0.0 | 0.0 | 10.0 | 10.0 | + | 25 March 2025 | Charge Adjustment | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 25 March 2025 | Accrual | 10.0 | 0.0 | 0.0 | 0.0 | 10.0 | 0.0 | + Then Loan Transactions tab has a "ACCRUAL" transaction with date "25 March 2025" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112603 | Interest/Fee Receivable | 10.0 | | + | INCOME | 404007 | Fee Income | | 10.0 | \ No newline at end of file diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java index 297774a04..ef944e9fe 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingService.java @@ -24,6 +24,7 @@ import java.util.List; import org.apache.fineract.infrastructure.core.exception.MultiException; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.springframework.lang.NonNull; public interface LoanAccrualsProcessingService { @@ -41,7 +42,7 @@ public interface LoanAccrualsProcessingService { void processIncomePostingAndAccruals(@NotNull Loan loan); - void processAccrualsOnLoanClosure(@NotNull Loan loan); + void processAccrualsOnLoanClosure(@NonNull Loan loan, boolean addJournal); void processAccrualsOnLoanForeClosure(@NotNull Loan loan, @NotNull LocalDate foreClosureDate, @NotNull List newAccrualTransactions); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java index 9697a56d0..c48dd9f08 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualEventService.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.service; import jakarta.annotation.PostConstruct; +import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.event.business.BusinessEventListener; @@ -27,6 +28,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBu import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; @Slf4j @RequiredArgsConstructor @@ -50,7 +52,7 @@ public class LoanAccrualEventService { LoanStatus status = loan.getStatus(); if (status.isClosedObligationsMet() || status.isOverpaid()) { log.debug("Loan closure on accrual for loan {}", loan.getId()); - loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan); + loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan, false); loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan); } } @@ -64,9 +66,19 @@ public class LoanAccrualEventService { LoanStatus status = loan.getStatus(); if (status.isClosedObligationsMet() || status.isOverpaid()) { log.debug("Loan balance change on accrual for loan {}", loan.getId()); - loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan); + final boolean hasChargeAdjustment = hasChargeAdjustment(loan); + loanAccrualsProcessingService.processAccrualsOnLoanClosure(loan, hasChargeAdjustment); loanAccrualActivityProcessingService.processAccrualActivityForLoanClosure(loan); } } + + private boolean hasChargeAdjustment(final Loan loan) { + return loan.getLoanTransactions().stream() + .filter(transaction -> transaction.isChargeAdjustment() && transaction.isNotReversed()) + .anyMatch(chargeAdjustment -> chargeAdjustment.getLoanTransactionRelations().stream() + .anyMatch(relation -> relation.getRelationType() == LoanTransactionRelationTypeEnum.CHARGE_ADJUSTMENT + && relation.getFromTransaction() != null + && Objects.equals(relation.getFromTransaction().getId(), chargeAdjustment.getId()))); + } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index 2e566a4b5..13d325963 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -83,6 +83,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.lang.NonNull; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -284,9 +285,9 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing * method calculates accruals for loan on loan closure */ @Override - public void processAccrualsOnLoanClosure(@NotNull Loan loan) { + public void processAccrualsOnLoanClosure(@NonNull final Loan loan, final boolean addJournal) { // check and process accruals for loan WITHOUT interest recalculation details and compounding posted as income - addAccruals(loan, loan.getLastLoanRepaymentScheduleInstallment().getDueDate(), false, true, false); + addAccruals(loan, loan.getLastLoanRepaymentScheduleInstallment().getDueDate(), false, true, addJournal); if (isProgressiveAccrual(loan)) { return; }