FINERACT-2354: First step - basic implementation of re-aging for Interest bearing loans - Default Behavior, interestRecalculation = true, without dueDate change

This commit is contained in:
mariiaKraievska
2025-10-27 14:22:46 +01:00
committed by Adam Saghy
parent 23c1b2b8af
commit d21a1a98a2
17 changed files with 416 additions and 461 deletions
@@ -3588,75 +3588,6 @@ Feature: LoanReAging
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
| Snooze fee | false | Specified due date | 15 February 2024 | Flat | 10.0 | 10.0 | 0.0 | 0.0 |
@TestRailId:C4077 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Charge-back before re-aging - UC3
When Admin sets the business date to "01 January 2024"
When Admin creates a client with random data
When Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_EMI_360_30_CHARGEBACK_INTEREST_PENALTY_FEE_PRINCIPAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
When Admin sets the business date to "01 February 2024"
And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount
When Admin makes "REPAYMENT_ADJUSTMENT_CHARGEBACK" chargeback with 17.01 EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | | 67.14 | 32.86 | 1.16 | 0.0 | 0.0 | 34.02 | 0.0 | 0.0 | 0.0 | 34.02 |
| 3 | 31 | 01 April 2024 | | 50.52 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.8 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.99 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.99 | 0.1 | 0.0 | 0.0 | 17.09 | 0.0 | 0.0 | 0.0 | 17.09 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 116.43 | 2.72 | 0.0 | 0.0 | 119.15 | 17.01 | 0.0 | 0.0 | 102.14 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 01 February 2024 | Chargeback | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 100.0 | false |
When Admin sets the business date to "15 March 2024"
When Admin creates a Loan re-aging transaction with the following data:
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
| 1 | MONTHS | 01 April 2024| 6 |
Then Loan Repayment schedule has 8 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | 15 March 2024 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 3 | 31 | 01 April 2024 | | 84.53 | 15.47 | 1.74 | 0.0 | 0.0 | 17.21 | 0.0 | 0.0 | 0.0 | 17.21 |
| 4 | 30 | 01 May 2024 | | 67.81 | 16.72 | 0.49 | 0.0 | 0.0 | 17.21 | 0.0 | 0.0 | 0.0 | 17.21 |
| 5 | 31 | 01 June 2024 | | 51.0 | 16.81 | 0.4 | 0.0 | 0.0 | 17.21 | 0.0 | 0.0 | 0.0 | 17.21 |
| 6 | 30 | 01 July 2024 | | 34.09 | 16.91 | 0.3 | 0.0 | 0.0 | 17.21 | 0.0 | 0.0 | 0.0 | 17.21 |
| 7 | 31 | 01 August 2024 | | 17.08 | 17.01 | 0.2 | 0.0 | 0.0 | 17.21 | 0.0 | 0.0 | 0.0 | 17.21 |
| 8 | 31 | 01 September 2024| | 0.0 | 17.08 | 0.1 | 0.0 | 0.0 | 17.18 | 0.0 | 0.0 | 0.0 | 17.18 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 116.43 | 3.81 | 0.0 | 0.0 | 120.24 | 17.01| 0.0 | 0.0 | 103.23 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 01 February 2024 | Chargeback | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 100.0 | false |
| 15 March 2024 | Re-age | 100.84 | 100.0 | 0.84 | 0.0 | 0.0 | 0.0 | false |
@TestRailId:C4083 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - N+1 Scenario - UC4
When Admin sets the business date to "01 January 2024"
@@ -4128,180 +4059,6 @@ Feature: LoanReAging
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 15 March 2024 | Re-age | 84.28 | 83.57 | 0.71 | 0.0 | 0.0 | 0.0 | true |
@TestRailId:C4089 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Charge-off scenario (zero interest) - UC10
When Admin sets the business date to "01 January 2024"
When Admin creates a client with random data
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
When Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024| | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
When Admin sets the business date to "01 February 2024"
And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024| 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
When Admin sets the business date to "01 March 2024"
And Admin does charge-off the loan on "01 March 2024"
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024| 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.04 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.03 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.02 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.02 | 0.0 | 0.0 | 0.0 | 16.02 | 0.0 | 0.0 | 0.0 | 16.02 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 1.07 | 0.0 | 0.0 | 101.07 | 17.01 | 0.0 | 0.0 | 84.06 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 01 March 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false |
| 01 March 2024 | Charge-off | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
When Admin sets the business date to "15 March 2024"
When Admin creates a Loan re-aging transaction with the following data:
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
| 1 | MONTHS | 01 April 2024 | 6 |
Then Loan Repayment schedule has 8 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 3 | 31 | 01 April 2024 | | 70.05 | 13.52 | 0.49 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 4 | 30 | 01 May 2024 | | 56.04 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 5 | 31 | 01 June 2024 | | 42.03 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 6 | 30 | 01 July 2024 | | 28.02 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 7 | 31 | 01 August 2024 | | 14.01 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 8 | 31 | 01 September 2024| | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 1.07 | 0.0 | 0.0 | 101.07| 17.01 | 0.0 | 0.0 | 84.06 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 01 March 2024 | Charge-off | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
| 01 March 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false |
| 15 March 2024 | Re-age | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
@TestRailId:C4090 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Charge-off scenario (accelerate maturity) - UC11
When Admin sets the business date to "01 January 2024"
When Admin creates a client with random data
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
When Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024| | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
When Admin sets the business date to "01 February 2024"
And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024| 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 17.01 | 0.0 | 0.0 | 85.04 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
When Admin sets the business date to "01 March 2024"
And Admin does charge-off the loan on "01 March 2024"
Then Loan Repayment schedule has 2 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024| 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | | 0.0 | 83.57 | 0.49 | 0.0 | 0.0 | 84.06 | 0.0 | 0.0 | 0.0 | 84.06 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 1.07 | 0.0 | 0.0 | 101.07 | 17.01 | 0.0 | 0.0 | 84.06 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 01 March 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false |
| 01 March 2024 | Charge-off | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
When Admin sets the business date to "15 March 2024"
When Admin creates a Loan re-aging transaction with the following data:
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
| 1 | MONTHS | 01 April 2024 | 6 |
Then Loan Repayment schedule has 8 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | 15 March 2024 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| 3 | 31 | 01 April 2024 | | 70.05 | 13.52 | 0.49 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 4 | 30 | 01 May 2024 | | 56.04 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 5 | 31 | 01 June 2024 | | 42.03 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 6 | 30 | 01 July 2024 | | 28.02 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 7 | 31 | 01 August 2024 | | 14.01 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
| 8 | 31 | 01 September 2024| | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 | 0.0 | 0.0 | 0.0 | 14.01 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 1.07 | 0.0 | 0.0 | 101.07| 17.01 | 0.0 | 0.0 | 84.06 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 01 March 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false |
| 01 March 2024 | Charge-off | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
| 15 March 2024 | Re-age | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
@TestRailId:C4091 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Fees and Interest Split after re-aging - UC12
When Admin sets the business date to "01 January 2024"
@@ -4392,78 +4149,3 @@ Feature: LoanReAging
Then Loan Charges tab has the following data:
| Name | isPenalty | Payment due at | Due as of | Calculation type | Due | Paid | Waived | Outstanding |
| Snooze fee | false | Specified due date | 15 May 2024 | Flat | 10.0 | 0.0 | 0.0 | 10.0 |
@TestRailId:C4110 @AdvancedPaymentAllocation
Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - backdated re-aging transaction - UC16
When Admin sets the business date to "01 January 2024"
When Admin creates a client with random data
When Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 01 January 2024 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 0.0 | 0.0 | 0.0 | 102.05 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
When Admin sets the business date to "01 February 2024"
And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
When Admin sets the business date to "01 March 2024"
And Customer makes "AUTOPAY" repayment on "01 March 2024" with 17.01 EUR transaction amount
Then Loan Repayment schedule has 6 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | 01 March 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 3 | 31 | 01 April 2024 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 4 | 30 | 01 May 2024 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 5 | 31 | 01 June 2024 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
| 6 | 30 | 01 July 2024 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.05 | 0.0 | 0.0 | 102.05 | 34.02 | 0.0 | 0.0 | 68.03 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false |
| 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false |
When Admin sets the business date to "01 June 2024"
# Backdated re-aging - created in June but effective from April 01
When Admin creates a Loan re-aging transaction with the following data:
| frequencyNumber | frequencyType | startDate | numberOfInstallments |
| 1 | MONTHS | 01 April 2024 | 6 |
Then Loan Repayment schedule has 8 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 2 | 29 | 01 March 2024 | 01 March 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
| 3 | 31 | 01 April 2024 | | 56.0 | 11.05 | 0.39 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 |
| 4 | 30 | 01 May 2024 | | 44.95 | 11.05 | 0.39 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 |
| 5 | 31 | 01 June 2024 | | 33.9 | 11.05 | 0.39 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 |
| 6 | 30 | 01 July 2024 | | 22.66 | 11.24 | 0.2 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 |
| 7 | 31 | 01 August 2024 | | 11.35 | 11.31 | 0.13 | 0.0 | 0.0 | 11.44 | 0.0 | 0.0 | 0.0 | 11.44 |
| 8 | 31 | 01 September 2024| | 0.0 | 11.35 | 0.07 | 0.0 | 0.0 | 11.42 | 0.0 | 0.0 | 0.0 | 11.42 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 100.0 | 2.64 | 0.0 | 0.0 | 102.64 | 34.02 | 0.0 | 0.0 | 68.62 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
| 01 February 2024 | Repayment | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 83.57 | false | false |
| 01 March 2024 | Repayment | 17.01 | 16.52 | 0.49 | 0.0 | 0.0 | 67.05 | false | false |
| 01 April 2024 | Re-age | 67.44 | 67.05 | 0.39 | 0.0 | 0.0 | 0.0 | false | true |
@@ -1824,4 +1824,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
return getLoanTransactions().stream().anyMatch(t -> t.isContractTermination() && t.isNotReversed());
}
public boolean hasReAgingTransaction() {
return getLoanTransactions().stream().anyMatch(t -> t.isReAge() && t.isNotReversed());
}
}
@@ -157,7 +157,8 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
reprocessLoanTransactionsService.reprocessTransactions(loan);
@@ -34,12 +34,10 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaym
public class EmbeddableProgressiveLoanScheduleGenerator {
private final ProgressiveLoanScheduleGenerator scheduleGenerator;
private final ScheduledDateGenerator scheduledDateGenerator;
private final EMICalculator emiCalculator;
public EmbeddableProgressiveLoanScheduleGenerator() {
this.emiCalculator = new ProgressiveEMICalculator();
this.scheduledDateGenerator = new DefaultScheduledDateGenerator();
final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
final EMICalculator emiCalculator = new ProgressiveEMICalculator(scheduledDateGenerator);
this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator,
new NoopInterestScheduleModelRepositoryWrapper());
}
@@ -47,6 +47,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -97,6 +98,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParamet
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
@@ -2890,76 +2892,149 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
MonetaryCurrency currency = ctx.getCurrency();
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
installments.forEach(i -> {
Money principalOutstanding = i.getPrincipalOutstanding(currency);
if (principalOutstanding.isGreaterThanZero()) {
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
i.addToPrincipal(loanTransaction.getTransactionDate(), principalOutstanding.negated());
// re-aging logic for interest-bearing loans
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
handleReAgeWithInterestRecalculationEnabled(loanTransaction, progressiveTransactionCtx);
} else if (loanTransaction.getLoan().isInterestBearing() && !loanTransaction.getLoan().isInterestRecalculationEnabled()) {
// TODO: implement interestRecalculation = false logic
throw new NotImplementedException(
"Logic for re-aging when interest bearing loan has interestRecalculation disabled is not implemented");
} else {
AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
installments.forEach(i -> {
Money principalOutstanding = i.getPrincipalOutstanding(currency);
if (principalOutstanding.isGreaterThanZero()) {
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
i.addToPrincipal(loanTransaction.getTransactionDate(), principalOutstanding.negated());
}
});
loanTransaction.updateComponentsAndTotal(outstandingPrincipalBalance.get(), Money.zero(currency), Money.zero(currency),
Money.zero(currency));
if (outstandingPrincipalBalance.get().isZero()) {
loanTransaction.reverse();
return;
}
Money calculatedPrincipal = Money.zero(currency);
Money adjustCalculatedPrincipal = Money.zero(currency);
if (outstandingPrincipalBalance.get().isGreaterThanZero()) {
calculatedPrincipal = outstandingPrincipalBalance.get()
.dividedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(), MoneyHelper.getMathContext());
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProductRelatedDetail()
.getInstallmentAmountInMultiplesOf();
if (installmentAmountInMultiplesOf != null) {
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal, installmentAmountInMultiplesOf);
}
adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
.minus(calculatedPrincipal.multipliedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()));
}
Optional<LoanRepaymentScheduleInstallment> lastNormalInstallmentOptional = installments.stream().filter(i -> !i.isDownPayment())
.filter(i -> i.getDueDate().isBefore(loanTransaction.getTransactionDate())).reduce((first, second) -> second);
int reAgedInstallmentNumber;
LocalDate fromDate;
Loan loan;
if (lastNormalInstallmentOptional.isEmpty()) {
LoanRepaymentScheduleInstallment firstNormalInstallment = installments.stream().filter(i -> !i.isDownPayment())
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).orElseThrow();
reAgedInstallmentNumber = firstNormalInstallment.getInstallmentNumber();
fromDate = firstNormalInstallment.getFromDate();
loan = firstNormalInstallment.getLoan();
} else {
LoanRepaymentScheduleInstallment lastNormalInstallment = lastNormalInstallmentOptional.get();
reAgedInstallmentNumber = lastNormalInstallment.getInstallmentNumber() + 1;
fromDate = lastNormalInstallment.getDueDate();
loan = lastNormalInstallment.getLoan();
}
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
reAgedInstallmentNumber, fromDate, loanTransaction.getLoanReAgeParameter().getStartDate(),
calculatedPrincipal.getAmount());
insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, loanTransaction.getTransactionDate());
for (int i = 1; i < loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(); i++) {
LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
reAgedInstallment.getDueDate());
int nextReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber() + 1;
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
nextReAgedInstallmentNumber, reAgedInstallment.getDueDate(), calculatedDueDate, calculatedPrincipal.getAmount());
if (i + 1 == loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()) {
reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
}
insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, loanTransaction.getTransactionDate());
}
int lastReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber();
List<LoanRepaymentScheduleInstallment> toRemove = installments.stream().filter(i -> i != null && !i.isAdditional()
&& i.getInstallmentNumber() != null && i.getInstallmentNumber() > lastReAgedInstallmentNumber).toList();
toRemove.forEach(installments::remove);
reprocessInstallments(installments);
}
}
private void cleanupInstallmentsAndRepaymentPeriodsAfterReAging(final ProgressiveTransactionCtx ctx) {
final List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
final List<RepaymentPeriod> repaymentPeriods = ctx.getModel().repaymentPeriods();
// Find the last re-aged installment number
final OptionalInt lastReAgedInstallmentNumberOpt = installments.stream().filter(LoanRepaymentScheduleInstallment::isReAged)
.mapToInt(LoanRepaymentScheduleInstallment::getInstallmentNumber).max();
if (lastReAgedInstallmentNumberOpt.isPresent()) {
final int lastReAgedInstallmentNumber = lastReAgedInstallmentNumberOpt.getAsInt();
final LoanRepaymentScheduleInstallment lastReAgedInstallment = installments.stream()
.filter(i -> i.getInstallmentNumber().equals(lastReAgedInstallmentNumber)).findFirst().orElse(null);
// Remove installments with numbers greater than the last re-aged installment
final List<LoanRepaymentScheduleInstallment> installmentsToRemove = installments.stream().filter(i -> i != null
&& !i.isAdditional() && i.getInstallmentNumber() != null && i.getInstallmentNumber() > lastReAgedInstallmentNumber)
.toList();
if (lastReAgedInstallment != null) {
final List<RepaymentPeriod> repaymentPeriodsToRemove = repaymentPeriods.stream()
.filter(rp -> !rp.getFromDate().isBefore(lastReAgedInstallment.getDueDate())).toList();
repaymentPeriodsToRemove.forEach(repaymentPeriods::remove);
}
installmentsToRemove.forEach(installments::remove);
}
}
private void updateInstallmentsByModelForReAging(final LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx) {
ctx.getModel().repaymentPeriods().forEach(rp -> {
final LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream().filter(
ri -> ri.getFromDate().equals(rp.getFromDate()) && ri.getDueDate().equals(rp.getDueDate()) && !ri.isDownPayment())
.findFirst().orElse(null);
final LocalDate transactionDate = loanTransaction.getTransactionDate();
if (installment != null) {
installment.setFromDate(rp.getFromDate());
installment.setDueDate(rp.getDueDate());
if (rp.getEmi().isZero()) {
installment.updatePrincipal(BigDecimal.ZERO);
installment.updateInterestCharged(BigDecimal.ZERO);
} else {
installment.updatePrincipal(rp.getDuePrincipal().getAmount());
installment.updateInterestCharged(rp.getDueInterest().getAmount());
}
installment.setReAged(true);
installment.updateObligationsMet(ctx.getCurrency(), transactionDate);
} else {
final LoanRepaymentScheduleInstallment lastInstallment = ctx.getInstallments().getLast();
final LoanRepaymentScheduleInstallment newInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(
loanTransaction.getLoan(), lastInstallment.getInstallmentNumber() + 1, rp.getFromDate(), rp.getDueDate(),
rp.getDuePrincipal().getAmount());
if (rp.getDueInterest().isGreaterThanZero()) {
newInstallment.addToInterest(transactionDate, rp.getDueInterest());
}
newInstallment.updateObligationsMet(ctx.getCurrency(), transactionDate);
ctx.getInstallments().add(newInstallment);
}
});
loanTransaction.updateComponentsAndTotal(outstandingPrincipalBalance.get(), Money.zero(currency), Money.zero(currency),
Money.zero(currency));
if (outstandingPrincipalBalance.get().isZero()) {
loanTransaction.reverse();
return;
}
Money calculatedPrincipal = Money.zero(currency);
Money adjustCalculatedPrincipal = Money.zero(currency);
if (outstandingPrincipalBalance.get().isGreaterThanZero()) {
calculatedPrincipal = outstandingPrincipalBalance.get()
.dividedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(), MoneyHelper.getMathContext());
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProductRelatedDetail()
.getInstallmentAmountInMultiplesOf();
if (installmentAmountInMultiplesOf != null) {
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal, installmentAmountInMultiplesOf);
}
adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
.minus(calculatedPrincipal.multipliedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()));
}
Optional<LoanRepaymentScheduleInstallment> lastNormalInstallmentOptional = installments.stream().filter(i -> !i.isDownPayment())
.filter(i -> i.getDueDate().isBefore(loanTransaction.getTransactionDate())).reduce((first, second) -> second);
int reAgedInstallmentNumber;
LocalDate fromDate;
Loan loan;
if (lastNormalInstallmentOptional.isEmpty()) {
LoanRepaymentScheduleInstallment firstNormalInstallment = installments.stream().filter(i -> !i.isDownPayment())
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).orElseThrow();
reAgedInstallmentNumber = firstNormalInstallment.getInstallmentNumber();
fromDate = firstNormalInstallment.getFromDate();
loan = firstNormalInstallment.getLoan();
} else {
LoanRepaymentScheduleInstallment lastNormalInstallment = lastNormalInstallmentOptional.get();
reAgedInstallmentNumber = lastNormalInstallment.getInstallmentNumber() + 1;
fromDate = lastNormalInstallment.getDueDate();
loan = lastNormalInstallment.getLoan();
}
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(loan,
reAgedInstallmentNumber, fromDate, loanTransaction.getLoanReAgeParameter().getStartDate(), calculatedPrincipal.getAmount());
insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, loanTransaction.getTransactionDate());
for (int i = 1; i < loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(); i++) {
LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
reAgedInstallment.getDueDate());
int nextReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber() + 1;
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
nextReAgedInstallmentNumber, reAgedInstallment.getDueDate(), calculatedDueDate, calculatedPrincipal.getAmount());
if (i + 1 == loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()) {
reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
}
insertOrReplaceRelatedInstallment(installments, reAgedInstallment, currency, loanTransaction.getTransactionDate());
}
int lastReAgedInstallmentNumber = reAgedInstallment.getInstallmentNumber();
List<LoanRepaymentScheduleInstallment> toRemove = installments.stream().filter(i -> i != null && !i.isAdditional()
&& i.getInstallmentNumber() != null && i.getInstallmentNumber() > lastReAgedInstallmentNumber).toList();
toRemove.forEach(installments::remove);
reprocessInstallments(installments);
cleanupInstallmentsAndRepaymentPeriodsAfterReAging(ctx);
}
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
@@ -3211,4 +3286,75 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
return false;
}
}
private void handleReAgeWithInterestRecalculationEnabled(final LoanTransaction loanTransaction, final ProgressiveTransactionCtx ctx) {
final MonetaryCurrency currency = ctx.getCurrency();
final Loan loan = loanTransaction.getLoan();
final MathContext mc = MoneyHelper.getMathContext();
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
final List<RepaymentPeriod> repaymentPeriods = ctx.getModel().repaymentPeriods();
final LocalDate reAgingStartDate = loanTransaction.getLoanReAgeParameter().getStartDate();
final List<RepaymentPeriod> periodsBeforeReAging = repaymentPeriods.stream()
.filter(rp -> rp.getFromDate().isBefore(reAgingStartDate) && !rp.isFullyPaid()).toList();
final RepaymentPeriod lastPeriod = periodsBeforeReAging.getLast();
if (!lastPeriod.getDueDate().isEqual(reAgingStartDate)) {
// TODO: implement logic when re-aging changes the due dates
throw new NotImplementedException("Logic when re-aging changes the due dates not implemented");
}
final BigDecimal interestBeforeReAging = emiCalculator
.getPeriodInterestTillDate(ctx.getModel(), lastPeriod.getDueDate(), transactionDate, false).getAmount();
final AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
installments.forEach(i -> {
final Money principalOutstanding = i.getPrincipalOutstanding(currency);
if (principalOutstanding.isGreaterThanZero()) {
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
}
});
final AtomicReference<BigDecimal> interestFromZeroedInstallments = new AtomicReference<>(interestBeforeReAging);
installments.stream()
.filter(installment -> !installment.isObligationsMet() && !installment.getDueDate().isAfter(lastPeriod.getFromDate()))
.forEach(installment -> {
final BigDecimal currentInterest = interestFromZeroedInstallments.get();
final BigDecimal additionalInterest = MathUtil.nullToZero(installment.getInterestOutstanding(currency).getAmount()
.add(MathUtil.nullToZero(installment.getCreditedInterest()).negate()));
interestFromZeroedInstallments.set(currentInterest.add(additionalInterest));
});
final BigDecimal interestRate = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate();
final LoanApplicationTerms loanApplicationTerms = new LoanApplicationTerms.Builder().currency(currency.getCurrencyData())
.repaymentsStartingFromDate(reAgingStartDate).principal(outstandingPrincipalBalance.get())
.loanTermFrequency(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments())
.loanTermPeriodFrequencyType(loanTransaction.getLoanReAgeParameter().getFrequencyType())
.numberOfRepayments(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments())
.repaymentEvery(loanTransaction.getLoanReAgeParameter().getFrequencyNumber())
.repaymentPeriodFrequencyType(loanTransaction.getLoanReAgeParameter().getFrequencyType())
.interestRatePerPeriod(interestRate)
.interestRatePeriodFrequencyType(loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType())
.annualNominalInterestRate(interestRate).daysInMonthType(loan.getLoanProduct().fetchDaysInMonthType())
.daysInYearType(loan.getLoanProduct().fetchDaysInYearType()).inArrearsTolerance(Money.zero(currency, mc))
.isDownPaymentEnabled(false).downPaymentPercentage(ZERO).seedDate(reAgingStartDate)
.interestRecognitionOnDisbursementDate(
loan.getLoanProduct().getLoanProductRelatedDetail().isInterestRecognitionOnDisbursementDate())
.daysInYearCustomStrategy(loan.getLoanProduct().getLoanProductRelatedDetail().getDaysInYearCustomStrategy())
.interestMethod(loan.getLoanProductRelatedDetail().getInterestMethod()).allowPartialPeriodInterestCalculation(
loan.getLoanProduct().getLoanProductRelatedDetail().isAllowPartialPeriodInterestCalculation())
.mc(mc).build();
// Update the existing model with re-aged periods
emiCalculator.updateModelRepaymentPeriodsDuringReAge(ctx.getModel(), loanTransaction, loanApplicationTerms, mc);
updateInstallmentsByModelForReAging(loanTransaction, ctx);
loanTransaction.updateComponentsAndTotal(outstandingPrincipalBalance.get(),
Money.of(currency, interestFromZeroedInstallments.get()), Money.zero(currency), Money.zero(currency));
reprocessInstallments(installments);
}
}
@@ -27,6 +27,8 @@ import java.util.Optional;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
@@ -140,4 +142,7 @@ public interface EMICalculator {
* interest paused.
*/
void applyInterestPause(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate fromDate, LocalDate endDate);
void updateModelRepaymentPeriodsDuringReAge(ProgressiveLoanInterestScheduleModel scheduleModel, LoanTransaction loanTransaction,
LoanApplicationTerms loanApplicationTerms, MathContext mc);
}
@@ -40,6 +40,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearCustomStrategyType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
@@ -47,7 +48,11 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.apache.fineract.portfolio.loanproduct.calc.data.EmiAdjustment;
import org.apache.fineract.portfolio.loanproduct.calc.data.EmiChangeOperation;
import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod;
@@ -66,6 +71,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
private static final BigDecimal DIVISOR_100 = new BigDecimal("100");
private static final BigDecimal ONE_WEEK_IN_DAYS = BigDecimal.valueOf(7);
private final ScheduledDateGenerator scheduledDateGenerator;
@Override
@NotNull
public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
@@ -559,6 +566,129 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
}
@Override
public void updateModelRepaymentPeriodsDuringReAge(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LoanTransaction loanTransaction, final LoanApplicationTerms loanApplicationTerms, final MathContext mc) {
final LoanReAgeParameter loanReAgeParameter = loanTransaction.getLoanReAgeParameter();
final LocalDate reAgingStartDate = loanReAgeParameter.getStartDate();
final LocalDate transactionDate = loanTransaction.getTransactionDate();
final List<RepaymentPeriod> existingRepaymentPeriods = scheduleModel.repaymentPeriods();
moveOutstandingAmountsFromPeriodsBeforeReAging(existingRepaymentPeriods, reAgingStartDate);
final LocalDate periodStartDate = calculateFirstReAgedPeriodStartDate(loanReAgeParameter);
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generateTemporaryReAgedScheduleModel(loanApplicationTerms,
mc, periodStartDate, transactionDate);
mergeNewInterestScheduleModelWithExistingOne(scheduleModel, temporaryReAgedScheduleModel, loanTransaction);
}
/**
* * Merging the new temporary model of re-aged repayment periods and existing one together. After that recalculate
* the balances of the updated model and also recalculate the EMI if the EMI of the last repayment period differs
* significantly from other periods.
*/
private void mergeNewInterestScheduleModelWithExistingOne(final ProgressiveLoanInterestScheduleModel scheduleModel,
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel, final LoanTransaction loanTransaction) {
final List<RepaymentPeriod> newPeriods = temporaryReAgedScheduleModel.repaymentPeriods();
if (newPeriods.isEmpty()) {
return;
}
final List<RepaymentPeriod> existingRepaymentPeriods = scheduleModel.repaymentPeriods();
final LocalDate reAgingStartDate = loanTransaction.getLoanReAgeParameter().getStartDate();
final Optional<RepaymentPeriod> firstExistingRepaymentPeriodOpt = existingRepaymentPeriods.stream()
.filter(period -> period.getDueDate().equals(reAgingStartDate)).findFirst();
for (final RepaymentPeriod newPeriod : newPeriods) {
final Optional<RepaymentPeriod> existingRepaymentPeriodOpt = existingRepaymentPeriods.stream().filter(
period -> period.getFromDate().equals(newPeriod.getFromDate()) && period.getDueDate().equals(newPeriod.getDueDate()))
.findFirst();
Optional<RepaymentPeriod> previousExistingRepaymentPeriodOpt = Optional.empty();
if (existingRepaymentPeriodOpt.isPresent() && firstExistingRepaymentPeriodOpt.isPresent()
&& existingRepaymentPeriodOpt.get().equals(firstExistingRepaymentPeriodOpt.get())) {
previousExistingRepaymentPeriodOpt = existingRepaymentPeriodOpt.get().getPrevious();
}
final Money newPrincipal = newPeriod.getDuePrincipal();
final Money newInterest = newPeriod.getDueInterest();
final RepaymentPeriod rp = RepaymentPeriod.create(
previousExistingRepaymentPeriodOpt.orElseGet(existingRepaymentPeriods::getLast), newPeriod.getFromDate(),
newPeriod.getDueDate(), newPrincipal.add(newInterest), MoneyHelper.getMathContext(),
loanTransaction.getLoan().getLoanProductRelatedDetail());
rp.setTotalDisbursedAmount(scheduleModel.repaymentPeriods().getFirst().getTotalDisbursedAmount());
existingRepaymentPeriodOpt.ifPresent(existingRepaymentPeriods::remove);
existingRepaymentPeriods.add(rp);
calculateRateFactorForRepaymentPeriod(rp, scheduleModel);
}
final RepaymentPeriod lastReAgedInstallment = newPeriods.getLast();
final List<RepaymentPeriod> reAgedRepaymentPeriods = existingRepaymentPeriods.stream()
.filter(repaymentPeriod -> (!repaymentPeriod.getFromDate().isBefore(reAgingStartDate)
|| repaymentPeriod.getDueDate().isEqual(reAgingStartDate))
&& !repaymentPeriod.getDueDate().isAfter(lastReAgedInstallment.getDueDate()))
.toList();
calculateOutstandingBalance(scheduleModel);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel, loanTransaction.getTransactionDate());
checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(scheduleModel, reAgedRepaymentPeriods);
}
/**
* * Generates temporary interestScheduleModel with re-aged repayment periods
*/
@NotNull
private ProgressiveLoanInterestScheduleModel generateTemporaryReAgedScheduleModel(final LoanApplicationTerms loanApplicationTerms,
final MathContext mc, final LocalDate periodStartDate, final LocalDate transactionDate) {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
periodStartDate, loanApplicationTerms, null);
final ProgressiveLoanInterestScheduleModel temporaryReAgedScheduleModel = generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), null,
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
addDisbursement(temporaryReAgedScheduleModel, EmiChangeOperation.disburse(transactionDate, loanApplicationTerms.getPrincipal()));
return temporaryReAgedScheduleModel;
}
/**
* * Based on the re-aging start date and frequency data calculates start date for the first re-aged period, which
* is used to generate re-aged repayment periods
*/
@NotNull
private static LocalDate calculateFirstReAgedPeriodStartDate(final LoanReAgeParameter loanReAgeParameter) {
final LocalDate reAgingStartDate = loanReAgeParameter.getStartDate();
return switch (loanReAgeParameter.getFrequencyType()) {
case DAYS -> reAgingStartDate.minusDays(loanReAgeParameter.getFrequencyNumber());
case WEEKS -> reAgingStartDate.minusWeeks(loanReAgeParameter.getFrequencyNumber());
case MONTHS -> reAgingStartDate.minusMonths(loanReAgeParameter.getFrequencyNumber());
case YEARS -> reAgingStartDate.minusYears(loanReAgeParameter.getFrequencyNumber());
case WHOLE_TERM -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: WHOLE_TERM");
case INVALID -> throw new IllegalStateException("Unexpected RecalculationFrequencyType: INVALID");
};
}
/**
* * Zeroing out the EMI of the repayment periods, that are before re-aging and not been fully paid. And decreases
* the balance correction amount (added during interest recalculation for the business date) by the amount of the
* principal that was moved.
*/
private static void moveOutstandingAmountsFromPeriodsBeforeReAging(final List<RepaymentPeriod> existingRepaymentPeriods,
final LocalDate reAgingStartDate) {
final List<RepaymentPeriod> periodsBeforeReAging = existingRepaymentPeriods.stream()
.filter(rp -> rp.getFromDate().isBefore(reAgingStartDate) && !rp.isFullyPaid()).toList();
periodsBeforeReAging.forEach(rp -> {
final InterestPeriod lastInterestPeriod = rp.getInterestPeriods().getLast();
lastInterestPeriod.addBalanceCorrectionAmount(rp.getOutstandingPrincipal().negated());
rp.setEmi(rp.getTotalPaidAmount());
});
}
private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate tillDate) {
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
.reduce((first, second) -> second);
@@ -44,7 +44,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class LoanScheduleGeneratorTest {
private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator();
private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(mock(ScheduledDateGenerator.class));
private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$");
private static final CurrencyData CURRENCY = APPLICATION_CURRENCY.toData();
private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22);
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.portfolio.loanproduct.calc;
import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
@@ -39,6 +41,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestScheduleModelParserServiceGsonImpl;
import org.apache.fineract.portfolio.loanproduct.calc.data.InterestPeriod;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
@@ -64,7 +67,7 @@ import org.springframework.lang.NonNull;
@ExtendWith(MockitoExtension.class)
class ProgressiveEMICalculatorTest {
private static ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator();
private static ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(mock(ScheduledDateGenerator.class));
private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class);
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
@@ -860,7 +860,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
loan.getLoanTransactions().add(refundTransaction);
@@ -278,7 +278,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
if (reprocessRequired) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
@@ -857,7 +858,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
if (reprocessRequired) {
addInstallmentIfPenaltyAppliedAfterLastDueDate(loan, lastChargeDate);
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
@@ -888,7 +890,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
@@ -1446,7 +1449,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
&& DateUtils.isBefore(loanCharge.getDueLocalDate(), businessDate)) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
// Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule
@@ -994,7 +994,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
@@ -1304,6 +1304,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
validateLoanTransactionAmountChargeBack(loanTransaction, newTransaction);
if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
}
// Store the Loan Transaction Relation
LoanTransactionRelation loanTransactionRelation = LoanTransactionRelation.linkToTransaction(loanTransaction, newTransaction,
LoanTransactionRelationTypeEnum.CHARGEBACK);
@@ -2273,11 +2282,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
updateDisbursementDateAndAmountForTranche(loan, loanDisbursementDetails, command, changes, scheduleGeneratorDTO);
} else {
loan.getLoanProductRelatedDetail().setPrincipal(loan.getPrincipalAmountForRepaymentSchedule());
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
reprocessLoanTransactionsService.processPostDisbursementTransactions(loan);
}
@@ -2432,7 +2441,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
}
@@ -3000,7 +3010,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
loan.addLoanTransaction(interestRefundTxn);
@@ -3264,7 +3275,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
reprocessLoanTransactionsService.reprocessTransactions(loan);
@@ -3373,7 +3385,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
} else if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
@@ -3430,7 +3442,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
writeOffTransaction.reverse();
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, loan);
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
@@ -81,9 +81,8 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
LocalDate relatedRefundTransactionDate, List<LoanTransaction> transactionsToReprocess) {
List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>(
loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList());
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
@@ -165,8 +165,13 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
ProgressiveLoanInterestScheduleModel model = savedModel
.orElseGet(() -> advancedProcessor.calculateInterestScheduleModel(loan.getId(), loanTransaction.getTransactionDate()));
transactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), model);
final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
new ChangedTransactionDetail(), model);
progressiveTransactionCtx.setChargedOff(loan.isChargedOff());
progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff());
progressiveTransactionCtx.setContractTerminated(loan.isContractTermination());
transactionCtx = progressiveTransactionCtx;
} else {
transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail());
@@ -24,6 +24,7 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
@@ -95,16 +96,25 @@ public class LoanReAgingServiceImpl {
LoanReAgeParameter reAgeParameter = createReAgeParameter(reAgeTransaction, command);
reAgeTransaction.setLoanReAgeParameter(reAgeParameter);
loanTransactionRepository.saveAndFlush(reAgeTransaction);
if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())
&& !loan.isInterestBearingAndInterestRecalculationEnabled()) {
reprocessLoanTransactionsService.reprocessTransactionsWithPostTransactionChecks(loan, reAgeTransaction.getTransactionDate());
} else if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction()
|| (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
final List<LoanTransaction> loanTransactions = loanTransactionRepository.findNonReversedTransactionsForReprocessingByLoan(loan);
loanTransactions.add(reAgeTransaction);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
} else {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(reAgeTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
}
loan.updateLoanScheduleDependentDerivedFields();
persistNote(loan, command, changes);
@@ -135,13 +145,13 @@ public class LoanReAgingServiceImpl {
if (reAgeTransaction == null) {
throw new LoanTransactionNotFoundException("Re-Age transaction for loan was not found");
}
reverseReAgeTransaction(reAgeTransaction, command);
loanTransactionRepository.saveAndFlush(reAgeTransaction);
if (loan.isProgressiveSchedule() && ((loan.hasChargeOffTransaction() && loan.hasAccelerateChargeOffStrategy())
|| loan.hasContractTerminationTransaction())) {
|| loan.hasContractTerminationTransaction() || (loan.isInterestRecalculationEnabled() && loan.hasReAgingTransaction()))) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
}
reverseReAgeTransaction(reAgeTransaction, command);
loanTransactionRepository.saveAndFlush(reAgeTransaction);
reprocessLoanTransactionsService.reprocessTransactions(loan);
loan.updateLoanScheduleDependentDerivedFields();
persistNote(loan, command, changes);
@@ -33,7 +33,6 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -68,7 +67,7 @@ public class LoanReAgingValidator {
.notExceedingLengthOf(100);
LocalDate startDate = command.localDateValueOfParameterNamed(LoanReAgingApiConstants.startDate);
if (loan.isProgressiveSchedule() && !loan.isInterestBearing()) {
if (loan.isProgressiveSchedule()) {
baseDataValidator.reset().parameter(LoanReAgingApiConstants.startDate).value(startDate).notNull()
.validateDateAfterOrEqual(loan.getDisbursementDate());
} else {
@@ -105,14 +104,6 @@ public class LoanReAgingValidator {
}
private void validateReAgeBusinessRules(Loan loan) {
// validate reaging shouldn't happen before maturity
// on progressive loans it can
if (!(loan.isProgressiveSchedule() && !loan.isInterestBearing())
&& DateUtils.isBefore(getBusinessLocalDate(), loan.getMaturityDate())) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.reage.cannot.be.submitted.before.maturity",
"Loan cannot be re-aged before maturity", loan.getId());
}
// validate reaging is only available for progressive schedule & advanced payment allocation
LoanScheduleType loanScheduleType = LoanScheduleType.valueOf(loan.getLoanProductRelatedDetail().getLoanScheduleType().name());
boolean isProgressiveSchedule = LoanScheduleType.PROGRESSIVE.equals(loanScheduleType);
@@ -127,12 +118,6 @@ public class LoanReAgingValidator {
loan.getId());
}
// validate reaging is only available for non-interest bearing loans
if (loan.isInterestBearing()) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.reage.supported.only.for.non.interest.loans",
"Loan reaging is only available for non-interest bearing loans", loan.getId());
}
// validate reaging is only done on an active loan
if (!loan.getStatus().isActive()) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.reage.supported.only.for.active.loans",
@@ -288,20 +288,6 @@ class LoanReAgingValidatorTest {
.isEqualTo("validation.msg.loan.reAge.numberOfInstallments.not.greater.than.zero");
}
@Test
public void testValidateReAge_ShouldThrowException_WhenLoanIsBeforeMaturity() {
// given
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
Loan loan = loan();
JsonCommand command = jsonCommand();
// when
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
() -> underTest.validateReAge(loan, command));
// then
assertThat(result).isNotNull();
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reage.cannot.be.submitted.before.maturity");
}
@Test
public void testValidateReAge_ShouldThrowException_WhenStartDateIsBeforeMaturity() {
// given
@@ -350,20 +336,6 @@ class LoanReAgingValidatorTest {
.isEqualTo("error.msg.loan.reage.supported.only.for.progressive.loan.schedule.type");
}
@Test
public void testValidateReAge_ShouldThrowException_WhenLoanIsInterestBearing() {
// given
Loan loan = loan();
given(loan.isInterestBearing()).willReturn(true);
JsonCommand command = jsonCommand();
// when
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
() -> underTest.validateReAge(loan, command));
// then
assertThat(result).isNotNull();
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reage.supported.only.for.non.interest.loans");
}
@Test
public void testValidateReAge_ShouldThrowException_WhenLoanIsNotActive() {
// given