From 14589e26006f0be5ce4f6159317305e91873336b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soma=20S=C3=B6r=C3=B6s?= Date: Tue, 12 Aug 2025 15:13:25 +0200 Subject: [PATCH] FINERACT-1981: Progressive Loan schedule handling - Reschedule installment (Extend repayment period) --- .../src/test/resources/features/Loan.feature | 312 ++++++++++++++++-- ...edPaymentScheduleTransactionProcessor.java | 80 +++-- .../impl/ChangeOperation.java | 22 +- ...iveLoanRescheduleRequestDataValidator.java | 83 +++-- .../loanproduct/calc/EMICalculator.java | 3 + .../calc/ProgressiveEMICalculator.java | 91 ++++- .../calc/data/EmiChangeOperation.java | 25 +- .../ProgressiveLoanInterestScheduleModel.java | 10 + .../calc/ProgressiveEMICalculatorTest.java | 219 ++++++++++++ ...heduleRequestWritePlatformServiceImpl.java | 2 +- 10 files changed, 752 insertions(+), 95 deletions(-) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature index b5210d39b..7b5fbb73f 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature @@ -5809,8 +5809,7 @@ Feature: Loan | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 February 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | -# TODO unskip and check when PS-1654 is done - @Skip @TestRailId:C3127 + @TestRailId:C3127 Scenario: Verify fixed length loan account Loan schedule - UC9: Reschedule by extra terms a loan account with fixed length of 40 days When Admin sets the business date to "01 February 2024" When Admin creates a client with random data @@ -5843,9 +5842,9 @@ Feature: Loan | 1 | 0 | 01 February 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | | 2 | 15 | 16 February 2024 | | 600.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | | 3 | 15 | 02 March 2024 | | 450.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 4 | 15 | 17 March 2024 | | 300.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 4 | 10 | 12 March 2024 | | 300.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | | 5 | 15 | 01 April 2024 | | 150.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | - | 6 | 10 | 11 April 2024 | | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | + | 6 | 15 | 16 April 2024 | | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.0 | 0.0 | 0.0 | 0.0 | 150.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 | 1000.0 | 0.0 | 0.0 | 0 | 1000.0 | @@ -7809,20 +7808,20 @@ Feature: Loan Then Loan's all installments have obligations met @TestRailId:C3963 - Scenario: Verify Progressive Loan reschedule by extending repayment period: Basic scenario without downpayment + Scenario: Verify Progressive Loan reschedule by extending repayment period: Basic scenario without downpayment, flat interest type When Admin sets the business date to "01 June 2024" 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | 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 | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount Then Loan Repayment schedule has 3 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 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 666.67 | 333.33 | 0.0 | 0.0 | 0.0 | 333.33 | 0.0 | 0.0 | 0.0 | 333.33 | - | 2 | 31 | 01 August 2024 | | 333.34 | 333.33 | 0.0 | 0.0 | 0.0 | 333.33 | 0.0 | 0.0 | 0.0 | 333.33 | - | 3 | 31 | 01 September 2024 | | 0.0 | 333.34 | 0.0 | 0.0 | 0.0 | 333.34 | 0.0 | 0.0 | 0.0 | 333.34 | + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 667.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 2 | 31 | 01 August 2024 | | 334.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.0 | 0.0 | 0.0 | 0.0 | 334.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 | @@ -7836,10 +7835,10 @@ Feature: Loan Then Loan Repayment schedule has 4 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 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 2 | 31 | 01 August 2024 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 3 | 31 | 01 September 2024 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 4 | 30 | 01 October 2024 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 1 | 30 | 01 July 2024 | | 667.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | 0.0 | 0.0 | 0.0 | 333.0 | + | 2 | 31 | 01 August 2024 | | 445.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | + | 3 | 31 | 01 September 2024 | | 223.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | 0.0 | 0.0 | 0.0 | 222.0 | + | 4 | 30 | 01 October 2024 | | 0.0 | 223.0 | 0.0 | 0.0 | 0.0 | 223.0 | 0.0 | 0.0 | 0.0 | 223.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 | @@ -7848,7 +7847,7 @@ Feature: Loan | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | @TestRailId:C3964 - Scenario: Verify Progressive Loan reschedule by extending repayment period with downpayment installment + Scenario: Verify Progressive Loan reschedule by extending repayment period with downpayment installment, flat interest type When Admin sets the business date to "01 June 2024" When Admin creates a client with random data When Admin creates a fully customized loan with the following data: @@ -7872,7 +7871,7 @@ Feature: Loan When Admin sets the business date to "01 July 2024" When Admin creates and approves Loan reschedule with the following data: | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 August 2024 | 01 July 2024 | | | | 1 | | + | 01 July 2024 | 01 July 2024 | | | | 1 | | Then Loan Repayment schedule has 5 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 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | @@ -7889,12 +7888,12 @@ Feature: Loan | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | @TestRailId:C3965 - Scenario: Verify Progressive Loan reschedule by extending repayment period - multiple extra terms + Scenario: Verify Progressive Loan reschedule by extending repayment period - multiple extra terms, flat interest type When Admin sets the business date to "01 June 2024" 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | 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 | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1500 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "01 June 2024" with "1500" amount and expected disbursement date on "01 June 2024" When Admin successfully disburse the loan on "01 June 2024" with "1500" EUR transaction amount Then Loan Repayment schedule has 3 periods, with the following data for periods: @@ -7912,7 +7911,7 @@ Feature: Loan When Admin sets the business date to "01 July 2024" When Admin creates and approves Loan reschedule with the following data: | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | - | 01 August 2024 | 01 July 2024 | | | | 2 | | + | 01 July 2024 | 01 July 2024 | | | | 2 | | Then Loan Repayment schedule has 5 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 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | @@ -7929,12 +7928,12 @@ Feature: Loan | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | @TestRailId:C3966 - Scenario: Verify Progressive Loan reschedule by extending repayment period after partial repayment + Scenario: Verify Progressive Loan reschedule by extending repayment period after partial repayment, flat interest type When Admin sets the business date to "01 June 2024" 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + | 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 | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount Then Loan Repayment schedule has 3 periods, with the following data for periods: @@ -7969,12 +7968,12 @@ Feature: Loan | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | | 01 August 2024 | 15 July 2024 | | | | 1 | | Then Loan Repayment schedule has 4 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | - | 2 | 31 | 01 August 2024 | | 533.33 | 266.67 | 0.0 | 0.0 | 0.0 | 266.67 | 0.0 | 0.0 | 0.0 | 266.67 | - | 3 | 31 | 01 September 2024 | | 266.66 | 266.67 | 0.0 | 0.0 | 0.0 | 266.67 | 0.0 | 0.0 | 0.0 | 266.67 | - | 4 | 30 | 01 October 2024 | | 0.0 | 266.66 | 0.0 | 0.0 | 0.0 | 266.66 | 0.0 | 0.0 | 0.0 | 266.66 | + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 533.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | + | 3 | 31 | 01 September 2024 | | 266.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | 0.0 | 0.0 | 0.0 | 267.0 | + | 4 | 30 | 01 October 2024 | | 0.0 | 266.0 | 0.0 | 0.0 | 0.0 | 266.0 | 0.0 | 0.0 | 0.0 | 266.0 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | @@ -7982,3 +7981,254 @@ Feature: Loan | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + + @TestRailId:C3967 + Scenario: Verify Progressive Loan reschedule by extending repayment periods after partial repayment and then backdated repayment occurs, flat interest type + When Admin sets the business date to "01 June 2024" + When Admin creates a client with random data + When Admin set "LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule + 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 | + | LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount + Then Loan Repayment schedule has 3 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + When Admin sets the business date to "01 July 2024" + And Customer makes "AUTOPAY" repayment on "01 July 2024" with 400 EUR transaction amount + Then Loan Repayment schedule has 3 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + When Admin sets the business date to "15 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 September 2024 | 15 July 2024 | | | | 2 | | + Then Loan Repayment schedule has 5 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | + | 3 | 31 | 01 September 2024 | | 267.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 4 | 30 | 01 October 2024 | | 134.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 5 | 31 | 01 November 2024 | | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 400.0 | 0.0 | 0.0 | 800.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + When Admin sets the business date to "15 October 2024" + And Customer makes "AUTOPAY" repayment on "01 August 2024" with 500 EUR transaction amount + Then Loan Repayment schedule has 5 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 800.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | 01 August 2024 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 September 2024 | | 267.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 4 | 30 | 01 October 2024 | | 134.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | 0.0 | 0.0 | 0.0 | 133.0 | + | 5 | 31 | 01 November 2024 | | 0.0 | 134.0 | 0.0 | 0.0 | 0.0 | 134.0 | 100.0 | 100.0 | 0.0 | 34.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 0.0 | 0.0 | 0.0 | 1200.0 | 900.0 | 100.0 | 0.0 | 300.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 400.0 | 400.0 | 0.0 | 0.0 | 0.0 | 800.0 | + | 01 August 2024 | Repayment | 500.0 | 500.0 | 0.0 | 0.0 | 0.0 | 300.0 | + When Admin set "LP1_ADV_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3987 + Scenario: Verify Progressive Loan reschedule by extending repayment period: Basic scenario without downpayment, declining balance interest type + When Admin sets the business date to "01 June 2024" + 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 3 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 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 669.98 | 330.02 | 10.0 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | + | 2 | 31 | 01 August 2024 | | 336.66 | 333.32 | 6.7 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | + | 3 | 31 | 01 September 2024 | | 0.0 | 336.66 | 3.37 | 0.0 | 0.0 | 340.03 | 0.0 | 0.0 | 0.0 | 340.03 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 20.07 | 0.0 | 0.0 | 1020.07 | 0.0 | 0.0 | 0.0 | 1020.07 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 August 2024 | 01 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 4 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 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 669.98 | 330.02 | 10.0 | 0.0 | 0.0 | 340.02 | 0.0 | 0.0 | 0.0 | 340.02 | + | 2 | 31 | 01 August 2024 | | 448.87 | 221.11 | 6.7 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | + | 3 | 31 | 01 September 2024 | | 225.55 | 223.32 | 4.49 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | + | 4 | 30 | 01 October 2024 | | 0.0 | 225.55 | 2.26 | 0.0 | 0.0 | 227.81 | 0.0 | 0.0 | 0.0 | 227.81 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 23.45 | 0.0 | 0.0 | 1023.45 | 0.0 | 0.0 | 0.0 | 1023.45 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3989 + Scenario: Verify Progressive Loan reschedule by extending repayment period with downpayment installment, declining balance interest type + When Admin sets the business date to "01 June 2024" + 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_DOWNPAYMENT_INTEREST | 01 June 2024 | 1000 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | PENALTIES_FEES_INTEREST_PRINCIPAL_ORDER | + And Admin successfully approves the loan on "01 June 2024" with "1000" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1000" EUR transaction amount + Then Loan Repayment schedule has 4 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 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 July 2024 | | 502.4 | 247.6 | 7.4 | 0.0 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | + | 3 | 31 | 01 August 2024 | | 252.52 | 249.88 | 5.12 | 0.0 | 0.0 | 255.0 | 0.0 | 0.0 | 0.0 | 255.0 | + | 4 | 31 | 01 September 2024 | | 0.0 | 252.52 | 2.57 | 0.0 | 0.0 | 255.09 | 0.0 | 0.0 | 0.0 | 255.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 15.09 | 0.0 | 0.0 | 1015.09 | 0.0 | 0.0 | 0.0 | 1015.09 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 July 2024 | 01 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 5 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 June 2024 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 June 2024 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | + | 2 | 30 | 01 July 2024 | | 629.4 | 120.6 | 7.4 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | + | 3 | 31 | 01 August 2024 | | 507.81 | 121.59 | 6.41 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | + | 4 | 31 | 01 September 2024 | | 384.99 | 122.82 | 5.18 | 0.0 | 0.0 | 128.0 | 0.0 | 0.0 | 0.0 | 128.0 | + | 5 | 30 | 01 October 2024 | | 0.0 | 384.99 | 3.8 | 0.0 | 0.0 | 388.79 | 0.0 | 0.0 | 0.0 | 388.79 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1000.0 | 22.79 | 0.0 | 0.0 | 1022.79 | 0.0 | 0.0 | 0.0 | 1022.79 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | + + @TestRailId:C3990 + Scenario: Verify Progressive Loan reschedule by extending repayment period - multiple extra terms, declining balance interest type + When Admin sets the business date to "01 June 2024" + 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1500 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1500" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1500" EUR transaction amount + Then Loan Repayment schedule has 3 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 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 1004.97 | 495.03 | 15.0 | 0.0 | 0.0 | 510.03 | 0.0 | 0.0 | 0.0 | 510.03 | + | 2 | 31 | 01 August 2024 | | 504.99 | 499.98 | 10.05 | 0.0 | 0.0 | 510.03 | 0.0 | 0.0 | 0.0 | 510.03 | + | 3 | 31 | 01 September 2024 | | 0.0 | 504.99 | 5.05 | 0.0 | 0.0 | 510.04 | 0.0 | 0.0 | 0.0 | 510.04 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 30.1 | 0.0 | 0.0 | 1530.1 | 0.0 | 0.0 | 0.0 | 1530.1 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + When Admin sets the business date to "01 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 July 2024 | 01 July 2024 | | | | 2 | | + Then Loan Repayment schedule has 5 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 June 2024 | | 1500.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 1205.94 | 294.06 | 15.0 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 2 | 31 | 01 August 2024 | | 908.94 | 297.0 | 12.06 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 3 | 31 | 01 September 2024 | | 608.97 | 299.97 | 9.09 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 4 | 30 | 01 October 2024 | | 306.0 | 302.97 | 6.09 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + | 5 | 31 | 01 November 2024 | | 0.0 | 306.0 | 3.06 | 0.0 | 0.0 | 309.06 | 0.0 | 0.0 | 0.0 | 309.06 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1500.0 | 45.3 | 0.0 | 0.0 | 1545.3 | 0.0 | 0.0 | 0.0 | 1545.3 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1500.0 | + + @TestRailId:C3994 + Scenario: Verify Progressive Loan reschedule by extending repayment period after partial repayment, declining balance interest type + When Admin sets the business date to "01 June 2024" + 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_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 June 2024 | 1200 | 12 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "01 June 2024" with "1200" amount and expected disbursement date on "01 June 2024" + When Admin successfully disburse the loan on "01 June 2024" with "1200" EUR transaction amount + Then Loan Repayment schedule has 3 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 0.0 | 0.0 | 0.0 | 408.03 | + | 2 | 31 | 01 August 2024 | | 403.98 | 399.99 | 8.04 | 0.0 | 0.0 | 408.03 | 0.0 | 0.0 | 0.0 | 408.03 | + | 3 | 31 | 01 September 2024 | | 0.0 | 403.98 | 4.04 | 0.0 | 0.0 | 408.02 | 0.0 | 0.0 | 0.0 | 408.02 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 24.08 | 0.0 | 0.0 | 1224.08 | 0.0 | 0.0 | 0.0 | 1224.08 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + When Admin sets the business date to "01 July 2024" + And Customer makes "AUTOPAY" repayment on "01 July 2024" with 417.03 EUR transaction amount + Then Loan Repayment schedule has 3 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 408.03 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 403.89 | 400.08 | 7.95 | 0.0 | 0.0 | 408.03 | 9.0 | 9.0 | 0.0 | 399.03 | + | 3 | 31 | 01 September 2024 | | 0.0 | 403.89 | 4.04 | 0.0 | 0.0 | 407.93 | 0.0 | 0.0 | 0.0 | 407.93 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 23.99 | 0.0 | 0.0 | 1223.99 | 417.03 | 9.0 | 0.0 | 806.96 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 417.03 | 405.03 | 12.0 | 0.0 | 0.0 | 794.97 | + When Admin sets the business date to "15 July 2024" + When Admin creates and approves Loan reschedule with the following data: + | rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate | + | 01 August 2024 | 15 July 2024 | | | | 1 | | + Then Loan Repayment schedule has 4 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 June 2024 | | 1200.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 01 July 2024 | 01 July 2024 | 803.97 | 396.03 | 12.0 | 0.0 | 0.0 | 408.03 | 408.03 | 0.0 | 0.0 | 0.0 | + | 2 | 31 | 01 August 2024 | | 538.58 | 265.39 | 7.95 | 0.0 | 0.0 | 273.34 | 9.0 | 9.0 | 0.0 | 264.34 | + | 3 | 31 | 01 September 2024 | | 270.63 | 267.95 | 5.39 | 0.0 | 0.0 | 273.34 | 0.0 | 0.0 | 0.0 | 273.34 | + | 4 | 30 | 01 October 2024 | | 0.0 | 270.63 | 2.71 | 0.0 | 0.0 | 273.34 | 0.0 | 0.0 | 0.0 | 273.34 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 1200.0 | 28.05 | 0.0 | 0.0 | 1228.05 | 417.03 | 9.0 | 0.0 | 811.02 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 June 2024 | Disbursement | 1200.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1200.0 | + | 01 July 2024 | Repayment | 417.03 | 405.03 | 12.0 | 0.0 | 0.0 | 794.97 | diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index ae15d0072..94896dc8a 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -53,6 +53,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -234,9 +235,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep List overpaidTransactions = new ArrayList<>(); for (final ChangeOperation changeOperation : changeOperations) { - if (changeOperation.isInterestRateChange()) { - final LoanTermVariationsData interestRateChange = changeOperation.getInterestRateChange().get(); - processInterestRateChange(installments, interestRateChange, scheduleModel); + if (changeOperation.isLoanTermVariationsData()) { + final LoanTermVariationsData interestRateChange = changeOperation.getLoanTermVariationsData().get(); + processLoanTermVariation(installments, interestRateChange, scheduleModel); } else if (changeOperation.isTransaction()) { LoanTransaction transaction = changeOperation.getLoanTransaction().get(); processSingleTransaction(transaction, ctx); @@ -301,16 +302,55 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep .map(TransactionChangeData::getNewTransaction).findFirst().orElse(transaction); } - private void processInterestRateChange(final List installments, - final LoanTermVariationsData interestRateChange, final ProgressiveLoanInterestScheduleModel scheduleModel) { - final LocalDate interestRateChangeSubmittedOnDate = interestRateChange.getTermVariationApplicableFrom(); - final BigDecimal newInterestRate = interestRateChange.getDecimalValue(); - if (interestRateChange.getTermVariationType().isInterestPauseVariation()) { - final LocalDate pauseEndDate = interestRateChange.getDateValue(); - emiCalculator.applyInterestPause(scheduleModel, interestRateChangeSubmittedOnDate, pauseEndDate); - } else { - emiCalculator.changeInterestRate(scheduleModel, interestRateChangeSubmittedOnDate, newInterestRate); + private void processLoanTermVariation(final List installments, + final LoanTermVariationsData termVariationsData, final ProgressiveLoanInterestScheduleModel scheduleModel) { + switch (termVariationsData.getTermVariationType()) { + case INTEREST_PAUSE -> handleInterestPause(installments, termVariationsData, scheduleModel); + case INTEREST_RATE_FROM_INSTALLMENT -> handleChangeInterestRate(installments, termVariationsData, scheduleModel); + case EXTEND_REPAYMENT_PERIOD -> handleExtraRepaymentPeriod(installments, termVariationsData, scheduleModel); + default -> throw new IllegalStateException("Unhandled LoanTermVariationType."); } + } + + private void handleExtraRepaymentPeriod(final List installments, + final LoanTermVariationsData termVariationsData, final ProgressiveLoanInterestScheduleModel scheduleModel) { + final LocalDate interestRateChangeSubmittedOnDate = termVariationsData.getTermVariationApplicableFrom(); + final int repaymentPeriodsToAdd = termVariationsData.getDecimalValue().intValue(); + emiCalculator.addRepaymentPeriods(scheduleModel, interestRateChangeSubmittedOnDate, repaymentPeriodsToAdd); + final Loan loan = installments.getFirst().getLoan(); + + int nextInstallmentNumber = installments.stream().mapToInt(LoanRepaymentScheduleInstallment::getInstallmentNumber).max().orElse(0) + + 1; + + for (int i = 0; i < scheduleModel.repaymentPeriods().size(); i++) { + final RepaymentPeriod rp = scheduleModel.repaymentPeriods().get(i); + // Check if this period already exists in installments + if (installments.stream().noneMatch(installment -> installment.getFromDate().equals(rp.getFromDate()) + && installment.getDueDate().equals(rp.getDueDate()))) { + + final LoanRepaymentScheduleInstallment newInstallment = new LoanRepaymentScheduleInstallment(loan, nextInstallmentNumber, + rp.getFromDate(), rp.getDueDate(), rp.getDuePrincipal().getAmount(), rp.getDueInterest().getAmount(), ZERO, ZERO, + false, null, ZERO); + installments.add(newInstallment); + nextInstallmentNumber++; + } + } + processInterestRateChangeOnInstallments(scheduleModel, interestRateChangeSubmittedOnDate, installments); + } + + private void handleChangeInterestRate(final List installments, + final LoanTermVariationsData termVariationsData, final ProgressiveLoanInterestScheduleModel scheduleModel) { + final LocalDate interestRateChangeSubmittedOnDate = termVariationsData.getTermVariationApplicableFrom(); + final BigDecimal newInterestRate = termVariationsData.getDecimalValue(); + emiCalculator.changeInterestRate(scheduleModel, interestRateChangeSubmittedOnDate, newInterestRate); + processInterestRateChangeOnInstallments(scheduleModel, interestRateChangeSubmittedOnDate, installments); + } + + private void handleInterestPause(final List installments, + final LoanTermVariationsData termVariationsData, final ProgressiveLoanInterestScheduleModel scheduleModel) { + final LocalDate interestRateChangeSubmittedOnDate = termVariationsData.getTermVariationApplicableFrom(); + final LocalDate pauseEndDate = termVariationsData.getDateValue(); + emiCalculator.applyInterestPause(scheduleModel, interestRateChangeSubmittedOnDate, pauseEndDate); processInterestRateChangeOnInstallments(scheduleModel, interestRateChangeSubmittedOnDate, installments); } @@ -1204,14 +1244,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep List changeOperations = new ArrayList<>(); Map> loanTermVariationsMap = loanTermVariations.stream() .collect(Collectors.groupingBy(ltvd -> LoanTermVariationType.fromInt(ltvd.getTermType().getId().intValue()))); - if (loanTermVariationsMap.get(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT) != null) { - changeOperations.addAll(loanTermVariationsMap.get(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT).stream() - .map(ChangeOperation::new).toList()); - } - if (loanTermVariationsMap.get(LoanTermVariationType.INTEREST_PAUSE) != null) { - changeOperations - .addAll(loanTermVariationsMap.get(LoanTermVariationType.INTEREST_PAUSE).stream().map(ChangeOperation::new).toList()); - } + + Stream.of(LoanTermVariationType.INTEREST_RATE_FROM_INSTALLMENT, LoanTermVariationType.INTEREST_PAUSE, + LoanTermVariationType.EXTEND_REPAYMENT_PERIOD).forEach(key -> { + if (loanTermVariationsMap.get(key) != null) { + changeOperations.addAll(loanTermVariationsMap.get(key).stream().map(ChangeOperation::new).toList()); + } + }); + if (charges != null) { changeOperations.addAll(charges.stream().map(ChangeOperation::new).toList()); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChangeOperation.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChangeOperation.java index 61cd6d7c9..9042dfec1 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChangeOperation.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ChangeOperation.java @@ -32,30 +32,30 @@ import org.springframework.lang.NonNull; @Getter public class ChangeOperation implements Comparable { - private final Optional interestRateChange; + private final Optional loanTermVariationsData; private final Optional loanCharge; private final Optional loanTransaction; public ChangeOperation(LoanCharge loanCharge) { - this.interestRateChange = Optional.empty(); + this.loanTermVariationsData = Optional.empty(); this.loanCharge = Optional.of(loanCharge); this.loanTransaction = Optional.empty(); } public ChangeOperation(LoanTransaction loanTransaction) { - this.interestRateChange = Optional.empty(); + this.loanTermVariationsData = Optional.empty(); this.loanTransaction = Optional.of(loanTransaction); this.loanCharge = Optional.empty(); } - public ChangeOperation(LoanTermVariationsData interestRateChange) { - this.interestRateChange = Optional.of(interestRateChange); + public ChangeOperation(LoanTermVariationsData loanTermVariationsData) { + this.loanTermVariationsData = Optional.of(loanTermVariationsData); this.loanTransaction = Optional.empty(); this.loanCharge = Optional.empty(); } - public boolean isInterestRateChange() { - return interestRateChange.isPresent(); + public boolean isLoanTermVariationsData() { + return loanTermVariationsData.isPresent(); } public boolean isTransaction() { @@ -80,7 +80,7 @@ public class ChangeOperation implements Comparable { } private LocalDate getEffectiveDate() { - if (interestRateChange.isPresent()) { + if (loanTermVariationsData.isPresent()) { return getSubmittedOnDate(); } else if (loanCharge.isPresent()) { if (isBackdatedCharge()) { @@ -98,8 +98,8 @@ public class ChangeOperation implements Comparable { } private LocalDate getSubmittedOnDate() { - if (interestRateChange.isPresent()) { - return interestRateChange.get().getTermVariationApplicableFrom(); + if (loanTermVariationsData.isPresent()) { + return loanTermVariationsData.get().getTermVariationApplicableFrom(); } else if (loanCharge.isPresent()) { return loanCharge.get().getSubmittedOnDate(); } else if (loanTransaction.isPresent()) { @@ -110,7 +110,7 @@ public class ChangeOperation implements Comparable { } private OffsetDateTime getCreatedDateTime() { - if (interestRateChange.isPresent()) { + if (loanTermVariationsData.isPresent()) { return DateUtils.getOffsetDateTimeOfTenantFromLocalDate(getSubmittedOnDate()); } else if (loanCharge.isPresent() && loanCharge.get().getCreatedDate().isPresent()) { return loanCharge.get().getCreatedDate().get(); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java index 5d706d3c1..33c5d216c 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java @@ -38,6 +38,7 @@ import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -78,42 +79,73 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche validateRescheduleReasonId(fromJsonHelper, jsonElement, dataValidatorBuilder); validateRescheduleReasonComment(fromJsonHelper, jsonElement, dataValidatorBuilder); LocalDate adjustedDueDate = validateAndRetrieveAdjustedDate(fromJsonHelper, jsonElement, rescheduleFromDate, dataValidatorBuilder); - BigDecimal interestRate = validateInterestRate(fromJsonHelper, jsonElement, dataValidatorBuilder, loan); + BigDecimal interestRate = validateInterestRateParam(fromJsonHelper, jsonElement, dataValidatorBuilder, loan); + Integer extraTerms = validateExtraTermsParam(fromJsonHelper, jsonElement, dataValidatorBuilder, loan); validateUnsupportedParams(jsonElement, dataValidatorBuilder); boolean hasInterestRateChange = interestRate != null; boolean hasAdjustDueDateChange = adjustedDueDate != null; - if (hasInterestRateChange) { - validateLoanStatusIsActiveOrClosed(loan, dataValidatorBuilder); - } else { - validateLoanIsActive(loan, dataValidatorBuilder); - } + boolean hasExtraTermsChange = extraTerms != null; - if (hasInterestRateChange && hasAdjustDueDateChange) { + if (Stream.of(hasInterestRateChange, hasAdjustDueDateChange, hasExtraTermsChange).filter(f -> f).count() > 1) { dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode( RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode, "Only one operation is supported at a time during Loan Rescheduling"); } - if (rescheduleFromDate != null && hasInterestRateChange) { - validateInterestRateChangeRescheduleFromDate(loan, rescheduleFromDate); + if (hasExtraTermsChange) { + validateExtraTerms(dataValidatorBuilder, loan); + } else if (hasAdjustDueDateChange) { + validateAdjustDueDateChange(dataValidatorBuilder, loan, rescheduleFromDate); + } else if (hasInterestRateChange) { + validateInterestRate(dataValidatorBuilder, loan, rescheduleFromDate); } - LoanRepaymentScheduleInstallment installment; - if (hasInterestRateChange) { - installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate); - } else { - installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate); - } - - validateReschedulingInstallment(dataValidatorBuilder, installment); - validateForOverdueCharges(dataValidatorBuilder, loan, installment); - if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + private void validateAdjustDueDateChange(DataValidatorBuilder dataValidatorBuilder, Loan loan, LocalDate rescheduleFromDate) { + validateLoanIsActive(loan, dataValidatorBuilder); + LoanRepaymentScheduleInstallment installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate); + validateReschedulingInstallment(dataValidatorBuilder, installment); + validateForOverdueCharges(dataValidatorBuilder, loan, installment); + } + + private void validateInterestRate(DataValidatorBuilder dataValidatorBuilder, Loan loan, LocalDate rescheduleFromDate) { + validateLoanStatusIsActiveOrClosed(loan, dataValidatorBuilder); + if (rescheduleFromDate != null) { + validateInterestRateChangeRescheduleFromDate(loan, rescheduleFromDate); + } + LoanRepaymentScheduleInstallment installment; + installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate); + validateReschedulingInstallment(dataValidatorBuilder, installment); + validateForOverdueCharges(dataValidatorBuilder, loan, installment); + } + + private void validateExtraTerms(DataValidatorBuilder dataValidatorBuilder, Loan loan) { + validateLoanIsActive(loan, dataValidatorBuilder); + } + + private Integer validateExtraTermsParam(FromJsonHelper fromJsonHelper, JsonElement jsonElement, + DataValidatorBuilder dataValidatorBuilder, Loan loan) { + + final Integer extraTerms = fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName, + jsonElement); + DataValidatorBuilder extraTermsDataValidator = dataValidatorBuilder.reset() + .parameter(RescheduleLoansApiConstants.extraTermsParamName).value(extraTerms).ignoreIfNull().integerGreaterThanZero(); + if (extraTerms != null) { + Integer maxNumberOfRepayments = loan.getLoanProduct().getMaxNumberOfRepayments(); + if (maxNumberOfRepayments != null) { + Integer numberOfRepayments = loan.getLoanProductRelatedDetail().getNumberOfRepayments(); + extraTermsDataValidator.notGreaterThanMax(maxNumberOfRepayments - numberOfRepayments); + } + } + + return extraTerms; + } + @Override public void validateReschedulingInstallment(DataValidatorBuilder dataValidatorBuilder, LoanRepaymentScheduleInstallment installment) { if (installment == null) { @@ -125,7 +157,6 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche private void validateUnsupportedParams(JsonElement jsonElement, DataValidatorBuilder dataValidatorBuilder) { final var unsupportedFields = List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, // RescheduleLoansApiConstants.graceOnInterestParamName, // - RescheduleLoansApiConstants.extraTermsParamName, // RescheduleLoansApiConstants.emiParamName// ); @@ -152,6 +183,7 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche final Loan loan = loanRescheduleRequest.getLoan(); LoanRepaymentScheduleInstallment installment; + boolean hasExtraTerms = false; boolean hasInterestRateChange = false; for (LoanRescheduleRequestToTermVariationMapping mapping : loanRescheduleRequest .getLoanRescheduleRequestToTermVariationMappings()) { @@ -159,6 +191,9 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche if (termType.isInterestRateVariation() || termType.isInterestRateFromInstallment()) { hasInterestRateChange = true; } + if (termType.isExtendRepaymentPeriod()) { + hasExtraTerms = true; + } } if (hasInterestRateChange) { validateLoanStatusIsActiveOrClosed(loan, dataValidatorBuilder); @@ -166,13 +201,15 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche validateLoanIsActive(loan, dataValidatorBuilder); } - if (loanRescheduleRequest.getInterestRateFromInstallmentTermVariationIfExists() != null) { + if (loanRescheduleRequest.getInterestRateFromInstallmentTermVariationIfExists() != null || hasExtraTerms) { installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate); } else { installment = loan.fetchLoanRepaymentScheduleInstallmentByDueDate(rescheduleFromDate); } validateReschedulingInstallment(dataValidatorBuilder, installment); - validateForOverdueCharges(dataValidatorBuilder, loan, installment); + if (!hasExtraTerms) { + validateForOverdueCharges(dataValidatorBuilder, loan, installment); + } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); @@ -198,7 +235,7 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche } } - private BigDecimal validateInterestRate(final FromJsonHelper fromJsonHelper, final JsonElement jsonElement, + private BigDecimal validateInterestRateParam(final FromJsonHelper fromJsonHelper, final JsonElement jsonElement, DataValidatorBuilder dataValidatorBuilder, Loan loan) { final BigDecimal interestRate = fromJsonHelper .extractBigDecimalWithLocaleNamed(RescheduleLoansApiConstants.newInterestRateParamName, jsonElement); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java index 0991245b5..7ed0283c6 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java @@ -78,6 +78,9 @@ public interface EMICalculator { void changeInterestRate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate newInterestSubmittedOnDate, BigDecimal newInterestRate); + void addRepaymentPeriods(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate submittedOnDate, + int numberOfRepaymentPeriodsToAdd); + /** * This method applies outstanding balance correction on the interest model. Negative amount decreases the * outstanding balance while positive amounts are increasing that. Typically used for late repayment or to count diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 092a520cb..b00777417 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -26,13 +26,16 @@ import java.time.LocalDate; import java.time.Year; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAdjusters; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.data.CurrencyData; @@ -178,6 +181,58 @@ public final class ProgressiveEMICalculator implements EMICalculator { changeInterestRate(scheduleModel, EmiChangeOperation.changeInterestRate(newInterestSubmittedOnDate, newInterestRate)); } + @Override + public void addRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate submittedOnDate, + final int numberOfRepaymentPeriodsToAdd) { + addRepaymentPeriods(scheduleModel, + EmiChangeOperation.addRepaymentPeriods(submittedOnDate, scheduleModel.zero(), numberOfRepaymentPeriodsToAdd)); + } + + public void addRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) { + LocalDate disbursementDate = scheduleModel.getStartDate(); + int repaymentPeriodCount = scheduleModel.repaymentPeriods().size(); + final LocalDate interestRateChangeEffectiveDate = operation.getSubmittedOnDate().minusDays(1); + + List periods2 = generateAdditionalRepaymentPeriodDueDates(scheduleModel, operation.getPeriodsToAdd(), + repaymentPeriodCount, scheduleModel.resolveRepaymentPEriodLengthGeneratorFunction(disbursementDate)); + updateModel(scheduleModel, periods2, LocalDateInterval::startDate, LocalDateInterval::endDate); + + scheduleModel + .changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.zero(), + scheduleModel.zero(), scheduleModel.zero()) + .ifPresent(repaymentPeriod -> calculateEMIValueAndRateFactors( + getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel, + operation)); + } + + public void updateModel(ProgressiveLoanInterestScheduleModel scheduleModel, List updateExpectedRepaymentPeriods, + Function from, Function to) { + final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); + MathContext mc = scheduleModel.mc(); + final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); + final AtomicReference prev = new AtomicReference<>(); + RepaymentPeriod originalLAstRepaymentPeriod = scheduleModel.getLastRepaymentPeriod(); + prev.set(originalLAstRepaymentPeriod); + List repaymentPeriods = updateExpectedRepaymentPeriods.stream().map(e -> { + RepaymentPeriod rp = RepaymentPeriod.create(prev.get(), from.apply(e), to.apply(e), zero, mc, loanProductRelatedDetail); + rp.setTotalDisbursedAmount(originalLAstRepaymentPeriod.getTotalDisbursedAmount()); + rp.setTotalCapitalizedIncomeAmount(originalLAstRepaymentPeriod.getTotalCapitalizedIncomeAmount()); + prev.set(rp); + return rp; + }).toList(); + scheduleModel.repaymentPeriods().addAll(repaymentPeriods); + } + + List generateAdditionalRepaymentPeriodDueDates(final ProgressiveLoanInterestScheduleModel scheduleModel, + final int periods, final int existingRepayments, Function repaymentPeriodLengthResolver) { + final List expectedRepaymentPeriods = new ArrayList<>(); + Integer repayEvery = scheduleModel.loanProductRelatedDetail().getRepayEvery(); + IntStream.range(existingRepayments, existingRepayments + periods).forEach( + i -> expectedRepaymentPeriods.add(LocalDateInterval.create(repaymentPeriodLengthResolver.apply((long) i * repayEvery), + repaymentPeriodLengthResolver.apply((long) (i + 1) * repayEvery)))); + return expectedRepaymentPeriods; + } + private void changeInterestRate(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) { final LocalDate interestRateChangeEffectiveDate = operation.getSubmittedOnDate().minusDays(1); scheduleModel.addInterestRate(interestRateChangeEffectiveDate, operation.getInterestRate()); @@ -486,7 +541,8 @@ public final class ProgressiveEMICalculator implements EMICalculator { final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) { final List relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate); final boolean onlyOnActualModelShouldApply = scheduleModel.isEmpty() - || operation.getAction() == EmiChangeOperation.Action.INTEREST_RATE_CHANGE || scheduleModel.isCopy(); + || operation.getAction() == EmiChangeOperation.Action.INTEREST_RATE_CHANGE + || operation.getAction() == EmiChangeOperation.Action.ADD_REPAYMENT_PERIODS || scheduleModel.isCopy(); calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel); calculateOutstandingBalance(scheduleModel); @@ -932,13 +988,40 @@ public final class ProgressiveEMICalculator implements EMICalculator { Money periodEmi = total.dividedBy(repaymentPeriods.size(), mc); Money periodEmiInMultiplesOf = applyInstallmentAmountInMultiplesOf(scheduleModel, periodEmi); Money remainder = total.minus(periodEmiInMultiplesOf.multipliedBy(repaymentPeriods.size(), mc)); + AtomicReference reallocationAmount = new AtomicReference<>(Money.zero(currency)); repaymentPeriods.forEach(rp -> { Money emi = rp.equals(lastRepaymentPeriod) ? periodEmiInMultiplesOf.add(remainder) : periodEmiInMultiplesOf; - rp.setEmi(emi); - rp.setOriginalEmi(emi); - rp.getInterestPeriods().forEach(InterestPeriod::updateOutstandingLoanBalance); + + if (emi.plus(rp.getTotalCreditedAmount(), mc).plus(rp.getFutureUnrecognizedInterest(), mc) + .isGreaterThanOrEqualTo(rp.getTotalPaidAmount())) { + rp.setEmi(emi); + rp.setOriginalEmi(emi); + rp.getInterestPeriods().forEach(InterestPeriod::updateOutstandingLoanBalance); + } else { + Money adjustment = rp.getTotalPaidAmount() + .minus(emi.plus(rp.getTotalCreditedAmount(), mc).plus(rp.getFutureUnrecognizedInterest(), mc)); + reallocationAmount.set(reallocationAmount.get().add(adjustment)); + rp.setEmi(rp.getTotalPaidAmount()); + rp.setOriginalEmi(rp.getTotalPaidAmount()); + rp.getInterestPeriods().forEach(InterestPeriod::updateOutstandingLoanBalance); + } }); + + if (reallocationAmount.get().isGreaterThanZero()) { + repaymentPeriods.reversed().forEach(rp -> { + if (reallocationAmount.get().isGreaterThanZero() && !rp.isFullyPaid()) { + Money minimumNewEmi = MathUtil.max(rp.getEmi().minus(reallocationAmount.get()), Money.zero(currency), true); + Money alreadyPaidEmi = rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount(), mc) + .minus(rp.getFutureUnrecognizedInterest(), mc); + Money newEmi = MathUtil.max(rp.getCalculatedDueInterest(), MathUtil.max(minimumNewEmi, alreadyPaidEmi, true), true); + reallocationAmount.set(reallocationAmount.get().minus(rp.getEmi().minus(newEmi))); + rp.setEmi(newEmi); + rp.getInterestPeriods().forEach(InterestPeriod::updateOutstandingLoanBalance); + } + }); + } + } private void calculateEMIOnActualModel(List repaymentPeriods, ProgressiveLoanInterestScheduleModel scheduleModel) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java index 96d8e43b8..5eab7086a 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java @@ -33,7 +33,7 @@ public class EmiChangeOperation { DISBURSEMENT, // INTEREST_RATE_CHANGE, // CAPITALIZED_INCOME, // - ; + ADD_REPAYMENT_PERIODS, // } private final Action action; @@ -42,22 +42,37 @@ public class EmiChangeOperation { private final Money amount; private final BigDecimal interestRate; + private final int periodsToAdd; + public static EmiChangeOperation disburse(final LocalDate disbursementDueDate, final Money disbursedAmount) { - return new EmiChangeOperation(EmiChangeOperation.Action.DISBURSEMENT, disbursementDueDate, disbursedAmount, null); + return new EmiChangeOperation(EmiChangeOperation.Action.DISBURSEMENT, disbursementDueDate, disbursedAmount, null, 0); } public static EmiChangeOperation changeInterestRate(final LocalDate newInterestSubmittedOnDate, final BigDecimal newInterestRate) { - return new EmiChangeOperation(EmiChangeOperation.Action.INTEREST_RATE_CHANGE, newInterestSubmittedOnDate, null, newInterestRate); + return new EmiChangeOperation(EmiChangeOperation.Action.INTEREST_RATE_CHANGE, newInterestSubmittedOnDate, null, newInterestRate, 0); } public static EmiChangeOperation capitalizedIncome(final LocalDate transactionDueDate, final Money transactionAmount) { - return new EmiChangeOperation(Action.CAPITALIZED_INCOME, transactionDueDate, transactionAmount, null); + return new EmiChangeOperation(Action.CAPITALIZED_INCOME, transactionDueDate, transactionAmount, null, 0); + } + + public static EmiChangeOperation addRepaymentPeriods(final LocalDate transactionDueDate, final Money transactionAmount, + final int numPeriods) { + return new EmiChangeOperation(Action.ADD_REPAYMENT_PERIODS, transactionDueDate, transactionAmount, null, numPeriods); } public EmiChangeOperation withZeroAmount() { if (action == Action.DISBURSEMENT || action == Action.CAPITALIZED_INCOME) { - return new EmiChangeOperation(action, submittedOnDate, amount.zero(), null); + return new EmiChangeOperation(action, submittedOnDate, amount.zero(), null, 0); } return null; } + + public boolean isAddRepaymentPeriods() { + return action == Action.ADD_REPAYMENT_PERIODS; + } + + public boolean isInterestRateChange() { + return action == Action.INTEREST_RATE_CHANGE; + } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java index f17b782db..3b4e990c1 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java @@ -38,6 +38,7 @@ import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.Data; @@ -430,4 +431,13 @@ public class ProgressiveLoanInterestScheduleModel { public boolean isCopy() { return this.modifiers.get(COPY); } + + public Function resolveRepaymentPEriodLengthGeneratorFunction(LocalDate instance) { + return switch (loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) { + case MONTHS -> instance::plusMonths; + case WEEKS -> instance::plusWeeks; + case DAYS -> instance::plusDays; + default -> throw new UnsupportedOperationException(); + }; + } } 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 35a4ca6e2..085b20501 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 @@ -3953,6 +3953,225 @@ class ProgressiveEMICalculatorTest { } } + @Nested + public class RescheduleInstallmentExtendRepaymentPeriod { + + @BeforeEach + void beforeEach() { + Mockito.when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE); + Mockito.when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY); + Mockito.when(loanProductRelatedDetail.getDaysInYearType()).thenReturn(DaysInYearType.DAYS_360.getValue()); + Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); + Mockito.when(loanProductRelatedDetail.getAnnualNominalInterestRate()).thenReturn(BigDecimal.valueOf(12.0)); + Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); + Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(3); + Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); + } + + @Test + public void testExtraTermsDecliningBalance() { + final LocalDate disbursementDate = LocalDate.of(2024, 6, 1); + final List expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate); + final Integer installmentAmountInMultiplesOf = null; + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc); + + emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(1000)); + + checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); // + checkPeriod(interestSchedule, 1, 340.02, 6.70, 333.32, 336.66, false); // + checkPeriod(interestSchedule, 2, 340.03, 3.37, 336.66, 0.00, false); // + + // add extra repayment period 1 + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 7, 2), 1); + + checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); // + checkPeriod(interestSchedule, 1, 227.81, 6.70, 221.11, 448.87, false); // + checkPeriod(interestSchedule, 2, 227.81, 4.49, 223.32, 225.55, false); // + checkPeriod(interestSchedule, 3, 227.81, 2.26, 225.55, 0.00, false); // + + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 8, 2), 2); + + checkPeriod(interestSchedule, 0, 340.02, 10.00, 330.02, 669.98, false); // + checkPeriod(interestSchedule, 1, 227.81, 6.70, 221.11, 448.87, false); // + checkPeriod(interestSchedule, 2, 115.04, 4.49, 110.55, 338.32, false); // + checkPeriod(interestSchedule, 3, 115.04, 3.38, 111.66, 226.66, false); // + checkPeriod(interestSchedule, 4, 115.04, 2.27, 112.77, 113.89, false); // + checkPeriod(interestSchedule, 5, 115.03, 1.14, 113.89, 0.00, false); // + + } + + @Test + public void testExtraTermsFlat() { + final LocalDate disbursementDate = LocalDate.of(2024, 6, 1); + final List expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate); + final Integer installmentAmountInMultiplesOf = null; + + Mockito.when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.FLAT); + Mockito.when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()) + .thenReturn(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD); + Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true); + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc); + + emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(1000)); + + checkPeriod(interestSchedule, 0, 343.33, 10.00, 333.33, 666.67, false); // + checkPeriod(interestSchedule, 1, 343.33, 10.00, 333.33, 333.34, false); // + checkPeriod(interestSchedule, 2, 343.34, 10.00, 333.34, 0.00, false); // + + // add extra repayment period 1 + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 7, 2), 1); + + checkPeriod(interestSchedule, 0, 343.33, 10.00, 333.33, 666.67, false); // + checkPeriod(interestSchedule, 1, 232.22, 10.00, 222.22, 444.45, false); // + checkPeriod(interestSchedule, 2, 232.22, 10.00, 222.22, 222.23, false); // + checkPeriod(interestSchedule, 3, 232.23, 10.00, 222.23, 0.00, false); // + + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 8, 2), 2); + + checkPeriod(interestSchedule, 0, 343.33, 10.00, 333.33, 666.67, false); // + checkPeriod(interestSchedule, 1, 232.22, 10.00, 222.22, 444.45, false); // + checkPeriod(interestSchedule, 2, 121.11, 10.00, 111.11, 333.34, false); // + checkPeriod(interestSchedule, 3, 121.11, 10.00, 111.11, 222.23, false); // + checkPeriod(interestSchedule, 4, 121.11, 10.00, 111.11, 111.12, false); // + checkPeriod(interestSchedule, 5, 121.12, 10.00, 111.12, 0.00, false); // + + } + + @Test + public void testExtraTermsDecliningBalanceWithFullyRepaidPeriods() { + final LocalDate disbursementDate = LocalDate.of(2024, 6, 1); + + Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(4); + + final List expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate); + final Integer installmentAmountInMultiplesOf = null; + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc); + + emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(1000)); + + checkPeriod(interestSchedule, 0, 256.28, 10.00, 246.28, 753.72, false); // + checkPeriod(interestSchedule, 1, 256.28, 7.54, 248.74, 504.98, false); // + checkPeriod(interestSchedule, 2, 256.28, 5.05, 251.23, 253.75, false); // + checkPeriod(interestSchedule, 3, 256.29, 2.54, 253.75, 0.00, false); // + + // simulate merchant issued refund allocated on last installment ( fully repaid ) on first date + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 10, 1), LocalDate.of(2024, 6, 1), + Money.of(currency, BigDecimal.valueOf(256.29D))); + + // simulate early repayment on day 0 + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 6, 1), + Money.of(currency, BigDecimal.valueOf(256.28D))); + + // verify 1st and 4th installments are fully repaid + checkPeriod(interestSchedule, 0, 256.28, 0.00, 256.28, 487.43, true); // + checkPeriod(interestSchedule, 1, 256.28, 9.74, 246.54, 240.89, false); // + checkPeriod(interestSchedule, 2, 243.30, 2.41, 240.89, 0.00, false); // + checkPeriod(interestSchedule, 3, 256.29, 0.00, 256.29, 0.00, true); // + + // add extra repayment period 1 + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 1); + + // verify 1st and 4th installments are not modified, additional principal paid are substract from last + // period + checkPeriod(interestSchedule, 0, 256.28, 0.00, 256.28, 487.43, true); // + checkPeriod(interestSchedule, 1, 206.04, 9.74, 196.30, 291.13, false); // + checkPeriod(interestSchedule, 2, 206.04, 2.91, 203.13, 88.00, false); // + checkPeriod(interestSchedule, 3, 256.29, 0.00, 256.29, 88.00, true); // + checkPeriod(interestSchedule, 4, 89.76, 1.76, 88.00, 0.00, false); // + + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 2); + + // verify 1st and 4th installments are not modified, additional principal paid are substract from last + // periods + checkPeriod(interestSchedule, 0, 256.28, 0.00, 256.28, 487.43, true); // + checkPeriod(interestSchedule, 1, 126.91, 9.74, 117.17, 370.26, false); // + checkPeriod(interestSchedule, 2, 126.91, 3.70, 123.21, 247.05, false); // + checkPeriod(interestSchedule, 3, 256.29, 0.00, 256.29, 247.05, true); // + checkPeriod(interestSchedule, 4, 126.91, 4.94, 121.97, 125.08, false); // + checkPeriod(interestSchedule, 5, 126.33, 1.25, 125.08, 0.00, false); // + checkPeriod(interestSchedule, 6, 0.00, 0.00, 0.00, 0.00, true); // + } + + @Test + public void testExtraTermsFlatWithFullyRepaidPeriods() { + final LocalDate disbursementDate = LocalDate.of(2024, 6, 1); + + Mockito.when(loanProductRelatedDetail.getNumberOfRepayments()).thenReturn(4); + Mockito.when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.FLAT); + Mockito.when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()) + .thenReturn(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD); + Mockito.when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true); + + final List expectedRepaymentPeriods = generateExpectedRepaymentPeriods(disbursementDate); + final Integer installmentAmountInMultiplesOf = null; + + final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( + expectedRepaymentPeriods, loanProductRelatedDetail, List.of(), installmentAmountInMultiplesOf, mc); + + emiCalculator.addDisbursement(interestSchedule, disbursementDate, toMoney(1000)); + + checkPeriod(interestSchedule, 0, 260.00, 10.00, 250.00, 750.00, false); // + checkPeriod(interestSchedule, 1, 260.00, 10.00, 250.00, 500.00, false); // + checkPeriod(interestSchedule, 2, 260.00, 10.00, 250.00, 250.00, false); // + checkPeriod(interestSchedule, 3, 260.00, 10.00, 250.00, 0.00, false); // + + // simulate merchant issued refund allocated on last installment ( fully repaid ) on first date + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 10, 1), LocalDate.of(2024, 6, 1), + Money.of(currency, BigDecimal.valueOf(250.0D))); + emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 10, 1), LocalDate.of(2024, 6, 1), + Money.of(currency, BigDecimal.valueOf(10.0D))); + + checkPeriod(interestSchedule, 0, 260.00, 10.00, 250.00, 500.00, false); // + checkPeriod(interestSchedule, 1, 260.00, 10.00, 250.00, 250.00, false); // + checkPeriod(interestSchedule, 2, 260.00, 10.00, 250.00, 0.00, false); // + checkPeriod(interestSchedule, 3, 260.00, 10.00, 250.00, 0.00, true); // + + // simulate early repayment on day 0 + emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 6, 1), + Money.of(currency, BigDecimal.valueOf(250.0D))); + emiCalculator.payInterest(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 6, 1), + Money.of(currency, BigDecimal.valueOf(10.0D))); + + // verify 1st and 4th installments are fully repaid + checkPeriod(interestSchedule, 0, 260.00, 10.00, 250.00, 500.00, true); // + checkPeriod(interestSchedule, 1, 260.00, 10.00, 250.00, 250.00, false); // + checkPeriod(interestSchedule, 2, 260.00, 10.00, 250.00, 0.00, false); // + checkPeriod(interestSchedule, 3, 260.00, 10.00, 250.00, 0.00, true); // + + // add extra repayment period 1 + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 1); + + // verify 1st and 4th installments are not modified, additional principal paid are substract from last + // period + checkPeriod(interestSchedule, 0, 260.00, 10.00, 250.00, 500.00, true); // + checkPeriod(interestSchedule, 1, 210.00, 10.00, 200.00, 300.00, false); // + checkPeriod(interestSchedule, 2, 210.00, 10.00, 200.00, 100.00, false); // + checkPeriod(interestSchedule, 3, 260.00, 10.00, 250.00, 100.00, true); // + checkPeriod(interestSchedule, 4, 110.00, 10.00, 100.00, 0.00, false); // + + // add extra repayment period 2 + emiCalculator.addRepaymentPeriods(interestSchedule, LocalDate.of(2024, 6, 15), 2); + + // verify 1st and 4th installments are not modified, additional principal paid are substract from last + // periods + checkPeriod(interestSchedule, 0, 260.00, 10.00, 250.00, 500.00, true); // + checkPeriod(interestSchedule, 1, 152.86, 10.00, 142.86, 357.14, false); // + checkPeriod(interestSchedule, 2, 152.86, 10.00, 142.86, 214.28, false); // + checkPeriod(interestSchedule, 3, 260.00, 10.00, 250.00, 214.28, true); // + checkPeriod(interestSchedule, 4, 152.86, 10.00, 142.86, 71.42, false); // + checkPeriod(interestSchedule, 5, 81.42, 10.00, 71.42, 0.00, false); // + checkPeriod(interestSchedule, 6, 10.00, 10.00, 0.00, 0.00, false); // + + } + + } + // utilities private List generateExpectedRepaymentPeriods(LocalDate disbursementDate) { return switch (loanProductRelatedDetail.getRepaymentPeriodFrequencyType()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 173e3f486..bff1713d7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -395,7 +395,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche .getLoanRescheduleRequestToTermVariationMappings()) { mapping.getLoanTermVariations().updateIsActive(true); LoanTermVariationType termType = mapping.getLoanTermVariations().getTermType(); - if (termType.isInterestRateVariation() || termType.isInterestRateFromInstallment()) { + if (termType.isInterestRateVariation() || termType.isInterestRateFromInstallment() || termType.isExtendRepaymentPeriod()) { hasInterestRateChange = true; } }