diff --git a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDelinquencyPause.feature b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDelinquencyPause.feature index de997a39bf8..55870d6eba2 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDelinquencyPause.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/WorkingCapitalDelinquencyPause.feature @@ -200,6 +200,147 @@ Feature: Working Capital Delinquency Pause | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 3 | | 2 | 2026-03-13 | 2026-04-11 | 270.0 | 0.0 | 270.0 | null | null | null | + @TestRailId:C78810 + Scenario: Verify working capital loan delinquency pause - UC6.1: delinquency pause in the middle of first period with future pauses overlapping to a few periods + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | + | WCLP | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + Then Working capital loan approval was successful + And Working capital loan account has the correct data: + | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount | + | WCLP | 2026-01-01 | 2026-01-01 | Approved | 9000.0 | 9000.0 | 100000.0 | 18.0 | 0.0 | + When Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount + Then Working Capital loan status will be "ACTIVE" + And Verify Working Capital loan disbursement was successful on "01 January 2026" with "9000" EUR transaction amount + And Working capital loan account has the correct data: + | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount | + | WCLP | 2026-01-01 | 2026-01-01 | Active | 9000.0 | 9000.0 | 100000.0 | 18.0 | 0.0 | + When Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-01-30 | 270.0 | 0.0 | 270.0 | null | null | null | + When Admin sets the business date to "15 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Admin initiate a Working Capital loan delinquency pause with startDate "15 January 2026" and endDate "25 February 2026" + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + And Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | null | null | null | + And Admin initiate a Working Capital loan delinquency pause with startDate "16 April 2026" and endDate "23 April 2026" + When Admin sets the business date to "13 March 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Admin initiate a Working Capital loan delinquency pause with startDate "13 March 2026" and endDate "17 March 2026" + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + | PAUSE | 2026-04-16 | 2026-04-23 | + | PAUSE | 2026-03-13 | 2026-03-17 | + And Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 1 | + | 2 | 2026-03-13 | 2026-04-15 | 270.0 | 0.0 | 270.0 | null | null | null | + When Admin sets the business date to "20 March 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + | PAUSE | 2026-04-16 | 2026-04-23 | + | PAUSE | 2026-03-13 | 2026-03-17 | + And Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 8 | + | 2 | 2026-03-13 | 2026-04-15 | 270.0 | 0.0 | 270.0 | null | null | null | + When Admin sets the business date to "30 April 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 49 | + | 2 | 2026-03-13 | 2026-04-15 | 270.0 | 0.0 | 270.0 | false | 270.0 | 15 | + | 3 | 2026-04-16 | 2026-05-22 | 270.0 | 0.0 | 270.0 | null | null | null | + + @TestRailId:C78811 + Scenario: Verify working capital loan delinquency pause - UC6.2: delinquency pause in the middle of first period with pauses overlapping to a few periods + When Admin sets the business date to "01 January 2026" + And Admin creates a client with random data + And Admin creates a working capital loan with the following data: + | LoanProduct | submittedOnDate | expectedDisbursementDate | principalAmount | totalPayment | periodPaymentRate | discount | + | WCLP | 01 January 2026 | 01 January 2026 | 9000 | 100000 | 18 | 0 | + And Admin successfully approves the working capital loan on "01 January 2026" with "9000" amount and expected disbursement date on "01 January 2026" + Then Working capital loan approval was successful + And Working capital loan account has the correct data: + | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount | + | WCLP | 2026-01-01 | 2026-01-01 | Approved | 9000.0 | 9000.0 | 100000.0 | 18.0 | 0.0 | + When Admin successfully disburse the Working Capital loan on "01 January 2026" with "9000" EUR transaction amount + Then Working Capital loan status will be "ACTIVE" + And Verify Working Capital loan disbursement was successful on "01 January 2026" with "9000" EUR transaction amount + And Working capital loan account has the correct data: + | product.name | submittedOnDate | expectedDisbursementDate | status | principal | approvedPrincipal | totalPayment | periodPaymentRate | discount | + | WCLP | 2026-01-01 | 2026-01-01 | Active | 9000.0 | 9000.0 | 100000.0 | 18.0 | 0.0 | + When Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-01-30 | 270.0 | 0.0 | 270.0 | null | null | null | + When Admin sets the business date to "15 January 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + And Admin initiate a Working Capital loan delinquency pause with startDate "15 January 2026" and endDate "25 February 2026" + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + And Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | null | null | null | + When Admin sets the business date to "13 March 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + And Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 1 | + | 2 | 2026-03-13 | 2026-04-11 | 270.0 | 0.0 | 270.0 | null | null | null | + And Admin initiate a Working Capital loan delinquency pause with startDate "13 March 2026" and endDate "17 March 2026" + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + | PAUSE | 2026-03-13 | 2026-03-17 | + When Admin sets the business date to "20 March 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + | PAUSE | 2026-03-13 | 2026-03-17 | + And Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 8 | + | 2 | 2026-03-13 | 2026-04-15 | 270.0 | 0.0 | 270.0 | null | null | null | + When Admin sets the business date to "30 April 2026" + And Admin runs inline COB job for Working Capital Loan by loanId + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + | PAUSE | 2026-03-13 | 2026-03-17 | + Then Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 49 | + | 2 | 2026-03-13 | 2026-04-15 | 270.0 | 0.0 | 270.0 | false | 270.0 | 15 | + | 3 | 2026-04-16 | 2026-05-15 | 270.0 | 0.0 | 270.0 | null | null | null | + And Admin initiate a Working Capital loan delinquency pause with startDate "16 April 2026" and endDate "23 April 2026" + Then Working Capital loan delinquency action has the following data: + | action | startDate | endDate | + | PAUSE | 2026-01-15 | 2026-02-25 | + | PAUSE | 2026-03-13 | 2026-03-17 | + | PAUSE | 2026-04-16 | 2026-04-23 | + Then Working Capital loan delinquency range schedule has the following data: + | periodNumber | fromDate | toDate | expectedAmount | paidAmount | outstandingAmount | minPaymentCriteriaMet | delinquentAmount | delinquentDays | + | 1 | 2026-01-01 | 2026-03-12 | 270.0 | 0.0 | 270.0 | false | 270.0 | 49 | + | 2 | 2026-03-13 | 2026-04-15 | 270.0 | 0.0 | 270.0 | false | 270.0 | 15 | + | 3 | 2026-04-16 | 2026-05-22 | 270.0 | 0.0 | 270.0 | null | null | null | + @TestRailId:C74486 Scenario: Verify working capital loan delinquency pause - UC7: backdated delinquency pause to an already evaluated period results an error (Negative) When Admin sets the business date to "01 January 2026" diff --git a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyRangeScheduleServiceImpl.java b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyRangeScheduleServiceImpl.java index f785d45ada3..9119991a19c 100644 --- a/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyRangeScheduleServiceImpl.java +++ b/fineract-working-capital-loan/src/main/java/org/apache/fineract/portfolio/workingcapitalloan/service/WorkingCapitalLoanDelinquencyRangeScheduleServiceImpl.java @@ -112,6 +112,8 @@ public void generateNextPeriodIfNeeded(WorkingCapitalLoan loan, LocalDate busine final DelinquencyFrequencyType effectiveFreqType = latestReschedule.map(WorkingCapitalLoanDelinquencyAction::getFrequencyType) .orElse(rule.getFrequencyType()); + final List recordedPauses = findAllPauseActions(loan.getId()); + WorkingCapitalLoanDelinquencyRangeSchedule latestPeriod = latestPeriodOpt.get(); while (!latestPeriod.getToDate().isAfter(businessDate)) { final LocalDate newFromDate = latestPeriod.getToDate().plusDays(1); @@ -128,6 +130,8 @@ public void generateNextPeriodIfNeeded(WorkingCapitalLoan loan, LocalDate busine nextPeriod.setOutstandingAmount(expectedAmount); nextPeriod.setMinPaymentCriteriaMet(null); + applyRecordedPauses(nextPeriod, recordedPauses); + latestPeriod = loanDelinquencyRangeScheduleRepository.saveAndFlush(nextPeriod); log.debug("Generated next delinquency range schedule period {} for WC loan {}", nextPeriod.getPeriodNumber(), loan.getId()); } @@ -260,6 +264,27 @@ private Optional findLatestRescheduleAction return loanDelinquencyActionRepository.findTopByWorkingCapitalLoanIdAndActionOrderByIdDesc(loanId, DelinquencyAction.RESCHEDULE); } + private List findAllPauseActions(final Long loanId) { + return loanDelinquencyActionRepository.findByWorkingCapitalLoanIdOrderById(loanId).stream() + .filter(a -> DelinquencyAction.PAUSE.equals(a.getAction())).toList(); + } + + private void applyRecordedPauses(final WorkingCapitalLoanDelinquencyRangeSchedule period, + final List pauseActions) { + for (final WorkingCapitalLoanDelinquencyAction pause : pauseActions) { + final LocalDate pauseStart = pause.getStartDate(); + final LocalDate pauseEnd = pause.getEndDate(); + // Apply only if the pause overlaps this period's date range + if (pauseEnd.isAfter(period.getFromDate()) && !pauseStart.isAfter(period.getToDate())) { + final long pauseDays = ChronoUnit.DAYS.between(pauseStart, pauseEnd); + period.setToDate(period.getToDate().plusDays(pauseDays)); + if (period.getFromDate().isAfter(pauseStart)) { + period.setFromDate(period.getFromDate().plusDays(pauseDays)); + } + } + } + } + @Override public void rescheduleMinimumPayment(final WorkingCapitalLoan loan, final WorkingCapitalLoanDelinquencyAction rescheduleAction) { final LocalDate businessDate = DateUtils.getBusinessLocalDate();