Skip to content

Commit 13c667a

Browse files
committed
FINERACT-2421: Run inline COB on newly created loans that are behind
1 parent ef12056 commit 13c667a

File tree

5 files changed

+48
-5
lines changed

5 files changed

+48
-5
lines changed

fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualTransaction.feature

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,3 +1961,33 @@ Feature: LoanAccrualTransaction
19611961
When Loan Pay-off is made on "01 July 2024"
19621962
Then Loan is closed with zero outstanding balance and it's all installments have obligations met
19631963
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
1964+
1965+
@TestRailId:C4627
1966+
Scenario: Verify accrual date matches charge creation date when repayment happens before COB run
1967+
When Admin sets the business date to "17 November 2025"
1968+
When Admin creates a client with random data
1969+
When Admin creates a fully customized loan with the following data:
1970+
| 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 |
1971+
| 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 |
1972+
And Admin successfully approves the loan on "17 November 2025" with "100" amount and expected disbursement date on "17 November 2025"
1973+
When Admin successfully disburse the loan on "17 November 2025" with "100" EUR transaction amount
1974+
When Admin adds "LOAN_SNOOZE_FEE" due date charge with "17 November 2025" due date and 10 EUR transaction amount
1975+
Then Loan Transactions tab has the following data:
1976+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
1977+
| 17 November 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 |
1978+
# --- Date changes to next day (post-midnight but before COB) ---
1979+
When Admin sets the business date to "18 November 2025"
1980+
# --- Full repayment made before COB runs ---
1981+
When Admin creates new user with "NO_BYPASS_AUTOTEST" username, "NO_BYPASS_AUTOTEST_ROLE" role name and given permissions:
1982+
| REPAYMENT_LOAN |
1983+
And Created user makes "AUTOPAY" repayment on "18 November 2025" with 110 EUR transaction amount
1984+
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
1985+
# --- Expected: Accrual transaction date should be 17 November 2025 (charge creation date) ---
1986+
Then Loan Transactions tab has the following data:
1987+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance |
1988+
| 17 November 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 |
1989+
| 18 November 2025 | Repayment | 110.0 | 100.0 | 0.0 | 10.0 | 0.0 | 0.0 |
1990+
| 17 November 2025 | Accrual | 10.0 | 0.0 | 0.0 | 10.0 | 0.0 | 0.0 |
1991+
| 18 November 2025 | Accrual Activity | 10.0 | 0.0 | 0.0 | 10.0 | 0.0 | 0.0 |
1992+
Then LoanAccrualTransactionCreatedBusinessEvent is raised on "17 November 2025"
1993+
Then LoanTransactionAccrualActivityPostBusinessEvent is raised on "18 November 2025"

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
100100

101101
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";
102102
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";
103+
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";
103104

104105
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";
105106

@@ -275,4 +276,7 @@ List<Loan> findLoansForAddAccrual(@Param("accountingType") AccountingRuleType ac
275276
@Query("select loan.loanRepaymentScheduleDetail.enableBuyDownFee from Loan loan where loan.id = :loanId")
276277
Boolean isEnabledBuyDownFee(@Param("loanId") Long loanId);
277278

279+
@Query(FIND_ALL_LOANS_BEHIND_ON_DISBURSEMENT_DATE)
280+
List<COBIdAndLastClosedBusinessDate> findAllLoansBehindOnDisbursementDate(@Param("cobBusinessDate") LocalDate cobBusinessDate,
281+
@Param("loanIds") List<Long> loanIds, @Param("loanStatuses") Collection<LoanStatus> loanStatuses);
278282
}

fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveAllNonClosedLoanIdServiceImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ public List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDate(LocalDate
7979
return loanRepository.findAllLoansBehindByLoanIdsAndStatuses(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES);
8080
}
8181

82+
@Override
83+
public List<COBIdAndLastClosedBusinessDate> retrieveLoanBehindOnDisbursementDate(LocalDate businessDate, List<Long> loanIds) {
84+
return loanRepository.findAllLoansBehindOnDisbursementDate(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES);
85+
}
86+
8287
@Override
8388
public List<COBIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds) {
8489
return loanRepository.findAllLoansBehindOrNullByLoanIdsAndStatuses(businessDate, loanIds, NON_CLOSED_LOAN_STATUSES);

fineract-provider/src/main/java/org/apache/fineract/cob/loan/RetrieveLoanIdService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ public interface RetrieveLoanIdService {
4040

4141
List<COBIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
4242

43+
List<COBIdAndLastClosedBusinessDate> retrieveLoanBehindOnDisbursementDate(LocalDate businessDateByType, List<Long> loanIds);
4344
}

fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBFilterHelper.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.apache.fineract.batch.domain.BatchRequest;
4141
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
4242
import org.apache.fineract.cob.data.COBIdAndLastClosedBusinessDate;
43+
import org.apache.fineract.cob.loan.LoanCOBConstant;
4344
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
4445
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
4546
import org.apache.fineract.cob.service.LoanAccountLockService;
@@ -85,8 +86,6 @@ public class LoanCOBFilterHelper implements InitializingBean {
8586
private static final Predicate<String> URL_FUNCTION = s -> LOAN_PATH_PATTERN.matcher(s).find()
8687
|| LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
8788

88-
private static final String JOB_NAME = "INLINE_LOAN_COB";
89-
9089
private Long getLoanId(boolean isGlim, String pathInfo) {
9190
if (!isGlim) {
9291
String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1");
@@ -197,8 +196,12 @@ private boolean isLockOverrulable(List<Long> loanIds) {
197196
public boolean isLoanBehind(List<Long> loanIds) {
198197
List<COBIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
199198
List<List<Long>> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
200-
partitions.forEach(partition -> loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService
201-
.retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)));
199+
partitions.forEach(partition -> {
200+
loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService
201+
.retrieveLoanIdsBehindDate(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition));
202+
loanIdAndLastClosedBusinessDates.addAll(retrieveLoanIdService.retrieveLoanBehindOnDisbursementDate(
203+
ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition));
204+
});
202205
return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates);
203206
}
204207

@@ -269,7 +272,7 @@ private List<Long> getLoanIdList(String pathInfo) {
269272
}
270273

271274
public void executeInlineCob(List<Long> loanIds) {
272-
inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME);
275+
inlineLoanCOBExecutorService.execute(loanIds, LoanCOBConstant.INLINE_LOAN_COB_JOB_NAME);
273276
}
274277

275278
@Override

0 commit comments

Comments
 (0)