From de462082deececba7c8f16cbe9dbf437bcc514a6 Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Tue, 28 Jan 2025 15:38:27 +0200 Subject: [PATCH] FINERACT-2152: Interest pause and existing interest periods --- .../features/LoanInterestPause.feature | 167 ++++++++- .../ProgressiveLoanInterestScheduleModel.java | 69 ++-- .../loanschedule/data/RepaymentPeriod.java | 3 +- .../calc/ProgressiveEMICalculatorTest.java | 328 +++++++++++++++++- 4 files changed, 523 insertions(+), 44 deletions(-) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature index 74065dd8b72..30eccde2ddd 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature @@ -207,4 +207,169 @@ Feature: Loan interest pause on repayment schedule | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false | - | 01 March 2024 | Repayment | 17.01 | 16.62 | 0.39 | 0.0 | 0.0 | 66.95 | false | true | \ No newline at end of file + | 01 March 2024 | Repayment | 17.01 | 16.62 | 0.39 | 0.0 | 0.0 | 66.95 | false | true | + + Scenario: Interest accrual pause between two periods - UC2 + When Admin sets the business date to "2 January 2024" + And Admin creates a client with random data + And 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_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + Then Loan Repayment schedule has 6 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 | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" + And Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount + When Admin runs inline COB job for Loan + And Create interest pause period with start date "10 February 2024" and end date "10 March 2024" + Then Loan term variations has 1 variation, with the following data: + | Term Type Id | Term Type Code | Term Type Value | Applicable From | Decimal Value | Date Value | Is Specific To Installment | Is Processed | + | 11 | loanTermType.interestPause | interestPause | 10 February 2024 | 0.0 | 10 March 2024 | false | | + Then Loan Repayment schedule has 6 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 | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 66.69 | 16.88 | 0.13 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 49.96 | 16.73 | 0.28 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.24 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.42 | 16.82 | 0.19 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.42 | 0.1 | 0.0 | 0.0 | 16.52 | 0.0 | 0.0 | 0.0 | 16.52 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.57 | 0 | 0 | 101.57 | 0.0 | 0 | 0 | 101.57 | + When Admin sets the business date to "15 March 2024" + When Admin runs inline COB job for Loan + When Admin sets the business date to "5 April 2024" + When Admin runs inline COB job for Loan + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 02 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 January 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 07 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 10 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 11 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 13 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 January 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 15 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 16 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 17 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 19 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 20 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 January 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 24 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 25 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 29 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 January 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 January 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 02 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 05 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 06 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 07 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 08 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 09 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 11 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 12 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 13 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 14 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 16 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 17 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 19 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 20 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 21 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 22 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 23 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 24 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 25 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 26 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 29 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 30 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 April 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | + | 02 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 03 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 04 April 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + + Scenario: Early repayment and interest pause + When Admin sets the business date to "1 January 2024" + And Admin creates a client with random data + And 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_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + Then Loan Repayment schedule has 6 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 | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024" + And Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount + When Admin sets the business date to "14 January 2024" + And Customer makes "AUTOPAY" repayment on "14 January 2024" with 17.01 EUR transaction amount + Then Loan Repayment schedule has 6 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 | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 14 January 2024 | 83.23 | 16.77 | 0.24 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 66.99 | 16.24 | 0.77 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.37 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.65 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.84 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.84 | 0.1 | 0.0 | 0.0 | 16.94 | 0.0 | 0.0 | 0.0 | 16.94 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.99 | 0 | 0 | 101.99 | 17.01 | 17.01 | 0 | 84.98 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 14 January 2024 | Repayment | 17.01 | 16.77 | 0.24 | 0.0 | 0.0 | 83.23 | false | false | + And Create interest pause period with start date "15 January 2024" and end date "25 January 2024" + Then Loan Repayment schedule has 6 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 | + | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2024 | 14 January 2024 | 83.23 | 16.77 | 0.24 | 0.0 | 0.0 | 17.01 | 17.01 | 17.01 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 66.82 | 16.41 | 0.6 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2024 | | 50.2 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2024 | | 33.48 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 16.67 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 16.67 | 0.1 | 0.0 | 0.0 | 16.77 | 0.0 | 0.0 | 0.0 | 16.77 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.82 | 0 | 0 | 101.82 | 17.01 | 17.01 | 0 | 84.81 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | + | 14 January 2024 | Repayment | 17.01 | 16.77 | 0.24 | 0.0 | 0.0 | 83.23 | false | false | diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java index 8c9e880916e..513cc6ff6c5 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java @@ -26,6 +26,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Optional; @@ -166,18 +167,13 @@ public Optional updateInterestPeriodsForInterestPause(final Loc } final List affectedPeriods = repaymentPeriods.stream()// - .filter(period -> isPeriodInRange(period, fromDate, endDate))// + .filter(period -> period.getFromDate().isBefore(endDate) && !period.getDueDate().isBefore(fromDate))// .toList(); affectedPeriods.forEach(period -> insertInterestPausePeriods(period, fromDate, endDate)); return affectedPeriods.stream().findFirst(); } - private boolean isPeriodInRange(final RepaymentPeriod repaymentPeriod, final LocalDate fromDate, final LocalDate endDate) { - return DateUtils.isDateInRangeFromExclusiveToInclusive(fromDate, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()) - || DateUtils.isDateInRangeFromExclusiveToInclusive(endDate, repaymentPeriod.getFromDate(), repaymentPeriod.getDueDate()); - } - Optional findRepaymentPeriodForBalanceChange(final LocalDate balanceChangeDate) { if (balanceChangeDate == null) { return Optional.empty(); @@ -235,38 +231,41 @@ void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate repaymentPeriod.getInterestPeriods().add(interestPeriod); } - private void insertInterestPausePeriods(final RepaymentPeriod repaymentPeriod, final LocalDate fromDate, final LocalDate endDate) { - final InterestPeriod previousInterestPeriod = findPreviousInterestPeriod(repaymentPeriod, fromDate); - final LocalDate originalFromDate = previousInterestPeriod.getFromDate(); - final LocalDate originalDueDate = previousInterestPeriod.getDueDate(); - final LocalDate newDueDate = calculateNewDueDate(previousInterestPeriod, fromDate.minusDays(1)); - - if (fromDate.isAfter(originalFromDate) && endDate.isBefore(originalDueDate)) { - previousInterestPeriod.setDueDate(newDueDate); - final InterestPeriod interestPausePeriod = new InterestPeriod(repaymentPeriod, newDueDate, endDate, BigDecimal.ZERO, - BigDecimal.ZERO, zero, zero, zero, mc, true); - repaymentPeriod.getInterestPeriods().add(interestPausePeriod); - final InterestPeriod interestAfterPausePeriod = new InterestPeriod(repaymentPeriod, endDate, originalDueDate, BigDecimal.ZERO, - BigDecimal.ZERO, zero, zero, zero, mc, false); - repaymentPeriod.getInterestPeriods().add(interestAfterPausePeriod); - } + private void insertInterestPausePeriods(final RepaymentPeriod repaymentPeriod, final LocalDate pauseStart, final LocalDate pauseEnd) { + final LocalDate effectivePauseStart = pauseStart.minusDays(1); + final LocalDate finalPauseStart = effectivePauseStart.isBefore(repaymentPeriod.getFromDate()) ? repaymentPeriod.getFromDate() + : effectivePauseStart; + final LocalDate finalPauseEnd = pauseEnd.isAfter(repaymentPeriod.getDueDate()) ? repaymentPeriod.getDueDate() : pauseEnd; - if (fromDate.isAfter(originalFromDate) && endDate.isAfter(originalDueDate)) { - previousInterestPeriod.setDueDate(newDueDate); - final InterestPeriod interestPausePeriod = new InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO, - BigDecimal.ZERO, zero, zero, zero, mc, true); - repaymentPeriod.getInterestPeriods().add(interestPausePeriod); + final List newInterestPeriods = new ArrayList<>(); + for (final InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) { + if (interestPeriod.getDueDate().isBefore(finalPauseStart) || !interestPeriod.getFromDate().isBefore(finalPauseEnd)) { + newInterestPeriods.add(interestPeriod); + } else { + if (interestPeriod.getFromDate().isBefore(finalPauseStart)) { + final InterestPeriod leftSlice = new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), finalPauseStart, + interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), + interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(), + interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(), false); + newInterestPeriods.add(leftSlice); + } + if (interestPeriod.getDueDate().isAfter(finalPauseEnd)) { + final InterestPeriod rightSlice = new InterestPeriod(repaymentPeriod, finalPauseEnd, interestPeriod.getDueDate(), + interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), + interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(), + interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(), false); + newInterestPeriods.add(rightSlice); + } + } } - if (fromDate.isBefore(originalFromDate) && endDate.isBefore(originalDueDate)) { - repaymentPeriod.getInterestPeriods().clear(); - final InterestPeriod interestPausePeriod = new InterestPeriod(repaymentPeriod, newDueDate, originalDueDate, BigDecimal.ZERO, - BigDecimal.ZERO, zero, zero, zero, mc, true); - repaymentPeriod.getInterestPeriods().add(interestPausePeriod); - InterestPeriod interestAfterPausePeriod = new InterestPeriod(repaymentPeriod, endDate, originalDueDate, BigDecimal.ZERO, - BigDecimal.ZERO, zero, zero, zero, mc, false); - repaymentPeriod.getInterestPeriods().add(interestAfterPausePeriod); - } + final InterestPeriod pausedSlice = new InterestPeriod(repaymentPeriod, finalPauseStart, finalPauseEnd, BigDecimal.ZERO, + BigDecimal.ZERO, zero, zero, zero, mc, true); + newInterestPeriods.add(pausedSlice); + + newInterestPeriods.sort(Comparator.comparing(InterestPeriod::getFromDate)); + + repaymentPeriod.setInterestPeriods(newInterestPeriods); } private InterestPeriod findPreviousInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate date) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java index 9edba49c06d..139ef71abc2 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java @@ -45,7 +45,8 @@ public class RepaymentPeriod { @Getter private final LocalDate dueDate; @Getter - private final List interestPeriods; + @Setter + private List interestPeriods; @Setter @Getter private Money emi; diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java index 694cb11c9e2..c3bb6bcaf86 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java @@ -1411,13 +1411,327 @@ public void test_interestPauseBetweenTwoPeriodsAmt100_dayInYears360_daysInMonth3 checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 16.43, 83.57); checkPeriod(interestSchedule, 1, 0, 17.01, 0.001609195402, 0.13, 0.13, 16.88, 66.69); checkPeriod(interestSchedule, 1, 1, 17.01, 0.004224137931, 0.0, 0.13, 16.88, 66.69); - checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.0, 0.28, 16.73, 49.96); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.001693548387, 0.0, 0.28, 16.73, 49.96); checkPeriod(interestSchedule, 2, 1, 17.01, 0.004139784946, 0.28, 0.28, 16.73, 49.96); checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.24); checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 0.19, 16.82, 16.42); checkPeriod(interestSchedule, 5, 0, 16.52, 0.005833333333, 0.1, 0.1, 16.42, 0.0); } + @Test + public void test_interestPauseFirstDayOfMonthAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 5)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.00564516129, 0.56, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 2, 17.01, 0.000188172043, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.000804597701, 0.0, 0.42, 16.59, 66.96); + checkPeriod(interestSchedule, 1, 1, 17.01, 0.005028735632, 0.42, 0.42, 16.59, 66.96); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 0.39, 16.62, 50.34); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.62); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.2, 0.2, 16.81, 16.81); + checkPeriod(interestSchedule, 5, 0, 16.91, 0.005833333333, 0.1, 0.1, 16.81, 0.0); + } + + @Test + public void test_interestPauseLastDayOfMonthAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 1, 10), LocalDate.of(2024, 1, 31)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.17, 16.84, 83.16); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.001505376344, 0.15, 0.17, 16.84, 83.16); + checkPeriod(interestSchedule, 0, 2, 17.01, 0.004139784946, 0.0, 0.17, 16.84, 83.16); + checkPeriod(interestSchedule, 0, 3, 17.01, 0.000188172043, 0.02, 0.17, 16.84, 83.16); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.49, 0.49, 16.52, 66.64); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 0.39, 16.62, 50.02); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.3); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 0.19, 16.82, 16.48); + checkPeriod(interestSchedule, 5, 0, 16.58, 0.005833333333, 0.1, 0.1, 16.48, 0.0); + } + + @Test + public void test_interestPauseWholeMonthAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 29)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.00564516129, 0.56, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 2, 17.01, 0.000188172043, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.005632183908, 0.0, 0.02, 16.99, 66.56); + checkPeriod(interestSchedule, 1, 1, 17.01, 0.000201149425, 0.02, 0.02, 16.99, 66.56); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 0.39, 16.62, 49.94); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.22); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 0.19, 16.82, 16.4); + checkPeriod(interestSchedule, 5, 0, 16.5, 0.005833333333, 0.1, 0.1, 16.4, 0.0); + } + + @Test + public void test_interestPauseTwoWholeMonthsAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 31)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.00564516129, 0.56, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 2, 17.01, 0.000188172043, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.0, 0.0, 17.01, 66.54); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.00564516129, 0.0, 0.01, 17.0, 49.54); + checkPeriod(interestSchedule, 2, 1, 17.01, 0.000188172043, 0.01, 0.01, 17.0, 49.54); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 32.82); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 0.19, 16.82, 16.0); + checkPeriod(interestSchedule, 5, 0, 16.09, 0.005833333333, 0.09, 0.09, 16.0, 0.0); + } + + @Test + public void test_interestPauseAndExistedMultipleInterestPeriodsAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 2, 11), BigDecimal.valueOf(7.0)); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 2, 21), BigDecimal.valueOf(7.0)); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 2, 5), LocalDate.of(2024, 2, 15)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 0.58, 16.43, 83.57); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.000603448276, 0.05, 0.3, 16.71, 66.86); + checkPeriod(interestSchedule, 1, 1, 17.01, 0.002212643678, 0.0, 0.3, 16.71, 66.86); + checkPeriod(interestSchedule, 1, 2, 17.01, 0.001005747126, 0.08, 0.3, 16.71, 66.86); + checkPeriod(interestSchedule, 1, 3, 17.01, 0.002011494253, 0.17, 0.3, 16.71, 66.86); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 0.39, 16.62, 50.24); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.52); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.2, 0.2, 16.81, 16.71); + checkPeriod(interestSchedule, 5, 0, 16.81, 0.005833333333, 0.1, 0.1, 16.71, 0.0); + } + + @Test + public void test_interestPauseBetweenRepaymentPeriodsAndExistedMultipleInterestPeriods_Amt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 2, 6), BigDecimal.valueOf(7.0)); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 2, 21), BigDecimal.valueOf(7.0)); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 3, 6), BigDecimal.valueOf(7.0)); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 3, 21), BigDecimal.valueOf(7.0)); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 2, 10), LocalDate.of(2024, 3, 10)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.58, 16.43, 83.57); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.005833333333, 0.58, 0.58, 16.43, 83.57); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.000804597701, 0.07, 0.14, 16.87, 66.7); + checkPeriod(interestSchedule, 1, 1, 17.01, 0.000804597701, 0.07, 0.14, 16.87, 66.7); + checkPeriod(interestSchedule, 1, 2, 17.01, 0.004224137931, 0.0, 0.14, 16.87, 66.7); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.001693548387, 0.0, 0.28, 16.73, 49.97); + checkPeriod(interestSchedule, 2, 1, 17.01, 0.001881720430, 0.13, 0.28, 16.73, 49.97); + checkPeriod(interestSchedule, 2, 1, 17.01, 0.00188172043, 0.13, 0.28, 16.73, 49.97); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.25); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 0.19, 16.82, 16.43); + checkPeriod(interestSchedule, 5, 0, 16.53, 0.005833333333, 0.1, 0.1, 16.43, 0.0); + } + + @Test + public void test_interestPauseOnFirstDayOfMonthAndExistedMultipleInterestPeriods_Amt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 2, 1), BigDecimal.valueOf(7.0)); + emiCalculator.changeInterestRate(interestSchedule, LocalDate.of(2024, 2, 6), BigDecimal.valueOf(7.0)); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 2, 5)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.00564516129, 0.56, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 2, 17.01, 0.000188172043, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.000804597701, 0.0, 0.42, 16.59, 66.96); + checkPeriod(interestSchedule, 1, 1, 17.01, 0.005028735632, 0.42, 0.42, 16.59, 66.96); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 0.39, 16.62, 50.34); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.62); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.2, 0.2, 16.81, 16.81); + checkPeriod(interestSchedule, 5, 0, 16.91, 0.005833333333, 0.1, 0.1, 16.81, 0.0); + } + + @Test + public void test_interestPauseBorderedByPeriod_Amt100_dayInYears360_daysInMonth30_repayEvery1Month() { + final List expectedRepaymentPeriods = List.of( + repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)), + repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)), + repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)), + repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)), + repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)), + repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1))); + + final BigDecimal interestRate = BigDecimal.valueOf(7.0); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(interestRate); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, loanTermVariations, installmentAmountInMultiplesOf, mc); + + final Money disbursedAmount = toMoney(100.0); + emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); + emiCalculator.applyInterestPause(interestSchedule, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)); + + checkPeriod(interestSchedule, 0, 0, 17.01, 0.0, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 1, 17.01, 0.00564516129, 0.56, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 0, 2, 17.01, 0.000188172043, 0.0, 0.56, 16.45, 83.55); + checkPeriod(interestSchedule, 1, 0, 17.01, 0.005833333333, 0.0, 0.0, 17.01, 66.54); + checkPeriod(interestSchedule, 2, 0, 17.01, 0.005833333333, 0.39, 0.39, 16.62, 49.92); + checkPeriod(interestSchedule, 3, 0, 17.01, 0.005833333333, 0.29, 0.29, 16.72, 33.2); + checkPeriod(interestSchedule, 4, 0, 17.01, 0.005833333333, 0.19, 0.19, 16.82, 16.38); + checkPeriod(interestSchedule, 5, 0, 16.48, 0.005833333333, 0.1, 0.1, 16.38, 0.0); + } + @Test public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() { final List expectedRepaymentPeriods = List.of( @@ -1600,12 +1914,12 @@ private static void checkPeriod(final ProgressiveLoanInterestScheduleModel inter final RepaymentPeriod repaymentPeriod = interestScheduleModel.repaymentPeriods().get(repaymentIdx); final InterestPeriod interestPeriod = repaymentPeriod.getInterestPeriods().get(interestIdx); - Assertions.assertEquals(emiValue, toDouble(repaymentPeriod.getEmi())); - Assertions.assertEquals(rateFactor, toDouble(applyMathContext(interestPeriod.getRateFactor()))); - Assertions.assertEquals(interestDue, toDouble(interestPeriod.getCalculatedDueInterest())); - Assertions.assertEquals(interestDueCumulated, toDouble(repaymentPeriod.getDueInterest())); - Assertions.assertEquals(principalDue, toDouble(repaymentPeriod.getDuePrincipal())); - Assertions.assertEquals(remaingBalance, toDouble(repaymentPeriod.getOutstandingLoanBalance())); + Assertions.assertAll("Check period", () -> Assertions.assertEquals(emiValue, toDouble(repaymentPeriod.getEmi())), + () -> Assertions.assertEquals(rateFactor, toDouble(applyMathContext(interestPeriod.getRateFactor()))), + () -> Assertions.assertEquals(interestDue, toDouble(interestPeriod.getCalculatedDueInterest())), + () -> Assertions.assertEquals(interestDueCumulated, toDouble(repaymentPeriod.getDueInterest())), + () -> Assertions.assertEquals(principalDue, toDouble(repaymentPeriod.getDuePrincipal())), + () -> Assertions.assertEquals(remaingBalance, toDouble(repaymentPeriod.getOutstandingLoanBalance()))); } private static double toDouble(final Money value) {