This is an automated email from the ASF dual-hosted git repository.

arnold 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 f6d013c68d FINERACT-2421: Run inline COB on newly created loans that 
are behind
f6d013c68d is described below

commit f6d013c68d939acbf67d854a481a748f57a70577
Author: Adam Saghy <[email protected]>
AuthorDate: Thu Feb 5 12:40:55 2026 +0100

    FINERACT-2421: Run inline COB on newly created loans that are behind
---
 .../features/LoanAccrualTransaction.feature        | 30 ++++++++++++++++++++++
 .../loanaccount/domain/LoanRepository.java         |  4 +++
 .../RetrieveAllNonClosedLoanIdServiceImpl.java     |  5 ++++
 .../fineract/cob/loan/RetrieveLoanIdService.java   |  1 +
 .../jobs/filter/LoanCOBFilterHelper.java           | 13 ++++++----
 5 files changed, 48 insertions(+), 5 deletions(-)

diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature
index 27dce9c3cd..3c831869dd 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature
@@ -1961,3 +1961,33 @@ Feature: LoanAccrualTransaction
     When Loan Pay-off is made on "01 July 2024"
     Then Loan is closed with zero outstanding balance and it's all 
installments have obligations met
     When Admin set 
"LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_ACCRUAL_ACTIVITY_POSTING"
 loan product "MERCHANT_ISSUED_REFUND" transaction type to "REAMORTIZATION" 
future installment allocation rule
+
+  @TestRailId:C4627
+  Scenario: Verify accrual date matches charge creation date when repayment 
happens before COB run
+    When Admin sets the business date to "17 November 2025"
+    When Admin creates a client with random data
+    When Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                     | 
submitted on date | with Principal | ANNUAL interest rate % | interest type     
| interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_ACCRUAL_ACTIVITY | 17 
November 2025  | 100            | 0                      | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 30                | DAYS     
             | 30             | DAYS                   | 1                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "17 November 2025" with "100" 
amount and expected disbursement date on "17 November 2025"
+    When Admin successfully disburse the loan on "17 November 2025" with "100" 
EUR transaction amount
+    When Admin adds "LOAN_SNOOZE_FEE" due date charge with "17 November 2025" 
due date and 10 EUR transaction amount
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance |
+      | 17 November 2025 | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        |
+  #   --- Date changes to next day (post-midnight but before COB) ---
+    When Admin sets the business date to "18 November 2025"
+  #   --- Full repayment made before COB runs ---
+    When Admin creates new user with "NO_BYPASS_AUTOTEST" username, 
"NO_BYPASS_AUTOTEST_ROLE" role name and given permissions:
+      | REPAYMENT_LOAN |
+    And Created user makes "AUTOPAY" repayment on "18 November 2025" with 110 
EUR transaction amount
+    Then Loan status will be "CLOSED_OBLIGATIONS_MET"
+  #   --- Expected: Accrual transaction date should be 17 November 2025 
(charge creation date) ---
+    Then Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance |
+      | 17 November 2025 | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        |
+      | 18 November 2025 | Repayment        | 110.0  | 100.0     | 0.0      | 
10.0 | 0.0       | 0.0          |
+      | 17 November 2025 | Accrual          | 10.0   | 0.0       | 0.0      | 
10.0 | 0.0       | 0.0          |
+      | 18 November 2025 | Accrual Activity | 10.0   | 0.0       | 0.0      | 
10.0 | 0.0       | 0.0          |
+    Then LoanAccrualTransactionCreatedBusinessEvent is raised on "17 November 
2025"
+    Then LoanTransactionAccrualActivityPostBusinessEvent is raised on "18 
November 2025"
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
index 24515f20ec..0cb93c1038 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java
@@ -100,6 +100,7 @@ public interface LoanRepository extends JpaRepository<Loan, 
Long>, JpaSpecificat
 
     String 
FIND_ALL_LOANS_BY_LAST_CLOSED_BUSINESS_DATE_NOT_NULL_AND_MIN_AND_MAX_LOAN_ID_AND_STATUSES
 = "select loan.id from Loan loan where loan.id BETWEEN :minLoanId and 
:maxLoanId and loan.loanStatus in :loanStatuses and :cobBusinessDate = 
loan.lastClosedBusinessDate";
     String FIND_ALL_LOANS_BEHIND_BY_LOAN_IDS_AND_STATUSES = "select loan.id, 
loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and 
loan.loanStatus in :loanStatuses and loan.lastClosedBusinessDate < 
:cobBusinessDate";
+    String FIND_ALL_LOANS_BEHIND_ON_DISBURSEMENT_DATE = "select loan.id, 
loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and 
loan.loanStatus in :loanStatuses and loan.lastClosedBusinessDate IS NULL and 
loan.actualDisbursementDate = :cobBusinessDate";
 
     String FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE = "select loan.id, 
loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan 
loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = 
:cobBusinessDate";
 
@@ -275,4 +276,7 @@ public interface LoanRepository extends JpaRepository<Loan, 
Long>, JpaSpecificat
     @Query("select loan.loanRepaymentScheduleDetail.enableBuyDownFee from Loan 
loan where loan.id = :loanId")
     Boolean isEnabledBuyDownFee(@Param("loanId") Long loanId);
 
+    @Query(FIND_ALL_LOANS_BEHIND_ON_DISBURSEMENT_DATE)
+    List<COBIdAndLastClosedBusinessDate> 
findAllLoansBehindOnDisbursementDate(@Param("cobBusinessDate") LocalDate 
cobBusinessDate,
+            @Param("loanIds") List<Long> loanIds, @Param("loanStatuses") 
Collection<LoanStatus> loanStatuses);
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
index 7c81cc43da..cdc09d38d5 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java
@@ -79,6 +79,11 @@ public class RetrieveAllNonClosedLoanIdServiceImpl 
implements RetrieveLoanIdServ
         return 
loanRepository.findAllLoansBehindByLoanIdsAndStatuses(businessDate, loanIds, 
NON_CLOSED_LOAN_STATUSES);
     }
 
+    @Override
+    public List<COBIdAndLastClosedBusinessDate> 
retrieveLoanBehindOnDisbursementDate(LocalDate businessDate, List<Long> 
loanIds) {
+        return 
loanRepository.findAllLoansBehindOnDisbursementDate(businessDate, loanIds, 
NON_CLOSED_LOAN_STATUSES);
+    }
+
     @Override
     public List<COBIdAndLastClosedBusinessDate> 
retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds) {
         return 
loanRepository.findAllLoansBehindOrNullByLoanIdsAndStatuses(businessDate, 
loanIds, NON_CLOSED_LOAN_STATUSES);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
index 8604b01baa..590757fc74 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java
@@ -40,4 +40,5 @@ public interface RetrieveLoanIdService {
 
     List<COBIdAndExternalIdAndAccountNo> 
findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate 
cobBusinessDate);
 
+    List<COBIdAndLastClosedBusinessDate> 
retrieveLoanBehindOnDisbursementDate(LocalDate businessDateByType, List<Long> 
loanIds);
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java
index 5066277a08..d1d7948421 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java
@@ -40,6 +40,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.batch.domain.BatchRequest;
 import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
 import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
+import org.apache.fineract.cob.loan.LoanCOBConstant;
 import org.apache.fineract.cob.loan.RetrieveLoanIdService;
 import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
 import org.apache.fineract.cob.service.LoanAccountLockService;
@@ -85,8 +86,6 @@ public class LoanCOBFilterHelper implements InitializingBean {
     private static final Predicate<String> URL_FUNCTION = s -> 
LOAN_PATH_PATTERN.matcher(s).find()
             || LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
 
-    private static final String JOB_NAME = "INLINE_LOAN_COB";
-
     private Long getLoanId(boolean isGlim, String pathInfo) {
         if (!isGlim) {
             String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1");
@@ -197,8 +196,12 @@ public class LoanCOBFilterHelper implements 
InitializingBean {
     public boolean isLoanBehind(List<Long> loanIds) {
         List<COBIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates 
= new ArrayList<>();
         List<List<Long>> partitions = Lists.partition(loanIds, 
fineractProperties.getQuery().getInClauseParameterSizeLimit());
-        partitions.forEach(partition -> 
loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService
-                
.retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE),
 partition)));
+        partitions.forEach(partition -> {
+            loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService
+                    
.retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE),
 partition));
+            
loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService.retrieveLoanBehindOnDisbursementDate(
+                    
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), 
partition));
+        });
         return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates);
     }
 
@@ -269,7 +272,7 @@ public class LoanCOBFilterHelper implements 
InitializingBean {
     }
 
     public void executeInlineCob(List<Long> loanIds) {
-        inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME);
+        inlineLoanCOBExecutorService.execute(loanIds, 
LoanCOBConstant.INLINE_LOAN_COB_JOB_NAME);
     }
 
     @Override

Reply via email to