This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 5649aa3ded FINERACT-2281: fix unrecognized interest handling when
first period is unpaid
5649aa3ded is described below
commit 5649aa3dedd1fd6523a4d133737e549bb1e75d1c
Author: mark.vituska <[email protected]>
AuthorDate: Thu Jun 5 11:43:24 2025 +0200
FINERACT-2281: fix unrecognized interest handling when first period is
unpaid
---
.../resources/features/LoanAccrualActivity.feature | 240 ++++++++++++++++++++-
.../loanproduct/calc/ProgressiveEMICalculator.java | 151 +++++++++----
.../loanproduct/calc/data/InterestPeriod.java | 2 +-
.../loanproduct/calc/data/RepaymentPeriod.java | 53 +++--
4 files changed, 373 insertions(+), 73 deletions(-)
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature
index d766312ace..f5a0af5635 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature
@@ -6195,7 +6195,7 @@ Feature: LoanAccrualActivity
Then Loan Repayment schedule has 12 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 |
| | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
- | 1 | 31 | 21 April 2025 | | 218.82 |
123.64 | 0.77 | 0.0 | 0.0 | 124.41 | 83.64 | 83.64 | 0.0
| 40.77 |
+ | 1 | 31 | 21 April 2025 | | 218.82 |
123.64 | 0.84 | 0.0 | 0.0 | 124.48 | 83.64 | 83.64 | 0.0
| 40.84 |
| 2 | 30 | 21 May 2025 | 21 March 2025 | 195.18 |
23.64 | 0.0 | 0.0 | 0.0 | 23.64 | 23.64 | 23.64 | 0.0
| 0.0 |
| 3 | 31 | 21 June 2025 | 21 March 2025 | 171.54 |
23.64 | 0.0 | 0.0 | 0.0 | 23.64 | 23.64 | 23.64 | 0.0
| 0.0 |
| 4 | 30 | 21 July 2025 | 21 March 2025 | 147.9 |
23.64 | 0.0 | 0.0 | 0.0 | 23.64 | 23.64 | 23.64 | 0.0
| 0.0 |
@@ -6209,7 +6209,7 @@ Feature: LoanAccrualActivity
| 12 | 28 | 21 March 2026 | 21 March 2025 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
0.0 |
And Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
- | 342.46 | 0.77 | 0.0 | 0.0 | 343.23 | 302.46 | 302.46
| 0.0 | 40.77 |
+ | 342.46 | 0.84 | 0.0 | 0.0 | 343.3 | 302.46 | 302.46
| 0.0 | 40.84 |
And Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
@@ -6759,11 +6759,11 @@ Feature: LoanAccrualActivity
When Admin sets the business date to "29 April 2025"
And Admin runs inline COB job for Loan
And Customer makes "AUTOPAY" repayment on "29 April 2025" with 100 EUR
transaction amount
- Then Loan has 1.93 outstanding amount
+ Then Loan has 2.6 outstanding amount
Then Loan Repayment schedule has 12 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 |
| | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
- | 1 | 31 | 21 April 2025 | | 222.26 |
120.2 | 1.93 | 0.0 | 0.0 | 122.13 |120.2 | 20.2 |
100.0| 1.93 |
+ | 1 | 31 | 21 April 2025 | | 222.26 |
120.2 | 2.6 | 0.0 | 0.0 | 122.8 |120.2 | 20.2 |
100.0| 2.6 |
| 2 | 30 | 21 May 2025 | 21 March 2025 | 202.06 |
20.2 | 0.0 | 0.0 | 0.0 | 20.2 | 20.2 | 20.2 | 0.0
| 0.0 |
| 3 | 31 | 21 June 2025 | 21 March 2025 | 181.86 |
20.2 | 0.0 | 0.0 | 0.0 | 20.2 | 20.2 | 20.2 | 0.0
| 0.0 |
| 4 | 30 | 21 July 2025 | 21 March 2025 | 161.66 |
20.2 | 0.0 | 0.0 | 0.0 | 20.2 | 20.2 | 20.2 | 0.0
| 0.0 |
@@ -6777,7 +6777,7 @@ Feature: LoanAccrualActivity
| 12 | 28 | 21 March 2026 | 21 March 2025 | 0.0 |
20.26 | 0.0 | 0.0 | 0.0 | 20.26 | 20.26 | 20.26 | 0.0
| 0.0 |
And Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
- | 342.46 | 1.93 | 0.0 | 0.0 | 344.39 | 342.46 | 242.46
| 100.0 | 1.93 |
+ | 342.46 | 2.6 | 0.0 | 0.0 | 345.06 | 342.46 | 242.46
| 100.0 | 2.6 |
And Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
@@ -6798,13 +6798,13 @@ Feature: LoanAccrualActivity
Then Credit Balance Refund transaction on active loan "29 April 2025" with
100 EUR transaction amount will result an error
When Admin sets the business date to "06 May 2025"
And Admin runs inline COB job for Loan
- And Customer makes "AUTOPAY" repayment on "06 May 2025" with 1.93 EUR
transaction amount
+ And Customer makes "AUTOPAY" repayment on "06 May 2025" with 2.6 EUR
transaction amount
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
Then Loan has 0 outstanding amount
Then Loan Repayment schedule has 12 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 |
| | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
- | 1 | 31 | 21 April 2025 | 06 May 2025 | 222.26 |
120.2 | 1.93 | 0.0 | 0.0 | 122.13 |122.13 | 20.2 |
101.93 | 0.0 |
+ | 1 | 31 | 21 April 2025 | 06 May 2025 | 222.26 |
120.2 | 2.6 | 0.0 | 0.0 | 122.8 |122.8 | 20.2 |
102.6 | 0.0 |
| 2 | 30 | 21 May 2025 | 21 March 2025 | 202.06 |
20.2 | 0.0 | 0.0 | 0.0 | 20.2 | 20.2 | 20.2 | 0.0
| 0.0 |
| 3 | 31 | 21 June 2025 | 21 March 2025 | 181.86 |
20.2 | 0.0 | 0.0 | 0.0 | 20.2 | 20.2 | 20.2 | 0.0
| 0.0 |
| 4 | 30 | 21 July 2025 | 21 March 2025 | 161.66 |
20.2 | 0.0 | 0.0 | 0.0 | 20.2 | 20.2 | 20.2 | 0.0
| 0.0 |
@@ -6818,7 +6818,7 @@ Feature: LoanAccrualActivity
| 12 | 28 | 21 March 2026 | 21 March 2025 | 0.0 |
20.26 | 0.0 | 0.0 | 0.0 | 20.26 | 20.26 | 20.26 | 0.0
| 0.0 |
And Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
- | 342.46 | 1.93 | 0.0 | 0.0 | 344.39 | 344.39 | 242.46
| 101.93 | 0.0 |
+ | 342.46 | 2.6 | 0.0 | 0.0 | 345.06 | 345.06 | 242.46
| 102.6 | 0.0 |
And Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
@@ -6836,8 +6836,7 @@ Feature: LoanAccrualActivity
| 28 April 2025 | Accrual | 0.08 | 0.0 | 0.08
| 0.0 | 0.0 | 0.0 | false | false |
| 29 April 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
| 29 April 2025 | Accrual | 0.09 | 0.0 | 0.09
| 0.0 | 0.0 | 0.0 | false | false |
- | 06 May 2025 | Repayment | 1.93 | 0.0 | 1.93
| 0.0 | 0.0 | 0.0 | false | false |
- | 06 May 2025 | Accrual Adjustment | 0.67 | 0.0 | 0.67
| 0.0 | 0.0 | 0.0 | false | false |
+ | 06 May 2025 | Repayment | 2.6 | 0.0 | 2.6
| 0.0 | 0.0 | 0.0 | false | false |
# - CBR on closed loan is forbidden - #
Then Credit Balance Refund transaction on active loan "06 May 2025" with
100 EUR transaction amount will result an error
@@ -7157,4 +7156,223 @@ Feature: LoanAccrualActivity
| 07 May 2025 | Accrual | 0.05 | 0.0 | 0.05
| 0.0 | 0.0 | 0.0 | false | false |
| 07 May 2025 | Accrual Activity | 1.18 | 0.0 | 1.18
| 0.0 | 0.0 | 0.0 | false | false |
# - CBR on closed loan is forbidden - #
- Then Credit Balance Refund transaction on active loan "07 May 2025" with
72.35 EUR transaction amount will result an error
\ No newline at end of file
+ Then Credit Balance Refund transaction on active loan "07 May 2025" with
72.35 EUR transaction amount will result an error
+
+ @TestRailId:C3736
+ Scenario: Verify that interest is calculated after last unpaid period in
case of reversed repayment made before MIR and CBR for progressive loan with
downpayment
+ When Admin sets the business date to "21 March 2025"
+ And Admin creates a client with random data
+ And Admin creates a fully customized loan with the following data:
+ | LoanProduct
| submitted on date | with Principal | ANNUAL interest rate % |
interest type | interest calculation period | amortization type |
loanTermFrequency | loanTermFrequencyType | repaymentEvery |
repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment |
graceOnInterestPayment | interest free period | Payment strategy |
+ |
LP2_ADV_PYMNT_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE_DOWNPAYMENT
| 21 March 2025 | 242.46 | 29.99 |
DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 12
| MONTHS | 1 | MONTHS | 12
| 0 | 0 | 0
| ADVANCED_PAYMENT_ALLOCATION |
+ And Admin successfully approves the loan on "21 March 2025" with "242.46"
amount and expected disbursement date on "21 March 2025"
+ And Admin successfully disburse the loan on "21 March 2025" with "242.46"
EUR transaction amount
+ Then Loan Repayment schedule has 13 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 |
+ | | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 21 March 2025 | | 181.84 |
60.62 | 0.0 | 0.0 | 0.0 | 60.62 | 0.0 | 0.0 | 0.0
| 60.62 |
+ | 2 | 31 | 21 April 2025 | | 168.65 |
13.19 | 4.54 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 3 | 30 | 21 May 2025 | | 155.13 |
13.52 | 4.21 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 4 | 31 | 21 June 2025 | | 141.28 |
13.85 | 3.88 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 5 | 30 | 21 July 2025 | | 127.08 | 14.2
| 3.53 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0 |
17.73 |
+ | 6 | 31 | 21 August 2025 | | 112.53 |
14.55 | 3.18 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 7 | 31 | 21 September 2025 | | 97.61 |
14.92 | 2.81 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 8 | 30 | 21 October 2025 | | 82.32 |
15.29 | 2.44 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 9 | 31 | 21 November 2025 | | 66.65 |
15.67 | 2.06 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 10 | 30 | 21 December 2025 | | 50.59 |
16.06 | 1.67 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 11 | 31 | 21 January 2026 | | 34.12 |
16.47 | 1.26 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 12 | 31 | 21 February 2026 | | 17.24 |
16.88 | 0.85 | 0.0 | 0.0 | 17.73 | 0.0 | 0.0 | 0.0
| 17.73 |
+ | 13 | 28 | 21 March 2026 | | 0.0 |
17.24 | 0.43 | 0.0 | 0.0 | 17.67 | 0.0 | 0.0 | 0.0
| 17.67 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 242.46 | 30.86 | 0.0 | 0.0 | 273.32 | 0.0 | 0.0
| 0.0 | 273.32 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ And Customer makes "AUTOPAY" repayment on "21 March 2025" with 100 EUR
transaction amount
+ And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY"
payment type on "21 March 2025" with 242.46 EUR transaction amount and
system-generated Idempotency key
+ Then Loan status will be "OVERPAID"
+ Then Loan has 100 overpaid amount
+ Then Loan Repayment schedule has 13 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 |
+ | | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 21 March 2025 | 21 March 2025 | 181.84 |
60.62 | 0.0 | 0.0 | 0.0 | 60.62 | 60.62 | 0.0 | 0.0
| 0.0 |
+ | 2 | 31 | 21 April 2025 | 21 March 2025 | 164.11 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 3 | 30 | 21 May 2025 | 21 March 2025 | 146.38 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 4 | 31 | 21 June 2025 | 21 March 2025 | 128.65 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 5 | 30 | 21 July 2025 | 21 March 2025 | 110.92 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 6 | 31 | 21 August 2025 | 21 March 2025 | 93.19 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 7 | 31 | 21 September 2025 | 21 March 2025 | 75.46 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 8 | 30 | 21 October 2025 | 21 March 2025 | 57.73 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 9 | 31 | 21 November 2025 | 21 March 2025 | 40.0 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 10 | 30 | 21 December 2025 | 21 March 2025 | 22.27 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 11 | 31 | 21 January 2026 | 21 March 2025 | 4.54 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 12 | 31 | 21 February 2026 | 21 March 2025 | 0.0 |
4.54 | 0.0 | 0.0 | 0.0 | 4.54 | 4.54 | 4.54 | 0.0
| 0.0 |
+ | 13 | 28 | 21 March 2026 | 21 March 2025 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
0.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 242.46 | 0.0 | 0.0 | 0.0 | 242.46 | 242.46 | 181.84
| 0.0 | 0.0 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ | 21 March 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 142.46 | false | false |
+ | 21 March 2025 | Merchant Issued Refund | 242.46 | 142.46 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ When Admin sets the business date to "28 March 2025"
+ And Admin runs inline COB job for Loan
+ When Admin makes Credit Balance Refund transaction on "28 March 2025" with
100 EUR transaction amount
+ Then Loan status will be "CLOSED_OBLIGATIONS_MET"
+ Then Loan has 0 outstanding amount
+ Then Loan Repayment schedule has 13 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 |
+ | | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 21 March 2025 | 21 March 2025 | 181.84 |
60.62 | 0.0 | 0.0 | 0.0 | 60.62 | 60.62 | 0.0 | 0.0
| 0.0 |
+ | 2 | 31 | 21 April 2025 | 21 March 2025 | 164.11 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 3 | 30 | 21 May 2025 | 21 March 2025 | 146.38 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 4 | 31 | 21 June 2025 | 21 March 2025 | 128.65 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 5 | 30 | 21 July 2025 | 21 March 2025 | 110.92 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 6 | 31 | 21 August 2025 | 21 March 2025 | 93.19 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 7 | 31 | 21 September 2025 | 21 March 2025 | 75.46 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 8 | 30 | 21 October 2025 | 21 March 2025 | 57.73 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 9 | 31 | 21 November 2025 | 21 March 2025 | 40.0 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 10 | 30 | 21 December 2025 | 21 March 2025 | 22.27 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 11 | 31 | 21 January 2026 | 21 March 2025 | 4.54 |
17.73 | 0.0 | 0.0 | 0.0 | 17.73 | 17.73 | 17.73 | 0.0
| 0.0 |
+ | 12 | 31 | 21 February 2026 | 21 March 2025 | 0.0 |
4.54 | 0.0 | 0.0 | 0.0 | 4.54 | 4.54 | 4.54 | 0.0
| 0.0 |
+ | 13 | 28 | 21 March 2026 | 21 March 2025 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
0.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 242.46 | 0.0 | 0.0 | 0.0 | 242.46 | 242.46 | 181.84
| 0.0 | 0.0 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ | 21 March 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 142.46 | false | false |
+ | 21 March 2025 | Merchant Issued Refund | 242.46 | 142.46 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ | 28 March 2025 | Credit Balance Refund | 100.0 | 0.0 | 0.0
| 0.0 | 0.0 | 0.0 | false | false |
+ When Admin sets the business date to "02 April 2025"
+ And Admin runs inline COB job for Loan
+ When Customer undo "1"th "Repayment" transaction made on "21 March 2025"
+ Then Loan Repayment schedule has 13 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 |
+ | | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 21 March 2025 | 21 March 2025 | 181.84 |
60.62 | 0.0 | 0.0 | 0.0 | 60.62 | 60.62 | 0.0 | 0.0
| 0.0 |
+ | 2 | 31 | 21 April 2025 | | 166.69 |
115.15 | 1.93 | 0.0 | 0.0 | 117.08 | 15.15 | 15.15 | 0.0
| 101.93 |
+ | 3 | 30 | 21 May 2025 | 21 March 2025 | 151.54 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 4 | 31 | 21 June 2025 | 21 March 2025 | 136.39 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 5 | 30 | 21 July 2025 | 21 March 2025 | 121.24 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 6 | 31 | 21 August 2025 | 21 March 2025 | 106.09 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 7 | 31 | 21 September 2025 | 21 March 2025 | 90.94 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 8 | 30 | 21 October 2025 | 21 March 2025 | 75.79 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 9 | 31 | 21 November 2025 | 21 March 2025 | 60.64 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 10 | 30 | 21 December 2025 | 21 March 2025 | 45.49 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 11 | 31 | 21 January 2026 | 21 March 2025 | 30.34 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 12 | 31 | 21 February 2026 | 21 March 2025 | 15.19 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 13 | 28 | 21 March 2026 | 21 March 2025 | 0.0 |
15.19 | 0.0 | 0.0 | 0.0 | 15.19 | 15.19 | 15.19 | 0.0
| 0.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 342.46 | 1.93 | 0.0 | 0.0 | 344.39 | 242.46 | 181.84
| 0.0 | 101.93 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ | 21 March 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 142.46 | true | false |
+ | 21 March 2025 | Merchant Issued Refund | 242.46 | 242.46 | 0.0
| 0.0 | 0.0 | 0.0 | false | true |
+ | 28 March 2025 | Credit Balance Refund | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 100.0 | false | true |
+ When Admin sets the business date to "21 April 2025"
+ And Admin runs inline COB job for Loan
+ Then Loan Repayment schedule has 13 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 |
+ | | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 21 March 2025 | 21 March 2025 | 181.84 |
60.62 | 0.0 | 0.0 | 0.0 | 60.62 | 60.62 | 0.0 | 0.0
| 0.0 |
+ | 2 | 31 | 21 April 2025 | | 166.69 |
115.15 | 1.93 | 0.0 | 0.0 | 117.08 | 15.15 | 15.15 | 0.0
| 101.93 |
+ | 3 | 30 | 21 May 2025 | 21 March 2025 | 151.54 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 4 | 31 | 21 June 2025 | 21 March 2025 | 136.39 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 5 | 30 | 21 July 2025 | 21 March 2025 | 121.24 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 6 | 31 | 21 August 2025 | 21 March 2025 | 106.09 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 7 | 31 | 21 September 2025 | 21 March 2025 | 90.94 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 8 | 30 | 21 October 2025 | 21 March 2025 | 75.79 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 9 | 31 | 21 November 2025 | 21 March 2025 | 60.64 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 10 | 30 | 21 December 2025 | 21 March 2025 | 45.49 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 11 | 31 | 21 January 2026 | 21 March 2025 | 30.34 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 12 | 31 | 21 February 2026 | 21 March 2025 | 15.19 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 13 | 28 | 21 March 2026 | 21 March 2025 | 0.0 |
15.19 | 0.0 | 0.0 | 0.0 | 15.19 | 15.19 | 15.19 | 0.0
| 0.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 342.46 | 1.93 | 0.0 | 0.0 | 344.39 | 242.46 | 181.84
| 0.0 | 101.93 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ | 21 March 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 142.46 | true | false |
+ | 21 March 2025 | Merchant Issued Refund | 242.46 | 242.46 | 0.0
| 0.0 | 0.0 | 0.0 | false | true |
+ | 28 March 2025 | Credit Balance Refund | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 100.0 | false | true |
+ | 20 April 2025 | Accrual | 1.85 | 0.0 | 1.85
| 0.0 | 0.0 | 0.0 | false | false |
+ When Admin sets the business date to "22 April 2025"
+ And Admin runs inline COB job for Loan
+ Then Loan Repayment schedule has 13 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 |
+ | | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 21 March 2025 | 21 March 2025 | 181.84 |
60.62 | 0.0 | 0.0 | 0.0 | 60.62 | 60.62 | 0.0 | 0.0
| 0.0 |
+ | 2 | 31 | 21 April 2025 | | 166.69 |
115.15 | 2.01 | 0.0 | 0.0 | 117.16 | 15.15 | 15.15 | 0.0
| 102.01 |
+ | 3 | 30 | 21 May 2025 | 21 March 2025 | 151.54 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 4 | 31 | 21 June 2025 | 21 March 2025 | 136.39 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 5 | 30 | 21 July 2025 | 21 March 2025 | 121.24 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 6 | 31 | 21 August 2025 | 21 March 2025 | 106.09 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 7 | 31 | 21 September 2025 | 21 March 2025 | 90.94 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 8 | 30 | 21 October 2025 | 21 March 2025 | 75.79 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 9 | 31 | 21 November 2025 | 21 March 2025 | 60.64 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 10 | 30 | 21 December 2025 | 21 March 2025 | 45.49 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 11 | 31 | 21 January 2026 | 21 March 2025 | 30.34 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 12 | 31 | 21 February 2026 | 21 March 2025 | 15.19 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 13 | 28 | 21 March 2026 | 21 March 2025 | 0.0 |
15.19 | 0.0 | 0.0 | 0.0 | 15.19 | 15.19 | 15.19 | 0.0
| 0.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 342.46 | 2.01 | 0.0 | 0.0 | 344.47 | 242.46 | 181.84
| 0.0 | 102.01 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ | 21 March 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 142.46 | true | false |
+ | 21 March 2025 | Merchant Issued Refund | 242.46 | 242.46 | 0.0
| 0.0 | 0.0 | 0.0 | false | true |
+ | 28 March 2025 | Credit Balance Refund | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 100.0 | false | true |
+ | 20 April 2025 | Accrual | 1.85 | 0.0 | 1.85
| 0.0 | 0.0 | 0.0 | false | false |
+ | 21 April 2025 | Accrual | 0.08 | 0.0 | 0.08
| 0.0 | 0.0 | 0.0 | false | false |
+ When Admin sets the business date to "23 April 2025"
+ And Admin runs inline COB job for Loan
+ Then Loan Repayment schedule has 13 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 |
+ | | | 21 March 2025 | | 242.46 |
| | 0.0 | | 0.0 | 0.0 | | |
|
+ | 1 | 0 | 21 March 2025 | 21 March 2025 | 181.84 |
60.62 | 0.0 | 0.0 | 0.0 | 60.62 | 60.62 | 0.0 | 0.0
| 0.0 |
+ | 2 | 31 | 21 April 2025 | | 166.69 |
115.15 | 2.1 | 0.0 | 0.0 | 117.25 | 15.15 | 15.15 | 0.0
| 102.1 |
+ | 3 | 30 | 21 May 2025 | 21 March 2025 | 151.54 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 4 | 31 | 21 June 2025 | 21 March 2025 | 136.39 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 5 | 30 | 21 July 2025 | 21 March 2025 | 121.24 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 6 | 31 | 21 August 2025 | 21 March 2025 | 106.09 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 7 | 31 | 21 September 2025 | 21 March 2025 | 90.94 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 8 | 30 | 21 October 2025 | 21 March 2025 | 75.79 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 9 | 31 | 21 November 2025 | 21 March 2025 | 60.64 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 10 | 30 | 21 December 2025 | 21 March 2025 | 45.49 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 11 | 31 | 21 January 2026 | 21 March 2025 | 30.34 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 12 | 31 | 21 February 2026 | 21 March 2025 | 15.19 |
15.15 | 0.0 | 0.0 | 0.0 | 15.15 | 15.15 | 15.15 | 0.0
| 0.0 |
+ | 13 | 28 | 21 March 2026 | 21 March 2025 | 0.0 |
15.19 | 0.0 | 0.0 | 0.0 | 15.19 | 15.19 | 15.19 | 0.0
| 0.0 |
+ And Loan Repayment schedule has the following data in Total row:
+ | Principal due | Interest | Fees | Penalties | Due | Paid | In
advance | Late | Outstanding |
+ | 342.46 | 2.1 | 0.0 | 0.0 | 344.56 | 242.46 | 181.84
| 0.0 | 102.1 |
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ | 21 March 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 142.46 | true | false |
+ | 21 March 2025 | Merchant Issued Refund | 242.46 | 242.46 | 0.0
| 0.0 | 0.0 | 0.0 | false | true |
+ | 28 March 2025 | Credit Balance Refund | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 100.0 | false | true |
+ | 20 April 2025 | Accrual | 1.85 | 0.0 | 1.85
| 0.0 | 0.0 | 0.0 | false | false |
+ | 21 April 2025 | Accrual | 0.08 | 0.0 | 0.08
| 0.0 | 0.0 | 0.0 | false | false |
+ | 22 April 2025 | Accrual | 0.08 | 0.0 | 0.08
| 0.0 | 0.0 | 0.0 | false | false |
+ And Customer makes "AUTOPAY" repayment on "23 April 2025" with 102.1 EUR
transaction amount
+ Then Loan status will be "CLOSED_OBLIGATIONS_MET"
+ Then Loan has 0 outstanding amount
+ And Loan Transactions tab has the following data:
+ | Transaction date | Transaction Type | Amount | Principal |
Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
+ | 21 March 2025 | Disbursement | 242.46 | 0.0 | 0.0
| 0.0 | 0.0 | 242.46 | false | false |
+ | 21 March 2025 | Repayment | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 142.46 | true | false |
+ | 21 March 2025 | Merchant Issued Refund | 242.46 | 242.46 | 0.0
| 0.0 | 0.0 | 0.0 | false | true |
+ | 28 March 2025 | Credit Balance Refund | 100.0 | 100.0 | 0.0
| 0.0 | 0.0 | 100.0 | false | true |
+ | 20 April 2025 | Accrual | 1.85 | 0.0 | 1.85
| 0.0 | 0.0 | 0.0 | false | false |
+ | 21 April 2025 | Accrual | 0.08 | 0.0 | 0.08
| 0.0 | 0.0 | 0.0 | false | false |
+ | 22 April 2025 | Accrual | 0.08 | 0.0 | 0.08
| 0.0 | 0.0 | 0.0 | false | false |
+ | 23 April 2025 | Repayment | 102.1 | 100.0 | 2.1
| 0.0 | 0.0 | 0.0 | false | false |
+ | 23 April 2025 | Accrual | 0.09 | 0.0 | 0.09
| 0.0 | 0.0 | 0.0 | false | false |
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 cd12648ec4..f4f84359ce 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
@@ -53,6 +53,7 @@ import
org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanIntere
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
@Component
@RequiredArgsConstructor
@@ -182,16 +183,25 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
scheduleModel.zero()).ifPresent(repaymentPeriod -> {
calculateRateFactorForRepaymentPeriod(repaymentPeriod,
scheduleModel);
calculateOutstandingBalance(scheduleModel);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel,
balanceCorrectionDate);
});
}
@Override
public void payInterest(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
Money interestAmount) {
- findRepaymentPeriod(scheduleModel,
repaymentPeriodDueDate).ifPresent(rp ->
rp.addPaidInterestAmount(interestAmount));
+ Optional<RepaymentPeriod> repaymentPeriod =
findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
+
+ Optional<RepaymentPeriod> latestNotLastOpenRepaymentPeriodBeforeDate =
getLatestNotLastOpenRepaymentPeriodBeforeDate(scheduleModel,
+ transactionDate);
+ if (latestNotLastOpenRepaymentPeriodBeforeDate.isPresent() &&
repaymentPeriod.equals(latestNotLastOpenRepaymentPeriodBeforeDate)) {
+
calculateUnrecognizedInterestTillDateOnScheduleModelCopyAndDefer(scheduleModel,
+ latestNotLastOpenRepaymentPeriodBeforeDate.get(),
transactionDate);
+ }
+
+ repaymentPeriod.ifPresent(rp ->
rp.addPaidInterestAmount(interestAmount));
calculateOutstandingBalance(scheduleModel);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, transactionDate);
}
@Override
@@ -211,17 +221,38 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
repaymentPeriod.ifPresent(rp -> {
// If any period total paid > calculated EMI, then set EMI to
total paid -> effectively it is marked as
// fully paid
- if (transactionDateIsBefore &&
rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmounts())) {
+ if (transactionDateIsBefore
+ &&
rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest()))
{
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
} else if (transactionDateIsBefore
&&
rp.getTotalPaidAmount().isEqualTo(rp.getOriginalEmi().add(rp.getTotalCreditedAmount())))
{
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
}
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel,
balanceCorrectionDate);
});
}
}
+ private Optional<RepaymentPeriod>
getLatestNotLastOpenRepaymentPeriodBeforeDate(ProgressiveLoanInterestScheduleModel
scheduleModel,
+ LocalDate transactionDate) {
+ List<RepaymentPeriod> unpaidRepaymentPeriods =
scheduleModel.repaymentPeriods() //
+ .stream() //
+ .filter(rp -> !rp.isFullyPaid()) //
+ .toList(); //
+
+ if (CollectionUtils.isEmpty(unpaidRepaymentPeriods)
+ ||
unpaidRepaymentPeriods.getLast().equals(scheduleModel.repaymentPeriods().getLast()))
{
+ return Optional.empty();
+ }
+
+ RepaymentPeriod latestNotLastOpenRepaymentPeriod =
unpaidRepaymentPeriods.getLast();
+ if (DateUtils.isBefore(transactionDate,
latestNotLastOpenRepaymentPeriod.getDueDate())) {
+ return Optional.empty();
+ }
+
+ return Optional.of(latestNotLastOpenRepaymentPeriod);
+ }
+
private void
addCreditedAmountsToInterestPeriod(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate transactionDate,
Money creditedPrincipalAmount, Money creditedInterestAmount) {
scheduleModel.repaymentPeriods().stream().filter(checkRepaymentPeriodIsInCreditRange(scheduleModel,
transactionDate)).findFirst()
@@ -260,7 +291,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
addCreditedAmountsToInterestPeriod(scheduleModel,
transactionDate, creditedPrincipalAmount, creditedInterestAmount);
calculateRateFactorForRepaymentPeriod(repaymentPeriod,
scheduleModel);
calculateOutstandingBalance(scheduleModel);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel,
transactionDate);
});
}
@@ -334,22 +365,9 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
public OutstandingDetails
getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate targetDate) {
MathContext mc = scheduleModel.mc();
ProgressiveLoanInterestScheduleModel scheduleModelCopy =
scheduleModel.deepCopy(mc);
- // TODO use findInterestPeriod
- scheduleModelCopy.repaymentPeriods().stream()//
- .filter(rp -> targetDate.isAfter(rp.getFromDate()) &&
!targetDate.isAfter(rp.getDueDate())).findFirst()//
- .flatMap(rp -> rp.getInterestPeriods().stream()//
- .filter(ip -> targetDate.isAfter(ip.getFromDate()) &&
!targetDate.isAfter(ip.getDueDate())) //
- .reduce((one, two) -> two))
- .ifPresent(ip -> ip.setDueDate(targetDate)); //
-
- calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(),
scheduleModelCopy);
- scheduleModelCopy.repaymentPeriods()
- .forEach(rp -> rp.getInterestPeriods().stream().filter(ip ->
targetDate.isBefore(ip.getDueDate())).forEach(ip -> {
- ip.setRateFactor(BigDecimal.ZERO);
- ip.setRateFactorTillPeriodDueDate(BigDecimal.ZERO);
- }));
+ calculateRateFactorForScheduleTillDateInclusive(scheduleModelCopy,
targetDate);
calculateOutstandingBalance(scheduleModelCopy);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy, targetDate);
Money totalOutstandingPrincipal = MathUtil
.negativeToZero(scheduleModelCopy.getTotalDuePrincipal().minus(scheduleModelCopy.getTotalPaidPrincipal()));
@@ -421,7 +439,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
});
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(),
scheduleModelCopy);
calculateOutstandingBalance(scheduleModelCopy);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy,
targetDate);
}
return scheduleModelCopy;
}
@@ -443,37 +461,63 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
calculateEMIOnNewModelAndMerge(relatedRepaymentPeriods,
scheduleModel, operation);
}
calculateOutstandingBalance(scheduleModel);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel,
calculateFromRepaymentPeriodDueDate);
if (onlyOnActualModelShouldApply &&
(scheduleModel.loanTermVariations() == null
||
scheduleModel.loanTermVariations().get(LoanTermVariationType.DUE_DATE) ==
null)) {
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel,
relatedRepaymentPeriods);
}
}
- private void
calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel
scheduleModel) {
- MathContext mc = scheduleModel.mc();
- Money totalDueInterest =
scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(scheduleModel.zero(),
- (m1, m2) -> m1.plus(m2, mc)); // 1.46
- Money totalEMI =
scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmiPlusCreditedAmounts)
- .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); //
101.48
- Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
- .flatMap(rp ->
rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
- .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); //
100
- Money totalCapitalizedIncome =
scheduleModel.repaymentPeriods().stream()
- .flatMap(rp ->
rp.getInterestPeriods().stream().map(InterestPeriod::getCapitalizedIncomePrincipal))
- .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); //
100
-
- Money diff = totalDisbursedAmount.plus(totalCapitalizedIncome,
mc).plus(scheduleModel.getTotalCreditedPrincipal(), mc)
- .plus(totalDueInterest, mc).minus(totalEMI, mc);
+ private void
calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel
scheduleModel, LocalDate tillDate) {
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod =
scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
.reduce((first, second) -> second);
+
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> {
+
repaymentPeriod.setFutureUnrecognizedInterest(scheduleModel.zero());
+ scheduleModel.repaymentPeriods().forEach(rp -> {
+ rp.setInterestMoved(false);
+ });
+
+ MathContext mc = scheduleModel.mc();
+ Money totalDueInterest =
scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest)
+ .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2,
mc)); // 1.46
+ Money totalEMI = scheduleModel.repaymentPeriods().stream()
+
.map(RepaymentPeriod::getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest)
+ .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2,
mc)); // 101.48
+ Money totalDisbursedAmount =
scheduleModel.repaymentPeriods().stream()
+ .flatMap(rp ->
rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
+ .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2,
mc)); // 100
+ Money totalCapitalizedIncome =
scheduleModel.repaymentPeriods().stream()
+ .flatMap(rp ->
rp.getInterestPeriods().stream().map(InterestPeriod::getCapitalizedIncomePrincipal))
+ .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2,
mc)); // 100
+
+ Money diff = totalDisbursedAmount.plus(totalCapitalizedIncome,
mc).plus(scheduleModel.getTotalCreditedPrincipal(), mc)
+ .plus(totalDueInterest, mc).minus(totalEMI, mc);
+
repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc));
if (repaymentPeriod.getEmi()
.isLessThan(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(),
mc))) {
repaymentPeriod.setEmi(repaymentPeriod.getTotalPaidAmount().minus(repaymentPeriod.getTotalCreditedAmount(),
mc));
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, tillDate);
}
+
+
calculateUnrecognizedInterestTillDateOnScheduleModelCopyAndDefer(scheduleModel,
repaymentPeriod, tillDate);
+ });
+ }
+
+ private void
calculateUnrecognizedInterestTillDateOnScheduleModelCopyAndDefer(ProgressiveLoanInterestScheduleModel
scheduleModel,
+ RepaymentPeriod repaymentPeriod, LocalDate tillDate) {
+ MathContext mc = scheduleModel.mc();
+ ProgressiveLoanInterestScheduleModel scheduleModelCopy =
scheduleModel.deepCopy(mc);
+ calculateRateFactorForScheduleTillDateInclusive(scheduleModelCopy,
tillDate);
+ Optional<RepaymentPeriod> futureUnrecognizedInterestPeriod =
getPeriodWithUnrecognizedInterest(repaymentPeriod, scheduleModelCopy);
+
+ futureUnrecognizedInterestPeriod.ifPresent(period -> {
+
repaymentPeriod.setFutureUnrecognizedInterest(period.getUnrecognizedInterest());
+ scheduleModel.repaymentPeriods().stream().filter(rp ->
rp.getDueDate().isAfter(repaymentPeriod.getDueDate())) //
+ .forEach(rp -> {
+ rp.setInterestMoved(true);
+ });
});
}
@@ -509,7 +553,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
}
});
calculateOutstandingBalance(newScheduleModel);
- calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel,
relatedPeriodsFirstDueDate);
if
(!getEmiAdjustment(newScheduleModel.repaymentPeriods()).hasLessEmiDifference(emiAdjustment))
{
break;
}
@@ -861,6 +905,31 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
return new EmiAdjustment(repaymentPeriods.get(0).getEmi(),
repaymentPeriods.get(0).getEmi().copy(0.0), repaymentPeriods, 0);
}
+ private void
calculateRateFactorForScheduleTillDateInclusive(ProgressiveLoanInterestScheduleModel
scheduleModelCopy,
+ LocalDate targetDate) {
+ scheduleModelCopy.findRepaymentPeriod(targetDate).flatMap(rp ->
rp.findInterestPeriod(targetDate))
+ .ifPresent(ip -> ip.setDueDate(targetDate));
+
+ calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(),
scheduleModelCopy);
+
+ scheduleModelCopy.repaymentPeriods()
+ .forEach(rp -> rp.getInterestPeriods().stream().filter(ip ->
targetDate.isBefore(ip.getDueDate())).forEach(ip -> {
+ ip.setRateFactor(BigDecimal.ZERO);
+ ip.setRateFactorTillPeriodDueDate(BigDecimal.ZERO);
+ }));
+ }
+
+ private Optional<RepaymentPeriod>
getPeriodWithUnrecognizedInterest(RepaymentPeriod lastUnpaidRepaymentPeriod,
+ ProgressiveLoanInterestScheduleModel scheduleModelCopy) {
+ for (RepaymentPeriod period :
scheduleModelCopy.repaymentPeriods().reversed()) {
+ if (MathUtil.isGreaterThanZero(period.getUnrecognizedInterest())
+ &&
period.getDueDate().isAfter(lastUnpaidRepaymentPeriod.getDueDate())) {
+ return Optional.of(period);
+ }
+ }
+ return Optional.empty();
+ }
+
/**
* Calculate Rate Factor Product from rate factors
*/
@@ -1082,7 +1151,7 @@ public final class ProgressiveEMICalculator implements
EMICalculator {
final List<RepaymentPeriod> relatedRepaymentPeriods =
scheduleModel.getRelatedRepaymentPeriods(startDate);
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
calculateOutstandingBalance(scheduleModel);
- calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
+ calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, startDate);
}
private long getUncountablePeriods(final List<RepaymentPeriod>
relatedRepaymentPeriods, final Money originalEmi) {
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
index c6497e8d24..d38280f281 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java
@@ -145,7 +145,7 @@ public class InterestPeriod implements
Comparable<InterestPeriod> {
.multiply(getRateFactorTillPeriodDueDate(), mc) //
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate),
mc) //
.multiply(BigDecimal.valueOf(getLength()), mc); //
- return
MathUtil.negativeToZero(MathUtil.add(creditedInterest.getAmount(),
interestDueTillRepaymentDueDate, mc));
+ return MathUtil.negativeToZero(MathUtil.add(mc,
creditedInterest.getAmount(), interestDueTillRepaymentDueDate));
}
public long getLength() {
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
index 26e9e2a9d2..33b94d852c 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java
@@ -60,6 +60,9 @@ public final class RepaymentPeriod {
private Money paidPrincipal;
@Getter
private Money paidInterest;
+ @Setter
+ @Getter
+ private Money futureUnrecognizedInterest;
@JsonExclude
private final MathContext mc;
@@ -72,9 +75,12 @@ public final class RepaymentPeriod {
private Memo<Money> dueInterestCalculation;
@JsonExclude
private Memo<Money> outstandingBalanceCalculation;
+ @Getter
+ @Setter
+ private boolean isInterestMoved = false;
private RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate,
LocalDate dueDate, List<InterestPeriod> interestPeriods,
- Money emi, Money originalEmi, Money paidPrincipal, Money
paidInterest, MathContext mc) {
+ Money emi, Money originalEmi, Money paidPrincipal, Money
paidInterest, Money futureUnrecognizedInterest, MathContext mc) {
this.previous = previous;
this.fromDate = fromDate;
this.dueDate = dueDate;
@@ -83,17 +89,18 @@ public final class RepaymentPeriod {
this.originalEmi = originalEmi;
this.paidPrincipal = paidPrincipal;
this.paidInterest = paidInterest;
+ this.futureUnrecognizedInterest = futureUnrecognizedInterest;
this.mc = mc;
}
public static RepaymentPeriod empty(RepaymentPeriod previous, MathContext
mc) {
- return new RepaymentPeriod(previous, null, null, new ArrayList<>(),
null, null, null, null, mc);
+ return new RepaymentPeriod(previous, null, null, new ArrayList<>(),
null, null, null, null, null, mc);
}
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate
fromDate, LocalDate dueDate, Money emi, MathContext mc) {
final Money zero = emi.zero();
final RepaymentPeriod newRepaymentPeriod = new
RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero,
zero,
- mc);
+ zero, mc);
// There is always at least 1 interest period, by default with same
from-due date as repayment period
newRepaymentPeriod.interestPeriods.add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod,
fromDate, dueDate));
return newRepaymentPeriod;
@@ -102,7 +109,7 @@ public final class RepaymentPeriod {
public static RepaymentPeriod copy(RepaymentPeriod previous,
RepaymentPeriod repaymentPeriod, MathContext mc) {
final RepaymentPeriod newRepaymentPeriod = new
RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
new ArrayList<>(), repaymentPeriod.emi,
repaymentPeriod.originalEmi, repaymentPeriod.paidPrincipal,
- repaymentPeriod.paidInterest, mc);
+ repaymentPeriod.paidInterest,
repaymentPeriod.futureUnrecognizedInterest, mc);
// There is always at least 1 interest period, by default with same
from-due date as repayment period
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
newRepaymentPeriod.interestPeriods.add(InterestPeriod.copy(newRepaymentPeriod,
interestPeriod, mc));
@@ -113,7 +120,7 @@ public final class RepaymentPeriod {
public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod
previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
final Money zero = repaymentPeriod.emi.zero();
final RepaymentPeriod newRepaymentPeriod = new
RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
- new ArrayList<>(), repaymentPeriod.emi,
repaymentPeriod.originalEmi, zero, zero, mc);
+ new ArrayList<>(), repaymentPeriod.emi,
repaymentPeriod.originalEmi, zero, zero, zero, mc);
// There is always at least 1 interest period, by default with same
from-due date as repayment period
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod,
interestPeriod);
@@ -154,14 +161,19 @@ public final class RepaymentPeriod {
public Money getCalculatedDueInterest() {
if (calculatedDueInterestCalculation == null) {
calculatedDueInterestCalculation =
Memo.of(this::calculateCalculatedDueInterest,
- () -> new Object[] { this.previous, this.interestPeriods
});
+ () -> new Object[] { this.previous, this.interestPeriods,
this.futureUnrecognizedInterest, this.isInterestMoved });
}
return calculatedDueInterestCalculation.get();
}
private Money calculateCalculatedDueInterest() {
- Money calculatedDueInterest = Money.of(emi.getCurrencyData(),
-
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO,
BigDecimal::add), mc);
+ Money calculatedDueInterest = getZero(mc);
+ if (!isInterestMoved) {
+ calculatedDueInterest = Money.of(emi.getCurrencyData(),
+
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO,
BigDecimal::add),
+ mc);
+ }
+ calculatedDueInterest =
calculatedDueInterest.add(getFutureUnrecognizedInterest(), mc);
if (getPrevious().isPresent()) {
calculatedDueInterest =
calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), mc);
}
@@ -176,22 +188,22 @@ public final class RepaymentPeriod {
public Money getDueInterest() {
if (dueInterestCalculation == null) {
// Due interest might be the maximum paid if there is pay-off or
early repayment
- dueInterestCalculation = Memo
- .of(() -> MathUtil.max(
-
getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ?
getPaidInterest()
- : MathUtil.min(getCalculatedDueInterest(),
getEmiPlusCreditedAmounts(), false),
- getPaidInterest(), false), () -> new Object[] {
paidPrincipal, paidInterest, interestPeriods });
+ dueInterestCalculation = Memo.of(
+ () ->
MathUtil.max(getPaidPrincipal().isGreaterThan(getCalculatedDuePrincipal()) ?
getPaidInterest()
+ : MathUtil.min(getCalculatedDueInterest(),
getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest(), false),
+ getPaidInterest(), false),
+ () -> new Object[] { paidPrincipal, paidInterest,
interestPeriods, futureUnrecognizedInterest });
}
return dueInterestCalculation.get();
}
/**
- * Gives back an EMI amount which includes credited amounts as well
+ * Gives back an EMI amount which includes credited amounts and future
unrecognized interest as well
*
* @return
*/
- public Money getEmiPlusCreditedAmounts() {
- return getEmi().plus(getTotalCreditedAmount(), mc); //
+ public Money getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest() {
+ return getEmi().plus(getTotalCreditedAmount(),
mc).plus(getFutureUnrecognizedInterest(), mc); //
}
/**
@@ -200,7 +212,7 @@ public final class RepaymentPeriod {
* @return
*/
public Money getCalculatedDuePrincipal() {
- return
MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getCalculatedDueInterest(),
mc), mc);
+ return
MathUtil.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getCalculatedDueInterest(),
mc), mc);
}
/**
@@ -243,8 +255,9 @@ public final class RepaymentPeriod {
*/
public Money getDuePrincipal() {
// Due principal might be the maximum paid if there is pay-off or
early repayment
- return
MathUtil.max(MathUtil.negativeToZero(getEmiPlusCreditedAmounts().minus(getDueInterest(),
mc), mc), getPaidPrincipal(),
- false);
+ return MathUtil.max(
+
MathUtil.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getDueInterest(),
mc), mc),
+ getPaidPrincipal(), false);
}
/**
@@ -266,7 +279,7 @@ public final class RepaymentPeriod {
}
public boolean isFullyPaid() {
- return getEmiPlusCreditedAmounts().isEqualTo(getTotalPaidAmount());
+ return
getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().isEqualTo(getTotalPaidAmount());
}
/**