From 36873911485e6f6dad0d7565d89d20527e886989 Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Mon, 21 Apr 2025 14:04:52 +0300 Subject: [PATCH] FINERACT-2221: Add immediate charge accrual post maturity for progressive loans --- .../resources/features/LoanCharge.feature | 50 +++++++++++++++++-- .../portfolio/loanaccount/domain/Loan.java | 5 ++ .../LoanChargeWritePlatformServiceImpl.java | 10 ++-- ...oanChargeWritePlatformServiceImplTest.java | 1 + .../LoanChargeProgressiveTest.java | 2 +- 5 files changed, 61 insertions(+), 7 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 9e33a7a89..2886bb482 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature @@ -2292,8 +2292,9 @@ Feature: LoanCharge Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 05 February 2024 | Accrual | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 0.0 | Given Global configuration "enable-immediate-charge-accrual-post-maturity" is disabled - Then LoanAccrualTransactionCreatedBusinessEvent is not raised on "05 February 2024" + Then LoanAccrualTransactionCreatedBusinessEvent is raised on "05 February 2024" @TestRailId:C3320 Scenario: Verify enhance the existing implementation to create accruals as part of Charge Creation post maturity with immediate charge accrual and zero interest rate @@ -2331,8 +2332,9 @@ Feature: LoanCharge Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 05 February 2024 | Accrual | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 0.0 | Given Global configuration "enable-immediate-charge-accrual-post-maturity" is disabled - Then LoanAccrualTransactionCreatedBusinessEvent is not raised on "05 February 2024" + Then LoanAccrualTransactionCreatedBusinessEvent is raised on "05 February 2024" @TestRailId:ะก3335 Scenario: Verify enhance the existing implementation to create accruals as part of Charge Creation post maturity with inline COB run and non-zero interest rate @@ -2454,7 +2456,8 @@ Feature: LoanCharge Then Loan Transactions tab has the following data: | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 January 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | - | 05 February 2024 | Accrual | 30.83 | 0.0 | 5.83 | 0.0 | 25.0 | 0.0 | + | 05 February 2024 | Accrual | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 0.0 | + | 05 February 2024 | Accrual | 5.83 | 0.0 | 5.83 | 0.0 | 0.0 | 0.0 | Given Global configuration "enable-immediate-charge-accrual-post-maturity" is disabled Then LoanAccrualTransactionCreatedBusinessEvent is raised on "05 February 2024" @@ -4750,3 +4753,44 @@ Feature: LoanCharge | 01 January 2024 | Repayment (at time of disbursement) | 1.0 | 0.0 | 0.0 | 1.0 | 0.0 | 100.0 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | true | false | | 01 March 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | true | + + @TestRailId:C3613 + Scenario: Verify immediate charge accrual post maturity for Progressive loans + Given Global configuration "enable-immediate-charge-accrual-post-maturity" is enabled + When Admin sets the business date to "25 February 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 | 25 February 2025 | 1000 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 1 | MONTHS | 1 | MONTHS | 1 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "25 February 2025" with "1000" amount and expected disbursement date on "25 February 2025" + When Admin successfully disburse the loan on "25 February 2025" with "1000" EUR transaction amount + Then Loan Repayment schedule has 1 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 25 February 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 28 | 25 March 2025 | | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 25 February 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "28 March 2025" + And Admin runs inline COB job for Loan + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "28 March 2025" due date and 25 EUR transaction amount + Then Loan Charges tab has the following data: + | Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding | + | Snooze fee | false | Specified due date | 28 March 2025 | Flat | 25.0 | 0.0 | 0.0 | 25.0 | + Then Loan Repayment schedule has 2 periods, with the following data for periods: + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 25 February 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 28 | 25 March 2025 | | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 2 | 3 | 28 March 2025 | | 0.0 | 0.0 | 0.0 | 25.0 | 0.0 | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 0.0 | 25.0 | 0.0 | 1025.0 | 0.0 | 0.0 | 0.0 | 1025.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 25 February 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + | 28 March 2025 | Accrual | 25.0 | 0.0 | 0.0 | 25.0 | 0.0 | 0.0 | + Then LoanAccrualTransactionCreatedBusinessEvent is raised on "28 March 2025" + Given Global configuration "enable-immediate-charge-accrual-post-maturity" is disabled \ No newline at end of file diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 5f970c3b8..fe77571ca 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -644,6 +644,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { if (isProgressiveSchedule()) { return null; } + + return createChargeAppliedTransaction(loanCharge, suppliedTransactionDate); + } + + public LoanTransaction createChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) { final Money chargeAmount = loanCharge.getAmount(getCurrency()); Money feeCharges = chargeAmount; Money penaltyCharges = Money.zero(getCurrency()); 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 03c39763f..c47752d87 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 @@ -1114,16 +1114,20 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo // we want to apply charge transactions only for those loans charges that are applied when a loan is active and // the loan product uses Upfront Accruals, or only when the loan are closed too, if ((loan.getStatus().isActive() && loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) - || loan.getStatus().isOverpaid() || loan.getStatus().isClosedObligationsMet() - || (configurationDomainService.isImmediateChargeAccrualPostMaturityEnabled() - && DateUtils.getBusinessLocalDate().isAfter(loan.getMaturityDate()))) { + || loan.getStatus().isOverpaid() || loan.getStatus().isClosedObligationsMet()) { final LoanTransaction applyLoanChargeTransaction = loan.handleChargeAppliedTransaction(loanCharge, null); if (applyLoanChargeTransaction != null) { this.loanTransactionRepository.saveAndFlush(applyLoanChargeTransaction); businessEventNotifierService .notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(applyLoanChargeTransaction)); } + } else if (configurationDomainService.isImmediateChargeAccrualPostMaturityEnabled() + && DateUtils.getBusinessLocalDate().isAfter(loan.getMaturityDate())) { + final LoanTransaction loanTransaction = loan.createChargeAppliedTransaction(loanCharge, null); + this.loanTransactionRepository.saveAndFlush(loanTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAccrualTransactionCreatedBusinessEvent(loanTransaction)); } + return DateUtils.isBeforeBusinessDate(loanCharge.getDueLocalDate()); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java index 8b3309da1..f53cabc10 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImplTest.java @@ -220,6 +220,7 @@ class LoanChargeWritePlatformServiceImplTest { when(configurationDomainService.isImmediateChargeAccrualPostMaturityEnabled()).thenReturn(isAccrualEnabled); when(loan.getMaturityDate()).thenReturn(maturityDate); when(loan.handleChargeAppliedTransaction(loanCharge, null)).thenReturn(loanTransaction); + when(loan.createChargeAppliedTransaction(loanCharge, null)).thenReturn(loanTransaction); if (isAccrualExpected) { when(loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()).thenReturn(true); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java index a217e704b..f84fdbcf3 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java @@ -79,7 +79,7 @@ public class LoanChargeProgressiveTest extends BaseLoanIntegrationTest { addLoanCharge(loanId, chargeResponse.getResourceId(), "03 October 2024", 20.0d); final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); Assertions.assertTrue( - loanDetails.getTransactions().stream().noneMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d))); + loanDetails.getTransactions().stream().anyMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d))); }); runAt("04 October 2024", () -> { globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY,