diff --git a/fineract-e2e-tests-runner/src/test/resources/features/COB.feature b/fineract-e2e-tests-runner/src/test/resources/features/COB.feature index 82fb15fd1dc..3b762515441 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/COB.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/COB.feature @@ -64,15 +64,15 @@ Feature: COBFeature Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-08-03" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | | + | RANGE_3 | 10 August 2023 | | | RANGE_1 | 04 August 2023 | 09 August 2023 | When Admin sets the business date to "12 August 2023" When Admin runs COB job Then Admin checks that last closed business date of loan is "09 August 2023" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | | - | RANGE_1 | 04 August 2023 | 09 August 2023 | + | RANGE_3 | 10 August 2023 | | + | RANGE_1 | 04 August 2023 | 10 August 2023 | Scenario: Verify that COB doesn’t touch CLOSED loans @@ -89,22 +89,22 @@ Feature: COBFeature Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-08-03" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | | - | RANGE_1 | 04 August 2023 | 09 August 2023 | + | RANGE_3 | 10 August 2023 | | + | RANGE_1 | 04 August 2023 | 10 August 2023 | And Customer makes "AUTOPAY" repayment on "10 August 2023" with 1000 EUR transaction amount Then Loan status will be "CLOSED_OBLIGATIONS_MET" Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | 10 August 2023 | - | RANGE_1 | 04 August 2023 | 09 August 2023 | + | RANGE_3 | 10 August 2023 | 10 August 2023 | + | RANGE_1 | 04 August 2023 | 10 August 2023 | When Admin sets the business date to "11 August 2023" When Admin runs COB job Then Admin checks that last closed business date of loan is "09 August 2023" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | 10 August 2023 | - | RANGE_1 | 04 August 2023 | 09 August 2023 | + | RANGE_3 | 10 August 2023 | 10 August 2023 | + | RANGE_1 | 04 August 2023 | 10 August 2023 | Scenario: Verify that COB doesn’t touch OVERPAID loans @@ -121,22 +121,22 @@ Feature: COBFeature Then Admin checks that delinquency range is: "RANGE_3" and has delinquentDate "2023-08-03" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | | - | RANGE_1 | 04 August 2023 | 09 August 2023 | + | RANGE_3 | 10 August 2023 | | + | RANGE_1 | 04 August 2023 | 10 August 2023 | And Customer makes "AUTOPAY" repayment on "10 August 2023" with 1200 EUR transaction amount Then Loan status will be "OVERPAID" Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has delinquentDate "" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | 10 August 2023 | - | RANGE_1 | 04 August 2023 | 09 August 2023 | + | RANGE_3 | 10 August 2023 | 10 August 2023 | + | RANGE_1 | 04 August 2023 | 10 August 2023 | When Admin sets the business date to "11 August 2023" When Admin runs COB job Then Admin checks that last closed business date of loan is "09 August 2023" Then Loan delinquency history has the following details: | Range (Classification) | Added on date | Lifted on date | - | RANGE_3 | 09 August 2023 | 10 August 2023 | - | RANGE_1 | 04 August 2023 | 09 August 2023 | + | RANGE_3 | 10 August 2023 | 10 August 2023 | + | RANGE_1 | 04 August 2023 | 10 August 2023 | @Skip Scenario: Verify that COB catch up runs properly on loan which is behind date because of locked with error diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature index 694b8362766..e72fb4c2955 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanDelinquency.feature @@ -625,10 +625,7 @@ Feature: LoanDelinquency | PAUSE | 15 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_3 | 250.0 | 04 October 2023 | 11 | 14 | - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 11 | 14 | Then Installment level delinquency event has correct data # --- Delinquency resume --- When Admin sets the business date to "25 October 2023" @@ -647,10 +644,7 @@ Feature: LoanDelinquency | RESUME | 25 October 2023 | | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_3 | 500.0 | 04 October 2023 | 12 | 25 | - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | + | NO_DELINQUENCY | 500.0 | 04 October 2023 | 12 | 25 | Then Installment level delinquency event has correct data @@ -674,10 +668,7 @@ Feature: LoanDelinquency | PAUSE | 15 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_3 | 250.0 | 04 October 2023 | 11 | 14 | - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 11 | 14 | Then Installment level delinquency event has correct data # --- Delinquency resume --- When Admin sets the business date to "25 October 2023" @@ -696,10 +687,7 @@ Feature: LoanDelinquency | RESUME | 25 October 2023 | | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_3 | 500.0 | 04 October 2023 | 12 | 25 | - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | + | NO_DELINQUENCY | 500.0 | 04 October 2023 | 12 | 25 | Then Installment level delinquency event has correct data # --- Delinquency runs --- When Admin sets the business date to "13 November 2023" @@ -755,11 +743,12 @@ Feature: LoanDelinquency | PAUSE | 14 November 2023 | 30 November 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_30 | 1000.0 | 04 October 2023 | 31 | 60 | + | RANGE_3 | 1000.0 | 04 October 2023 | 31 | 60 | Then Loan has the following INSTALLMENT level delinquency data: | rangeId | Range | Amount | - | 2 | RANGE_3 | 500.00 | - | 3 | RANGE_30 | 250.00 | + | 2 | RANGE_1 | 250.00 | + | 3 | RANGE_3 | 250.00 | + | 4 | RANGE_30 | 250.00 | # --- Delinquency runs again --- When Admin sets the business date to "01 December 2023" When Admin runs inline COB job for Loan @@ -802,11 +791,8 @@ Feature: LoanDelinquency | PAUSE | 06 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_1 | 250.0 | 04 October 2023 | 2 | 5 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 2 | 5 | # --- Grace period applied only on Loan level, not on installment level --- - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | Then Installment level delinquency event has correct data # --- Full repayment for late/due date installments --- When Admin sets the business date to "16 October 2023" @@ -818,11 +804,8 @@ Feature: LoanDelinquency | PAUSE | 06 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_1 | 250.0 | 04 October 2023 | 2 | 15 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 2 | 15 | # --- Grace period applied only on Loan level, not on installment level --- - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | And Customer makes "AUTOPAY" repayment on "16 October 2023" with 500 EUR transaction amount When Admin runs inline COB job for Loan Then Loan Delinquency pause periods has the following data: @@ -857,11 +840,8 @@ Feature: LoanDelinquency | PAUSE | 06 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_1 | 250.0 | 04 October 2023 | 2 | 5 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 2 | 5 | # --- Grace period applied only on Loan level, not on installment level --- - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | Then Installment level delinquency event has correct data # --- Full repayment for late/due date installments --- When Admin sets the business date to "16 October 2023" @@ -873,11 +853,8 @@ Feature: LoanDelinquency | PAUSE | 06 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_1 | 250.0 | 04 October 2023 | 2 | 15 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 2 | 15 | # --- Grace period applied only on Loan level, not on installment level --- - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | And Customer makes "AUTOPAY" repayment on "16 October 2023" with 150 EUR transaction amount When Admin runs inline COB job for Loan Then Loan Delinquency pause periods has the following data: @@ -915,11 +892,8 @@ Feature: LoanDelinquency | PAUSE | 06 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_1 | 250.0 | 04 October 2023 | 2 | 5 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 2 | 5 | # --- Grace period applied only on Loan level, not on installment level --- - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | Then Installment level delinquency event has correct data # --- Full repayment for late/due date installments --- When Admin sets the business date to "16 October 2023" @@ -931,11 +905,8 @@ Feature: LoanDelinquency | PAUSE | 06 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_1 | 250.0 | 04 October 2023 | 2 | 15 | + | NO_DELINQUENCY | 250.0 | 04 October 2023 | 2 | 15 | # --- Grace period applied only on Loan level, not on installment level --- - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 250.00 | And Customer makes "AUTOPAY" repayment on "16 October 2023" with 250 EUR transaction amount When Admin runs inline COB job for Loan Then Loan Delinquency pause periods has the following data: @@ -1034,10 +1005,7 @@ Feature: LoanDelinquency | true | 30 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_3 | 500.0 | 04 October 2023 | 21 | 29 | - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 500.00 | + | NO_DELINQUENCY | 500.0 | 04 October 2023 | 21 | 29 | When Admin sets the business date to "31 October 2023" Then Loan Delinquency pause periods has the following data: | active | pausePeriodStart | pausePeriodEnd | @@ -1045,10 +1013,7 @@ Feature: LoanDelinquency | false | 30 October 2023 | 30 October 2023 | Then Loan has the following LOAN level delinquency data: | classification | delinquentAmount | delinquentDate | delinquentDays | pastDueDays | - | RANGE_3 | 500.0 | 04 October 2023 | 22 | 30 | - Then Loan has the following INSTALLMENT level delinquency data: - | rangeId | Range | Amount | - | 2 | RANGE_3 | 500.00 | + | NO_DELINQUENCY | 500.0 | 04 October 2023 | 22 | 30 | Scenario: Verify that creating a loan with Advanced payment allocation with product no Advanced payment allocation set results an error diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java index 86a26eceb3f..7d157a49fc7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyChargeToOverdueLoansBusinessStep.java @@ -37,7 +37,9 @@ public class ApplyChargeToOverdueLoansBusinessStep implements LoanCOBBusinessSte public Loan execute(Loan loan) { final Collection overdueLoanScheduleDataList = loanReadPlatformService .retrieveAllOverdueInstallmentsForLoan(loan); - loanChargeWritePlatformService.applyOverdueChargesForLoan(loan.getId(), overdueLoanScheduleDataList); + if (!overdueLoanScheduleDataList.isEmpty()) { + loanChargeWritePlatformService.applyOverdueChargesForLoan(loan.getId(), overdueLoanScheduleDataList); + } return loan; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java index bd09da8fcb3..e04fdf5ba46 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/jobs/applychargetooverdueloaninstallment/ApplyChargeToOverdueLoanInstallmentTasklet.java @@ -68,8 +68,9 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon List exceptions = new ArrayList<>(); for (Map.Entry> entry : overdueScheduleData.entrySet()) { try { - loanChargeWritePlatformService.applyOverdueChargesForLoan(entry.getKey(), entry.getValue()); - + if (!entry.getValue().isEmpty()) { + loanChargeWritePlatformService.applyOverdueChargesForLoan(entry.getKey(), entry.getValue()); + } } catch (final PlatformApiDataValidationException e) { final List errors = e.getErrors(); for (final ApiParameterError error : errors) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index 12295fd0574..307394a090e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -757,11 +758,19 @@ public CommandProcessingResult adjustmentForLoanCharge(Long loanId, Long loanCha @Transactional @Override public void applyOverdueChargesForLoan(final Long loanId, Collection overdueLoanScheduleDataList) { + if (overdueLoanScheduleDataList.isEmpty()) { + return; + } Loan loan = this.loanAssembler.assembleFrom(loanId); if (loan.isChargedOff()) { log.warn("Adding charge to Loan: {} is not allowed. Loan Account is Charged-off", loanId); return; } + Optional optPenaltyCharge = loan.getLoanProduct().getCharges().stream() + .filter((e) -> ChargeTimeType.OVERDUE_INSTALLMENT.getValue().equals(e.getChargeTimeType()) && e.isLoanCharge()).findFirst(); + if (optPenaltyCharge.isEmpty()) { + return; + } final List existingTransactionIds = loan.findExistingTransactionIds(); final List existingReversedTransactionIds = loan.findExistingReversedTransactionIds(); boolean runInterestRecalculation = false; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 7b745a752ba..4e38213f2b9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -1642,6 +1642,15 @@ public Collection retrieveAllOverdueInstallmentsForLoan if (!loan.isOpen()) { return list; } + + Optional optPenaltyCharge = loan.getLoanProduct().getCharges().stream() + .filter((e) -> ChargeTimeType.OVERDUE_INSTALLMENT.getValue().equals(e.getChargeTimeType()) && e.isLoanCharge()).findFirst(); + + if (optPenaltyCharge.isEmpty()) { + return list; + } + final Charge penaltyCharge = optPenaltyCharge.get(); + final Long penaltyWaitPeriod = configurationDomainService.retrievePenaltyWaitPeriod(); final boolean backdatePenalties = configurationDomainService.isBackdatePenaltiesEnabled(); @@ -1657,16 +1666,9 @@ public Collection retrieveAllOverdueInstallmentsForLoan if (!backdatePenalties && !isDueToday) { continue; } - Optional penaltyCharge = loan.getLoanProduct().getCharges().stream() - .filter((e) -> ChargeTimeType.OVERDUE_INSTALLMENT.getValue().equals(e.getChargeTimeType()) && e.isLoanCharge()) - .findFirst(); - - if (penaltyCharge.isEmpty()) { - continue; - } - list.add(new OverdueLoanScheduleData(loan.getId(), penaltyCharge.get().getId(), - DateUtils.DEFAULT_DATE_FORMATTER.format(installment.getDueDate()), penaltyCharge.get().getAmount(), + list.add(new OverdueLoanScheduleData(loan.getId(), penaltyCharge.getId(), + DateUtils.DEFAULT_DATE_FORMATTER.format(installment.getDueDate()), penaltyCharge.getAmount(), DateUtils.DEFAULT_DATE_FORMAT, Locale.ENGLISH.toLanguageTag(), installment.getPrincipalOutstanding(loan.getCurrency()).getAmount(), installment.getInterestOutstanding(loan.getCurrency()).getAmount(), installment.getInstallmentNumber()));