From 8ab9c9ea9afd2f2f34aa3e6c04f679d11baf8f99 Mon Sep 17 00:00:00 2001 From: Janos Meszaros Date: Wed, 15 Jan 2025 20:09:47 +0100 Subject: [PATCH] FINERACT-1981: Fix interest calculation when applies multi disbursement --- .../resources/features/EMICalculation.feature | 120 +++++++++--------- .../ProgressiveLoanInterestScheduleModel.java | 55 +++++++- .../loanproduct/calc/EmiChangeOperation.java | 49 +++++++ .../calc/ProgressiveEMICalculator.java | 95 +++++++++----- .../calc/ProgressiveEMICalculatorTest.java | 31 +++-- .../LoanInterestRefundTest.java | 6 +- 6 files changed, 249 insertions(+), 107 deletions(-) create mode 100644 fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EmiChangeOperation.java diff --git a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature index d489a3a3eac..131e769c531 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature @@ -4082,10 +4082,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 01 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.54 | 249.46 | 5.68 | 0.0 | 0.0 | 255.14 | 255.14 | 255.14 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 22 January 2024 | 495.4 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | 255.14 | 255.14 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 22 January 2024 | 240.26 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | 255.14 | 255.14 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 22 January 2024 | 0.0 | 240.26 | 0.0 | 0.0 | 0.0 | 240.26 | 240.26 | 240.26 | 0.0 | 0.0 | + | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.53 | 249.47 | 5.68 | 0.0 | 0.0 | 255.15 | 255.15 | 255.15 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 22 January 2024 | 495.38 | 255.15 | 0.0 | 0.0 | 0.0 | 255.15 | 255.15 | 255.15 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 22 January 2024 | 240.23 | 255.15 | 0.0 | 0.0 | 0.0 | 255.15 | 255.15 | 255.15 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 22 January 2024 | 0.0 | 240.23 | 0.0 | 0.0 | 0.0 | 240.23 | 240.23 | 240.23 | 0.0 | 0.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 | 5.68 | 0.0 | 0.0 | 1005.68 | 1005.68 | 1005.68 | 0.0 | 0.0 | @@ -4354,10 +4354,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 01 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 753.25 | 246.75 | 8.39 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 2 | 29 | 01 March 2024 | | 504.02 | 249.23 | 5.91 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 3 | 31 | 01 April 2024 | | 253.11 | 250.91 | 4.23 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 253.11 | 2.05 | 0.0 | 0.0 | 255.16 | 0.0 | 0.0 | 0.0 | 255.16 | + | 1 | 31 | 01 February 2024 | | 753.24 | 246.76 | 8.39 | 0.0 | 0.0 | 255.15 | 0.0 | 0.0 | 0.0 | 255.15 | + | 2 | 29 | 01 March 2024 | | 504.0 | 249.24 | 5.91 | 0.0 | 0.0 | 255.15 | 0.0 | 0.0 | 0.0 | 255.15 | + | 3 | 31 | 01 April 2024 | | 253.08 | 250.92 | 4.23 | 0.0 | 0.0 | 255.15 | 0.0 | 0.0 | 0.0 | 255.15 | + | 4 | 30 | 01 May 2024 | | 0.0 | 253.08 | 2.05 | 0.0 | 0.0 | 255.13 | 0.0 | 0.0 | 0.0 | 255.13 | 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.58 | 0.0 | 0.0 | 1020.58 | 0.0 | 0.0 | 0.0 | 1020.58 | @@ -4371,10 +4371,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 01 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.54 | 249.46 | 5.68 | 0.0 | 0.0 | 255.14 | 255.14 | 255.14 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | | 500.7 | 249.84 | 5.3 | 0.0 | 0.0 | 255.14 | 247.7 | 247.7 | 0.0 | 7.44 | - | 3 | 31 | 01 April 2024 | | 249.76 | 250.94 | 4.2 | 0.0 | 0.0 | 255.14 | 0.0 | 0.0 | 0.0 | 255.14 | - | 4 | 30 | 01 May 2024 | | 0.0 | 249.76 | 2.03 | 0.0 | 0.0 | 251.79 | 0.0 | 0.0 | 0.0 | 251.79 | + | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.53 | 249.47 | 5.68 | 0.0 | 0.0 | 255.15 | 255.15 | 255.15 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 500.68 | 249.85 | 5.3 | 0.0 | 0.0 | 255.15 | 247.69 | 247.69 | 0.0 | 7.46 | + | 3 | 31 | 01 April 2024 | | 249.73 | 250.95 | 4.2 | 0.0 | 0.0 | 255.15 | 0.0 | 0.0 | 0.0 | 255.15 | + | 4 | 30 | 01 May 2024 | | 0.0 | 249.73 | 2.03 | 0.0 | 0.0 | 251.76 | 0.0 | 0.0 | 0.0 | 251.76 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 1000.0 | 17.21 | 0.0 | 0.0 | 1017.21 | 502.84 | 502.84 | 0.0 | 514.37 | @@ -4385,19 +4385,19 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 22 January 2024 | Payout Refund | 500.0 | 494.32 | 5.68 | 0.0 | 0.0 | 505.68 | false | false | | 22 January 2024 | Interest Refund | 2.84 | 2.84 | 0.0 | 0.0 | 0.0 | 502.84 | false | false | When Admin sets the business date to "01 March 2024" - And Customer makes "AUTOPAY" repayment on "01 March 2024" with 7.44 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 March 2024" with 7.46 EUR transaction amount When Admin sets the business date to "01 April 2024" - And Customer makes "AUTOPAY" repayment on "01 April 2024" with 255.14 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 April 2024" with 255.15 EUR transaction amount When Admin sets the business date to "01 May 2024" - And Customer makes "AUTOPAY" repayment on "01 May 2024" with 251.79 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 May 2024" with 251.76 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 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 01 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.54 | 249.46 | 5.68 | 0.0 | 0.0 | 255.14 | 255.14 | 255.14 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 01 March 2024 | 500.7 | 249.84 | 5.3 | 0.0 | 0.0 | 255.14 | 255.14 | 247.7 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 01 April 2024 | 249.76 | 250.94 | 4.2 | 0.0 | 0.0 | 255.14 | 255.14 | 0.0 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 249.76 | 2.03 | 0.0 | 0.0 | 251.79 | 251.79 | 0.0 | 0.0 | 0.0 | + | 1 | 31 | 01 February 2024 | 22 January 2024 | 750.53 | 249.47 | 5.68 | 0.0 | 0.0 | 255.15 | 255.15 | 255.15 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 01 March 2024 | 500.68 | 249.85 | 5.3 | 0.0 | 0.0 | 255.15 | 255.15 | 247.69 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 01 April 2024 | 249.73 | 250.95 | 4.2 | 0.0 | 0.0 | 255.15 | 255.15 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 249.73 | 2.03 | 0.0 | 0.0 | 251.76 | 251.76 | 0.0 | 0.0 | 0.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 | 17.21 | 0.0 | 0.0 | 1017.21 | 1017.21 | 502.84 | 0.0 | 0.0 | @@ -4407,9 +4407,9 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 01 January 2024 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | | 22 January 2024 | Payout Refund | 500.0 | 494.32 | 5.68 | 0.0 | 0.0 | 505.68 | false | false | | 22 January 2024 | Interest Refund | 2.84 | 2.84 | 0.0 | 0.0 | 0.0 | 502.84 | false | false | - | 01 March 2024 | Repayment | 7.44 | 2.14 | 5.3 | 0.0 | 0.0 | 500.7 | false | false | - | 01 April 2024 | Repayment | 255.14 | 250.94 | 4.2 | 0.0 | 0.0 | 249.76 | false | false | - | 01 May 2024 | Repayment | 251.79 | 249.76 | 2.03 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Repayment | 7.46 | 2.16 | 5.3 | 0.0 | 0.0 | 500.68 | false | false | + | 01 April 2024 | Repayment | 255.15 | 250.95 | 4.2 | 0.0 | 0.0 | 249.73 | false | false | + | 01 May 2024 | Repayment | 251.76 | 249.73 | 2.03 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Accrual | 17.21 | 0.0 | 17.21 | 0.0 | 0.0 | 0.0 | false | false | Then Loan status will be "CLOSED_OBLIGATIONS_MET" @@ -4430,10 +4430,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 07 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 22 January 2024 | 749.64 | 250.36 | 4.47 | 0.0 | 0.0 | 254.83 | 254.83 | 254.83 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | | 500.11 | 249.53 | 5.3 | 0.0 | 0.0 | 254.83 | 247.61 | 247.61 | 0.0 | 7.22 | - | 3 | 31 | 01 April 2024 | | 249.47 | 250.64 | 4.19 | 0.0 | 0.0 | 254.83 | 0.0 | 0.0 | 0.0 | 254.83 | - | 4 | 30 | 01 May 2024 | | 0.0 | 249.47 | 2.02 | 0.0 | 0.0 | 251.49 | 0.0 | 0.0 | 0.0 | 251.49 | + | 1 | 31 | 01 February 2024 | 22 January 2024 | 749.63 | 250.37 | 4.47 | 0.0 | 0.0 | 254.84 | 254.84 | 254.84 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | | 500.09 | 249.54 | 5.3 | 0.0 | 0.0 | 254.84 | 247.6 | 247.6 | 0.0 | 7.24 | + | 3 | 31 | 01 April 2024 | | 249.44 | 250.65 | 4.19 | 0.0 | 0.0 | 254.84 | 0.0 | 0.0 | 0.0 | 254.84 | + | 4 | 30 | 01 May 2024 | | 0.0 | 249.44 | 2.02 | 0.0 | 0.0 | 251.46 | 0.0 | 0.0 | 0.0 | 251.46 | 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.98 | 0.0 | 0.0 | 1015.98 | 502.44 | 502.44 | 0.0 | 513.54 | @@ -4444,19 +4444,19 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 22 January 2024 | Merchant Issued Refund | 500.0 | 495.53 | 4.47 | 0.0 | 0.0 | 504.47 | false | false | | 22 January 2024 | Interest Refund | 2.44 | 2.44 | 0.0 | 0.0 | 0.0 | 502.03 | false | false | When Admin sets the business date to "01 March 2024" - And Customer makes "AUTOPAY" repayment on "01 March 2024" with 7.22 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 March 2024" with 7.24 EUR transaction amount When Admin sets the business date to "01 April 2024" - And Customer makes "AUTOPAY" repayment on "01 April 2024" with 254.83 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 April 2024" with 254.84 EUR transaction amount When Admin sets the business date to "01 May 2024" - And Customer makes "AUTOPAY" repayment on "01 May 2024" with 251.49 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 May 2024" with 251.46 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 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 07 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 22 January 2024 | 749.64 | 250.36 | 4.47 | 0.0 | 0.0 | 254.83 | 254.83 | 254.83 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 01 March 2024 | 500.11 | 249.53 | 5.3 | 0.0 | 0.0 | 254.83 | 254.83 | 247.61 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 01 April 2024 | 249.47 | 250.64 | 4.19 | 0.0 | 0.0 | 254.83 | 254.83 | 0.0 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 249.47 | 2.02 | 0.0 | 0.0 | 251.49 | 251.49 | 0.0 | 0.0 | 0.0 | + | 1 | 31 | 01 February 2024 | 22 January 2024 | 749.63 | 250.37 | 4.47 | 0.0 | 0.0 | 254.84 | 254.84 | 254.84 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 01 March 2024 | 500.09 | 249.54 | 5.3 | 0.0 | 0.0 | 254.84 | 254.84 | 247.6 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 01 April 2024 | 249.44 | 250.65 | 4.19 | 0.0 | 0.0 | 254.84 | 254.84 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 249.44 | 2.02 | 0.0 | 0.0 | 251.46 | 251.46 | 0.0 | 0.0 | 0.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 | 15.98 | 0.0 | 0.0 | 1015.98 | 1015.98 | 502.44 | 0.0 | 0.0 | @@ -4466,9 +4466,9 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 07 January 2024 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | | 22 January 2024 | Merchant Issued Refund | 500.0 | 495.53 | 4.47 | 0.0 | 0.0 | 504.47 | false | false | | 22 January 2024 | Interest Refund | 2.44 | 2.44 | 0.0 | 0.0 | 0.0 | 502.03 | false | false | - | 01 March 2024 | Repayment | 7.22 | 1.92 | 5.3 | 0.0 | 0.0 | 500.11 | false | false | - | 01 April 2024 | Repayment | 254.83 | 250.64 | 4.19 | 0.0 | 0.0 | 249.47 | false | false | - | 01 May 2024 | Repayment | 251.49 | 249.47 | 2.02 | 0.0 | 0.0 | 0.0 | false | false | + | 01 March 2024 | Repayment | 7.24 | 1.94 | 5.3 | 0.0 | 0.0 | 500.09 | false | false | + | 01 April 2024 | Repayment | 254.84 | 250.65 | 4.19 | 0.0 | 0.0 | 249.44 | false | false | + | 01 May 2024 | Repayment | 251.46 | 249.44 | 2.02 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Accrual | 15.98 | 0.0 | 15.98 | 0.0 | 0.0 | 0.0 | false | false | Then Loan status will be "CLOSED_OBLIGATIONS_MET" @@ -4487,10 +4487,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 07 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | | 752.34 | 247.66 | 7.17 | 0.0 | 0.0 | 254.83 | 0.0 | 0.0 | 0.0 | 254.83 | - | 2 | 29 | 01 March 2024 | | 503.41 | 248.93 | 5.9 | 0.0 | 0.0 | 254.83 | 0.0 | 0.0 | 0.0 | 254.83 | - | 3 | 31 | 01 April 2024 | | 252.8 | 250.61 | 4.22 | 0.0 | 0.0 | 254.83 | 0.0 | 0.0 | 0.0 | 254.83 | - | 4 | 30 | 01 May 2024 | | 0.0 | 252.8 | 2.05 | 0.0 | 0.0 | 254.85 | 0.0 | 0.0 | 0.0 | 254.85 | + | 1 | 31 | 01 February 2024 | | 752.33 | 247.67 | 7.17 | 0.0 | 0.0 | 254.84 | 0.0 | 0.0 | 0.0 | 254.84 | + | 2 | 29 | 01 March 2024 | | 503.39 | 248.94 | 5.9 | 0.0 | 0.0 | 254.84 | 0.0 | 0.0 | 0.0 | 254.84 | + | 3 | 31 | 01 April 2024 | | 252.77 | 250.62 | 4.22 | 0.0 | 0.0 | 254.84 | 0.0 | 0.0 | 0.0 | 254.84 | + | 4 | 30 | 01 May 2024 | | 0.0 | 252.77 | 2.05 | 0.0 | 0.0 | 254.82 | 0.0 | 0.0 | 0.0 | 254.82 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 1000.0 | 19.34 | 0.0 | 0.0 | 1019.34 | 0.0 | 0.0 | 0.0 | 1019.34 | @@ -4499,23 +4499,23 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 01 January 2024 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 250.0 | false | false | | 07 January 2024 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | When Admin sets the business date to "01 February 2024" - And Customer makes "AUTOPAY" repayment on "01 February 2024" with 254.83 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 February 2024" with 254.84 EUR transaction amount When Admin sets the business date to "01 March 2024" - And Customer makes "AUTOPAY" repayment on "01 March 2024" with 254.83 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 March 2024" with 254.84 EUR transaction amount When Admin sets the business date to "01 April 2024" - And Customer makes "AUTOPAY" repayment on "01 April 2024" with 254.83 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 April 2024" with 254.84 EUR transaction amount When Admin sets the business date to "01 May 2024" - And Customer makes "AUTOPAY" repayment on "01 May 2024" with 254.85 EUR transaction amount + And Customer makes "AUTOPAY" repayment on "01 May 2024" with 254.82 EUR transaction amount When Admin sets the business date to "10 May 2024" When Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "10 May 2024" with 500 EUR transaction amount and system-generated Idempotency key 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 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 07 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 752.34 | 247.66 | 7.17 | 0.0 | 0.0 | 254.83 | 254.83 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 01 March 2024 | 503.41 | 248.93 | 5.9 | 0.0 | 0.0 | 254.83 | 254.83 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 01 April 2024 | 252.8 | 250.61 | 4.22 | 0.0 | 0.0 | 254.83 | 254.83 | 0.0 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 252.8 | 2.05 | 0.0 | 0.0 | 254.85 | 254.85 | 0.0 | 0.0 | 0.0 | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 752.33 | 247.67 | 7.17 | 0.0 | 0.0 | 254.84 | 254.84 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 01 March 2024 | 503.39 | 248.94 | 5.9 | 0.0 | 0.0 | 254.84 | 254.84 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 01 April 2024 | 252.77 | 250.62 | 4.22 | 0.0 | 0.0 | 254.84 | 254.84 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 252.77 | 2.05 | 0.0 | 0.0 | 254.82 | 254.82 | 0.0 | 0.0 | 0.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 | 19.34 | 0.0 | 0.0 | 1019.34 | 1019.34 | 0.0 | 0.0 | 0.0 | @@ -4523,10 +4523,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 01 January 2024 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 250.0 | false | false | | 07 January 2024 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 February 2024 | Repayment | 254.83 | 247.66 | 7.17 | 0.0 | 0.0 | 752.34 | false | false | - | 01 March 2024 | Repayment | 254.83 | 248.93 | 5.9 | 0.0 | 0.0 | 503.41 | false | false | - | 01 April 2024 | Repayment | 254.83 | 250.61 | 4.22 | 0.0 | 0.0 | 252.8 | false | false | - | 01 May 2024 | Repayment | 254.85 | 252.8 | 2.05 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Repayment | 254.84 | 247.67 | 7.17 | 0.0 | 0.0 | 752.33 | false | false | + | 01 March 2024 | Repayment | 254.84 | 248.94 | 5.9 | 0.0 | 0.0 | 503.39 | false | false | + | 01 April 2024 | Repayment | 254.84 | 250.62 | 4.22 | 0.0 | 0.0 | 252.77 | false | false | + | 01 May 2024 | Repayment | 254.82 | 252.77 | 2.05 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Accrual | 19.34 | 0.0 | 19.34 | 0.0 | 0.0 | 0.0 | false | false | | 10 May 2024 | Payout Refund | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | | 10 May 2024 | Interest Refund | 14.01 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | @@ -4536,10 +4536,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 250.0 | | | 0.0 | | 0.0 | 0.0 | | | | | | | 07 January 2024 | | 750.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 31 | 01 February 2024 | 01 February 2024 | 752.34 | 247.66 | 7.17 | 0.0 | 0.0 | 254.83 | 254.83 | 0.0 | 0.0 | 0.0 | - | 2 | 29 | 01 March 2024 | 01 March 2024 | 503.41 | 248.93 | 5.9 | 0.0 | 0.0 | 254.83 | 254.83 | 0.0 | 0.0 | 0.0 | - | 3 | 31 | 01 April 2024 | 01 April 2024 | 252.8 | 250.61 | 4.22 | 0.0 | 0.0 | 254.83 | 254.83 | 0.0 | 0.0 | 0.0 | - | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 252.8 | 2.05 | 0.0 | 0.0 | 254.85 | 254.85 | 0.0 | 0.0 | 0.0 | + | 1 | 31 | 01 February 2024 | 01 February 2024 | 752.33 | 247.67 | 7.17 | 0.0 | 0.0 | 254.84 | 254.84 | 0.0 | 0.0 | 0.0 | + | 2 | 29 | 01 March 2024 | 01 March 2024 | 503.39 | 248.94 | 5.9 | 0.0 | 0.0 | 254.84 | 254.84 | 0.0 | 0.0 | 0.0 | + | 3 | 31 | 01 April 2024 | 01 April 2024 | 252.77 | 250.62 | 4.22 | 0.0 | 0.0 | 254.84 | 254.84 | 0.0 | 0.0 | 0.0 | + | 4 | 30 | 01 May 2024 | 01 May 2024 | 0.0 | 252.77 | 2.05 | 0.0 | 0.0 | 254.82 | 254.82 | 0.0 | 0.0 | 0.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 | 19.34 | 0.0 | 0.0 | 1019.34 | 1019.34 | 0.0 | 0.0 | 0.0 | @@ -4547,10 +4547,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 01 January 2024 | Disbursement | 250.0 | 0.0 | 0.0 | 0.0 | 0.0 | 250.0 | false | false | | 07 January 2024 | Disbursement | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | - | 01 February 2024 | Repayment | 254.83 | 247.66 | 7.17 | 0.0 | 0.0 | 752.34 | false | false | - | 01 March 2024 | Repayment | 254.83 | 248.93 | 5.9 | 0.0 | 0.0 | 503.41 | false | false | - | 01 April 2024 | Repayment | 254.83 | 250.61 | 4.22 | 0.0 | 0.0 | 252.8 | false | false | - | 01 May 2024 | Repayment | 254.85 | 252.8 | 2.05 | 0.0 | 0.0 | 0.0 | false | false | + | 01 February 2024 | Repayment | 254.84 | 247.67 | 7.17 | 0.0 | 0.0 | 752.33 | false | false | + | 01 March 2024 | Repayment | 254.84 | 248.94 | 5.9 | 0.0 | 0.0 | 503.39 | false | false | + | 01 April 2024 | Repayment | 254.84 | 250.62 | 4.22 | 0.0 | 0.0 | 252.77 | false | false | + | 01 May 2024 | Repayment | 254.82 | 252.77 | 2.05 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Accrual | 19.34 | 0.0 | 19.34 | 0.0 | 0.0 | 0.0 | false | false | | 10 May 2024 | Payout Refund | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | | 10 May 2024 | Interest Refund | 14.01 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 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 1bb00af6172..94bc3f6eb6e 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,9 +26,12 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.TreeSet; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; import java.util.function.Consumer; import lombok.Data; import lombok.experimental.Accessors; @@ -62,7 +65,8 @@ private ProgressiveLoanInterestScheduleModel(final List repayme final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) { this.mc = mc; - this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods); + this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods, + (previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod, mc)); this.interestRates = new TreeSet<>(interestRates); this.loanProductRelatedDetail = loanProductRelatedDetail; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; @@ -74,11 +78,20 @@ public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) { installmentAmountInMultiplesOf, mc); } - private List copyRepaymentPeriods(final List repaymentPeriods) { + public ProgressiveLoanInterestScheduleModel emptyCopy() { + final List repaymentPeriodCopies = copyRepaymentPeriods(repaymentPeriods, + (previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod.getFromDate(), + repaymentPeriod.getDueDate(), repaymentPeriod.getEmi().zero(), mc)); + return new ProgressiveLoanInterestScheduleModel(repaymentPeriodCopies, interestRates, loanProductRelatedDetail, + installmentAmountInMultiplesOf, mc); + } + + private List copyRepaymentPeriods(final List repaymentPeriods, + final BiFunction repaymentCopyFunction) { final List repaymentCopies = new ArrayList<>(repaymentPeriods.size()); RepaymentPeriod previousPeriod = null; for (RepaymentPeriod repaymentPeriod : repaymentPeriods) { - RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentPeriod, mc); + RepaymentPeriod currentPeriod = repaymentCopyFunction.apply(previousPeriod, repaymentPeriod); previousPeriod = currentPeriod; repaymentCopies.add(currentPeriod); } @@ -229,4 +242,40 @@ public Optional findRepaymentPeriod(@NotNull LocalDate transact .filter(period -> isInPeriod(transactionDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))// .findFirst(); } + + public boolean isEmpty() { + return repaymentPeriods.stream() // + .filter(rp -> !rp.getEmi().isZero()) // + .findFirst() // + .isEmpty(); // + } + + /** + * This method gives you repayment pairs to copy attributes. + * + * @param periodFromDueDate + * Copy from this due periods. + * @param copyFromPeriods + * Copy source + * @param copyConsumer + * Consumer to copy attributes. Params: (from, to) + */ + public void copyPeriodsFrom(final LocalDate periodFromDueDate, List copyFromPeriods, + BiConsumer copyConsumer) { + if (copyFromPeriods.isEmpty()) { + return; + } + final Iterator actualIterator = repaymentPeriods.iterator(); + final Iterator copyFromIterator = copyFromPeriods.iterator(); + while (actualIterator.hasNext()) { + final RepaymentPeriod copyFromPeriod = copyFromIterator.next(); + RepaymentPeriod actualPeriod = actualIterator.next(); + while (actualIterator.hasNext() && !copyFromPeriod.getDueDate().isEqual(actualPeriod.getDueDate())) { + actualPeriod = actualIterator.next(); + } + if (!actualPeriod.getDueDate().isBefore(periodFromDueDate)) { + copyConsumer.accept(copyFromPeriod, actualPeriod); + } + } + } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EmiChangeOperation.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EmiChangeOperation.java new file mode 100644 index 00000000000..9d404aa4b1c --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EmiChangeOperation.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.calc; + +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.apache.fineract.organisation.monetary.domain.Money; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class EmiChangeOperation { + + public enum Action { + DISBURSEMENT, INTEREST_RATE_CHANGE + } + + private final Action action; + private final LocalDate submittedOnDate; + + private final Money amount; + private final BigDecimal interestRate; + + public static EmiChangeOperation disburse(final LocalDate disbursementDueDate, final Money disbursedAmount) { + return new EmiChangeOperation(EmiChangeOperation.Action.DISBURSEMENT, disbursementDueDate, disbursedAmount, null); + } + + public static EmiChangeOperation changeInterestRate(final LocalDate newInterestSubmittedOnDate, final BigDecimal newInterestRate) { + return new EmiChangeOperation(EmiChangeOperation.Action.INTEREST_RATE_CHANGE, newInterestSubmittedOnDate, null, newInterestRate); + } +} 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 6e4e2ce834c..994d16432e3 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 @@ -102,9 +102,16 @@ public Optional findRepaymentPeriod(final ProgressiveLoanIntere @Override public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate, final Money disbursedAmount) { - scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, scheduleModel.zero()) + addDisbursement(scheduleModel, EmiChangeOperation.disburse(disbursementDueDate, disbursedAmount)); + } + + private void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) { + scheduleModel + .changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), operation.getAmount(), + scheduleModel.zero()) .ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors( - getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, disbursementDueDate), scheduleModel)); + getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, operation.getSubmittedOnDate()), scheduleModel, + operation)); } private LocalDate getEffectiveRepaymentDueDate(final ProgressiveLoanInterestScheduleModel scheduleModel, @@ -125,13 +132,18 @@ private LocalDate getEffectiveRepaymentDueDate(final ProgressiveLoanInterestSche @Override public void changeInterestRate(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate newInterestSubmittedOnDate, final BigDecimal newInterestRate) { - final LocalDate interestRateChangeEffectiveDate = newInterestSubmittedOnDate.minusDays(1); - scheduleModel.addInterestRate(interestRateChangeEffectiveDate, newInterestRate); + changeInterestRate(scheduleModel, EmiChangeOperation.changeInterestRate(newInterestSubmittedOnDate, newInterestRate)); + } + + private void changeInterestRate(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) { + final LocalDate interestRateChangeEffectiveDate = operation.getSubmittedOnDate().minusDays(1); + scheduleModel.addInterestRate(interestRateChangeEffectiveDate, operation.getInterestRate()); scheduleModel .changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.zero(), scheduleModel.zero()) .ifPresent(repaymentPeriod -> calculateEMIValueAndRateFactors( - getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel)); + getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel, + operation)); } @Override @@ -283,15 +295,24 @@ private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate( /** * Calculate Equal Monthly Installment value and Rate Factor -1 values for calculate Interest */ - void calculateEMIValueAndRateFactors(final LocalDate calculateFromRepaymentPeriodDueDate, - final ProgressiveLoanInterestScheduleModel scheduleModel) { + private void calculateEMIValueAndRateFactors(final LocalDate calculateFromRepaymentPeriodDueDate, + final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) { final List relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate); + final boolean onlyOnActualModelShouldApply = scheduleModel.isEmpty() + || operation.getAction() == EmiChangeOperation.Action.INTEREST_RATE_CHANGE; + calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel); calculateOutstandingBalance(scheduleModel); - calculateEMIOnPeriods(relatedRepaymentPeriods, scheduleModel); + if (onlyOnActualModelShouldApply) { + calculateEMIOnActualModel(relatedRepaymentPeriods, scheduleModel); + } else { + calculateEMIOnNewEmptyModelAndMerge(relatedRepaymentPeriods, scheduleModel, operation); + } calculateOutstandingBalance(scheduleModel); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); - checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods); + if (onlyOnActualModelShouldApply) { + checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, relatedRepaymentPeriods); + } } private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) { @@ -378,19 +399,19 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv * * @return Rate Interest Rate in fraction format */ - BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate, MathContext mc) { + private BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate, MathContext mc) { return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, mc); } /** * * Calculate rate factors from ONLY repayment periods */ - void calculateRateFactorForPeriods(final List repaymentPeriods, + private void calculateRateFactorForPeriods(final List repaymentPeriods, final ProgressiveLoanInterestScheduleModel scheduleModel) { repaymentPeriods.forEach(repaymentPeriod -> calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel)); } - void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod, + private void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod, final ProgressiveLoanInterestScheduleModel scheduleModel) { repaymentPeriod.getInterestPeriods().forEach(interestPeriod -> { interestPeriod.setRateFactor(calculateRateFactorPerPeriod(scheduleModel, repaymentPeriod, interestPeriod.getFromDate(), @@ -403,8 +424,8 @@ void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod /** * Calculate Rate Factor for an exact Period */ - BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, final RepaymentPeriod repaymentPeriod, - final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) { + private BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, + final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) { final MathContext mc = scheduleModel.mc(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate), @@ -443,7 +464,8 @@ BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleMod * @param interestPeriodDueDate * @return */ - BigDecimal calculatePeriodFractions(final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate, MathContext mc) { + private BigDecimal calculatePeriodFractions(final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate, + MathContext mc) { BigDecimal cumulatedRateFactor = BigDecimal.ZERO; int actualYear = interestPeriodFromDate.getYear(); int endYear = interestPeriodDueDate.getYear(); @@ -473,7 +495,7 @@ BigDecimal calculatePeriodFractions(final LocalDate interestPeriodFromDate, fina * @param calculatedDaysInPeriod * @return */ - BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate, + private BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate, final PeriodFrequencyType repaymentFrequency, final BigDecimal repaymentEvery, final BigDecimal daysInMonth, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, final MathContext mc) { @@ -488,7 +510,7 @@ BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecima }; } - void calculateEMIOnPeriods(final List repaymentPeriods, final ProgressiveLoanInterestScheduleModel scheduleModel) { + private void calculateEMIOnActualModel(List repaymentPeriods, ProgressiveLoanInterestScheduleModel scheduleModel) { if (repaymentPeriods.isEmpty()) { return; } @@ -496,7 +518,7 @@ void calculateEMIOnPeriods(final List repaymentPeriods, final P final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc)); final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc)); final RepaymentPeriod startPeriod = repaymentPeriods.get(0); - // TODO: double check + final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation(); final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrencyData(), @@ -511,7 +533,22 @@ void calculateEMIOnPeriods(final List repaymentPeriods, final P }); } - Money applyInstallmentAmountInMultiplesOf(final ProgressiveLoanInterestScheduleModel scheduleModel, + private void calculateEMIOnNewEmptyModelAndMerge(List repaymentPeriods, + ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) { + if (repaymentPeriods.isEmpty()) { + return; + } + final ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.emptyCopy(); + addDisbursement(scheduleModelCopy, operation); + + final LocalDate firstDueDate = repaymentPeriods.get(0).getDueDate(); + scheduleModel.copyPeriodsFrom(firstDueDate, scheduleModelCopy.repaymentPeriods(), (newRepaymentPeriod, actualRepaymentPeriod) -> { + actualRepaymentPeriod.setEmi(actualRepaymentPeriod.getEmi().plus(newRepaymentPeriod.getEmi())); + actualRepaymentPeriod.setOriginalEmi(actualRepaymentPeriod.getOriginalEmi().plus(newRepaymentPeriod.getOriginalEmi())); + }); + } + + private Money applyInstallmentAmountInMultiplesOf(final ProgressiveLoanInterestScheduleModel scheduleModel, final Money equalMonthlyInstallment) { return scheduleModel.installmentAmountInMultiplesOf() != null ? Money.roundToMultiplesOf(equalMonthlyInstallment, scheduleModel.installmentAmountInMultiplesOf()) @@ -534,7 +571,7 @@ public EmiAdjustment getEmiAdjustment(final List repaymentPerio /** * Calculate Rate Factor Product from rate factors */ - BigDecimal calculateRateFactorPlus1N(final List periods, MathContext mc) { + private BigDecimal calculateRateFactorPlus1N(final List periods, MathContext mc) { return periods.stream().map(RepaymentPeriod::getRateFactorPlus1).reduce(BigDecimal.ONE, (BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc)); } @@ -542,7 +579,7 @@ BigDecimal calculateRateFactorPlus1N(final List periods, MathCo /** * Summarize Fn values */ - BigDecimal calculateFnResult(final List periods, final MathContext mc) { + private BigDecimal calculateFnResult(final List periods, final MathContext mc) { return periods.stream()// .skip(1)// .map(RepaymentPeriod::getRateFactorPlus1)// @@ -552,8 +589,8 @@ BigDecimal calculateFnResult(final List periods, final MathCont /** * Calculate the EMI (Equal Monthly Installment) value */ - BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult, - MathContext mc) { + private BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, + final BigDecimal fnResult, MathContext mc) { return rateFactorPlus1N.multiply(outstandingBalanceForRest, mc).divide(fnResult, mc); } @@ -581,8 +618,8 @@ BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal * * @return Rate Factor for period */ - BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear, - final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) { + private BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final BigDecimal repaymentEvery, + final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) { return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc); } @@ -611,8 +648,8 @@ BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final Bi * * @return Rate Factor for period */ - BigDecimal rateFactorByRepaymentEveryWeek(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear, - final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) { + private BigDecimal rateFactorByRepaymentEveryWeek(final BigDecimal interestRate, final BigDecimal repaymentEvery, + final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) { return rateFactorByRepaymentPeriod(interestRate, ONE_WEEK_IN_DAYS, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc); } @@ -678,7 +715,7 @@ BigDecimal rateFactorByRepaymentEveryMonth(final BigDecimal interestRate, final * * @return Rate Factor for period */ - BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays, + private BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays, final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, final MathContext mc) { if (MathUtil.isZero(calculatedDaysInPeriod)) { @@ -697,7 +734,7 @@ BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigD * Calculate Rate Factor based on Partial Period * */ - BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery, + private BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, final MathContext mc) { if (MathUtil.isZero(calculatedDaysInPeriod)) { 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 04b6e43ce37..edf91cd7a9b 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 @@ -953,14 +953,16 @@ public void test_multiDisbursedAmt150InSamePeriod_dayInYears360_daysInMonth30_re disbursedAmount = toMoney(25.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount); - checkPeriod(interestSchedule, 0, 0, 29.94, 0.001019591398, 0.00, 1.15, 28.79, 146.21); - checkPeriod(interestSchedule, 0, 1, 29.94, 0.000764693548, 0.08, 1.15, 28.79, 146.21); - checkPeriod(interestSchedule, 0, 2, 29.94, 0.006117548387, 1.07, 1.15, 28.79, 146.21); - checkPeriod(interestSchedule, 1, 0, 29.94, 0.007901833333, 1.16, 28.78, 117.43); - checkPeriod(interestSchedule, 2, 0, 29.94, 0.007901833333, 0.93, 29.01, 88.42); - checkPeriod(interestSchedule, 3, 0, 29.94, 0.007901833333, 0.70, 29.24, 59.18); - checkPeriod(interestSchedule, 4, 0, 29.94, 0.007901833333, 0.47, 29.47, 29.71); - checkPeriod(interestSchedule, 5, 0, 29.94, 0.007901833333, 0.23, 29.71, 0.0); + checkTotalInterestDue(interestSchedule, 4.65); + + checkPeriod(interestSchedule, 0, 0, 29.93, 0.001019591398, 0.00, 1.15, 28.78, 146.22); + checkPeriod(interestSchedule, 0, 1, 29.93, 0.000764693548, 0.08, 1.15, 28.78, 146.22); + checkPeriod(interestSchedule, 0, 2, 29.93, 0.006117548387, 1.07, 1.15, 28.78, 146.22); + checkPeriod(interestSchedule, 1, 0, 29.93, 0.007901833333, 1.16, 28.77, 117.45); + checkPeriod(interestSchedule, 2, 0, 29.93, 0.007901833333, 0.93, 29.00, 88.45); + checkPeriod(interestSchedule, 3, 0, 29.93, 0.007901833333, 0.70, 29.23, 59.22); + checkPeriod(interestSchedule, 4, 0, 29.93, 0.007901833333, 0.47, 29.46, 29.76); + checkPeriod(interestSchedule, 5, 0, 30.0, 0.007901833333, 0.24, 29.76, 0.0); } @Test @@ -1034,13 +1036,13 @@ public void test_multidisbursement_total_repay1st_dayInYears360_daysInMonth30_re emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 1, 1), toMoney(51.03)); emiCalculator.payPrincipal(interestSchedule, LocalDate.of(2024, 7, 1), LocalDate.of(2024, 1, 1), toMoney(44.85)); - Assertions.assertEquals(0.0, toDouble(interestSchedule.getTotalDueInterest())); + checkTotalInterestDue(interestSchedule, 0.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), toMoney(200.0)); - Assertions.assertEquals(4.20, toDouble(interestSchedule.getTotalDueInterest())); + checkTotalInterestDue(interestSchedule, 4.11); - checkEmi(interestSchedule, 4, 84.03); - checkEmi(interestSchedule, 5, 84.05); + checkEmi(interestSchedule, 4, 85.05); + checkEmi(interestSchedule, 5, 78.86); } @Test @@ -1398,6 +1400,11 @@ private static void checkEmi(final ProgressiveLoanInterestScheduleModel interest Assertions.assertEquals(emiValue, toDouble(interestScheduleModel.repaymentPeriods().get(repaymentIdx).getEmi())); } + private static void checkTotalInterestDue(final ProgressiveLoanInterestScheduleModel interestScheduleModel, + final double totalInterestDue) { + Assertions.assertEquals(totalInterestDue, toDouble(interestScheduleModel.getTotalDueInterest())); + } + private static void checkPeriod(final ProgressiveLoanInterestScheduleModel interestScheduleModel, final int repaymentIdx, final int interestIdx, final double emiValue, final double rateFactor, final double interestDue, final double principalDue, final double remaingBalance) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java index 2ebad05818b..7edca277107 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java @@ -889,7 +889,7 @@ public void verifyUC10() { transaction(171.29, "Repayment", "01 May 2021"), // transaction(171.29, "Repayment", "01 June 2021"), // transaction(171.32, "Repayment", "01 July 2021"), // - transaction(27.77, "Accrual", "01 July 2021") // + transaction(27.76, "Accrual", "01 July 2021") // ); // }); runAt("11 July 2021", () -> { @@ -909,8 +909,8 @@ public void verifyUC10() { transaction(171.29, "Repayment", "01 June 2021"), // transaction(171.32, "Repayment", "01 July 2021"), // transaction(500.0, "Payout Refund", "11 July 2021"), // - transaction(20.41, "Interest Refund", "11 July 2021"), // - transaction(27.77, "Accrual", "01 July 2021") // + transaction(20.40, "Interest Refund", "11 July 2021"), // + transaction(27.76, "Accrual", "01 July 2021") // ); // }); }