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:
committed by
Adam Saghy
parent
23c1b2b8af
commit
d21a1a98a2
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+2
-4
@@ -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());
|
||||
}
|
||||
|
||||
+213
-67
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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);
|
||||
}
|
||||
|
||||
+130
@@ -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);
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+4
-1
@@ -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);
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
+20
-8
@@ -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);
|
||||
}
|
||||
|
||||
+1
-2
@@ -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);
|
||||
}
|
||||
|
||||
+7
-2
@@ -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());
|
||||
|
||||
+18
-8
@@ -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);
|
||||
|
||||
+1
-16
@@ -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",
|
||||
|
||||
-28
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user