FINERACT-2326: Add the ability to reschedule non-interest-bearing progressive loans to have interest
This commit is contained in:
+1
-2
@@ -75,8 +75,7 @@ public class LoanRescheduleStepDef extends AbstractStepDef {
|
||||
: Integer.valueOf(rescheduleData.get(4));
|
||||
Integer extraTerms = (rescheduleData.get(5) == null || "0".equals(rescheduleData.get(5))) ? null
|
||||
: Integer.valueOf(rescheduleData.get(5));
|
||||
BigDecimal newInterestRate = (rescheduleData.get(6) == null || "0".equals(rescheduleData.get(6))) ? null
|
||||
: new BigDecimal(rescheduleData.get(6));
|
||||
BigDecimal newInterestRate = (rescheduleData.get(6) == null) ? null : new BigDecimal(rescheduleData.get(6));
|
||||
|
||||
PostCreateRescheduleLoansRequest request = new PostCreateRescheduleLoansRequest()//
|
||||
.loanId(loanId)//
|
||||
|
||||
@@ -6708,8 +6708,8 @@ Feature: Loan
|
||||
| 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 |
|
||||
| | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
| 3 | 0 | 01 April 2024 | 01 April 2024 | 478.31 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 |
|
||||
| 4 | 15 | 13 April 2024 | | 239.16 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 121.89 | 121.89 | 0.0 | 117.26 |
|
||||
| 5 | 15 | 28 April 2024 | | 0.0 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 121.9 | 121.9 | 0.0 | 117.26 |
|
||||
| 4 | 15 | 13 April 2024 | | 239.15 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 121.89 | 121.89 | 0.0 | 117.27 |
|
||||
| 5 | 15 | 28 April 2024 | | 0.0 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 121.9 | 121.9 | 0.0 | 117.25 |
|
||||
Then Loan Repayment schedule has the following data in Total row:
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 800.27 | 0.0 | 0.0 | 0.0 | 800.27 | 565.75 | 365.68 | 0.0 | 234.52 |
|
||||
@@ -6733,8 +6733,8 @@ Feature: Loan
|
||||
| 2 | 15 | 29 March 2024 | 24 March 2024 | 243.79 | 121.89 | 0.0 | 0.0 | 0.0 | 121.89 | 121.89 | 121.89 | 0.0 | 0.0 |
|
||||
| | | 01 April 2024 | | 312.69 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
| 3 | 0 | 01 April 2024 | 01 April 2024 | 478.31 | 78.17 | 0.0 | 0.0 | 0.0 | 78.17 | 78.17 | 0.0 | 0.0 | 0.0 |
|
||||
| 4 | 15 | 13 April 2024 | 10 April 2024 | 239.16 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 239.15 | 239.15 | 0.0 | 0.0 |
|
||||
| 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 239.16 | 239.16 | 0.0 | 0.0 |
|
||||
| 4 | 15 | 13 April 2024 | 10 April 2024 | 239.15 | 239.16 | 0.0 | 0.0 | 0.0 | 239.16 | 239.16 | 239.16 | 0.0 | 0.0 |
|
||||
| 5 | 15 | 28 April 2024 | 10 April 2024 | 0.0 | 239.15 | 0.0 | 0.0 | 0.0 | 239.15 | 239.15 | 239.15 | 0.0 | 0.0 |
|
||||
Then Loan Repayment schedule has the following data in Total row:
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 800.27 | 0.0 | 0.0 | 0.0 | 800.27 | 800.27 | 600.2 | 0.0 | 0.0 |
|
||||
|
||||
@@ -1788,4 +1788,4 @@ Feature: Credit Balance Refund
|
||||
| 25 March 2025 | Credit Balance Refund | 122.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 01 April 2025 | Disbursement | 243.79 | 0.0 | 0.0 | 0.0 | 0.0 | 243.79 | false | false |
|
||||
| 01 April 2025 | Down Payment | 61.0 | 61.0 | 0.0 | 0.0 | 0.0 | 182.79 | false | false |
|
||||
Then Loan status will be "ACTIVE"
|
||||
Then Loan status will be "ACTIVE"
|
||||
|
||||
@@ -917,141 +917,92 @@ Feature: LoanReschedule
|
||||
And 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 |
|
||||
|
||||
@TestRailId:C3809
|
||||
Scenario: Verify change interest rate with 0->4 reschedule scenario - UC1
|
||||
When Admin sets the business date to "01 April 2025"
|
||||
|
||||
@TestRailId:C3882
|
||||
Scenario: Reschedule progressive loan to 0 percent interest then back to 10 percent
|
||||
When Admin sets the business date to "1 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_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 April 2025 | 1000 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||
And Admin successfully approves the loan on "01 April 2025" with "1000" amount and expected disbursement date on "01 April 2025"
|
||||
When Admin successfully disburse the loan on "01 April 2025" with "1000" EUR transaction amount
|
||||
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| | | 01 April 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
| 1 | 30 | 01 May 2025 | | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
| 2 | 31 | 01 June 2025 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
| 3 | 30 | 01 July 2025 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
| 4 | 31 | 01 August 2025 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
| 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_EMI_360_30 | 1 January 2024 | 100 | 10 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||
And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024"
|
||||
When Admin successfully disburse the loan on "1 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.67 | 16.33 | 0.83 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 |
|
||||
| 2 | 29 | 01 March 2024 | | 67.21 | 16.46 | 0.7 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 |
|
||||
| 3 | 31 | 01 April 2024 | | 50.61 | 16.6 | 0.56 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 |
|
||||
| 4 | 30 | 01 May 2024 | | 33.87 | 16.74 | 0.42 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 |
|
||||
| 5 | 31 | 01 June 2024 | | 16.99 | 16.88 | 0.28 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 |
|
||||
| 6 | 30 | 01 July 2024 | | 0.0 | 16.99 | 0.14 | 0.0 | 0.0 | 17.13 | 0.0 | 0.0 | 0.0 | 17.13 |
|
||||
Then Loan Repayment schedule has the following data in Total row:
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 April 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||
When Admin sets the business date to "01 May 2025"
|
||||
And Admin runs inline COB job for Loan
|
||||
And Customer makes "AUTOPAY" repayment on "01 May 2025" with 250.0 EUR transaction amount
|
||||
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| | | 01 April 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
| 1 | 30 | 01 May 2025 | 01 May 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 |
|
||||
| 2 | 31 | 01 June 2025 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
| 3 | 30 | 01 July 2025 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
| 4 | 31 | 01 August 2025 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 100.0 | 2.93 | 0.0 | 0.0 | 102.93 | 0.0 | 0.0 | 0.0 | 102.93 |
|
||||
When Admin creates and approves Loan reschedule with the following data:
|
||||
| rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
|
||||
| 02 January 2024 | 01 January 2024 | | | | | 0 |
|
||||
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.33 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 2 | 29 | 01 March 2024 | | 66.66 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 3 | 31 | 01 April 2024 | | 49.99 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 4 | 30 | 01 May 2024 | | 33.32 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 5 | 31 | 01 June 2024 | | 16.65 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 6 | 30 | 01 July 2024 | | 0.0 | 16.65 | 0.0 | 0.0 | 0.0 | 16.65 | 0.0 | 0.0 | 0.0 | 16.65 |
|
||||
Then Loan Repayment schedule has the following data in Total row:
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 250.0 | 0.0 | 0.0 | 750.0 |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 April 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||
| 01 May 2025 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false |
|
||||
When Admin sets the business date to "05 May 2025"
|
||||
And Admin runs inline COB job for Loan
|
||||
# --- Loan reschedule: Interest rate modification effective from next day ---
|
||||
#When Admin attempt Loan reschedule with changing interest rate to zero following data validation error should occur
|
||||
Then Loan reschedule with the following data results a 400 error and "LOAN_RESCHEDULE_NOT_ALLOWED_FROM_ZERO_TO_NEW_INTEREST_RATE" error message
|
||||
| rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
|
||||
| 06 May 2025 | 05 May 2025 | | | | | 4 |
|
||||
When Admin sets the business date to "06 May 2025"
|
||||
And Customer makes "AUTOPAY" repayment on "06 May 2025" with 100.0 EUR transaction amount
|
||||
# TODO uncomment and update repayment schedule after fixing PS-2665
|
||||
# Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||
# | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
# | | | 01 April 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
# | 1 | 30 | 01 May 2025 | 01 May 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 |
|
||||
# | 2 | 31 | 01 June 2025 | | 500.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 100.0 | 100.0 | 0.0 | 150.0 |
|
||||
# | 3 | 30 | 01 July 2025 | | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
# | 4 | 31 | 01 August 2025 | | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 |
|
||||
# Then Loan Repayment schedule has the following data in Total row:
|
||||
# | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
# | 1000.0 | 0.0 | 0.0 | 0.0 | 1000.0 | 350.0 | 100.0 | 0.0 | 650.0 |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 April 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||
| 01 May 2025 | Repayment | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | 750.0 | false | false |
|
||||
| 06 May 2025 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 650.0 | false | false |
|
||||
Then Loan status will be "ACTIVE"
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
|
||||
When Admin creates and approves Loan reschedule with the following data:
|
||||
| rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
|
||||
| 03 January 2024 | 01 January 2024 | | | | | 10 |
|
||||
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.66 | 16.34 | 0.81 | 0.0 | 0.0 | 17.15 | 0.0 | 0.0 | 0.0 | 17.15 |
|
||||
| 2 | 29 | 01 March 2024 | | 67.21 | 16.45 | 0.7 | 0.0 | 0.0 | 17.15 | 0.0 | 0.0 | 0.0 | 17.15 |
|
||||
| 3 | 31 | 01 April 2024 | | 50.62 | 16.59 | 0.56 | 0.0 | 0.0 | 17.15 | 0.0 | 0.0 | 0.0 | 17.15 |
|
||||
| 4 | 30 | 01 May 2024 | | 33.89 | 16.73 | 0.42 | 0.0 | 0.0 | 17.15 | 0.0 | 0.0 | 0.0 | 17.15 |
|
||||
| 5 | 31 | 01 June 2024 | | 17.02 | 16.87 | 0.28 | 0.0 | 0.0 | 17.15 | 0.0 | 0.0 | 0.0 | 17.15 |
|
||||
| 6 | 30 | 01 July 2024 | | 0.0 | 17.02 | 0.14 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 |
|
||||
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.91 | 0.0 | 0.0 | 102.91 | 0.0 | 0.0 | 0.0 | 102.91 |
|
||||
|
||||
@TestRailId:C3810
|
||||
Scenario: Verify change interest rate with 4->0 reschedule scenario - UC2
|
||||
When Admin sets the business date to "01 April 2025"
|
||||
@TestRailId:C3883
|
||||
Scenario: Create progressive loan with 0 percent interest then to 10 percent after first period
|
||||
When Admin sets the business date to "1 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_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE | 01 April 2025 | 1000 | 4 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||
And Admin successfully approves the loan on "01 April 2025" with "1000" amount and expected disbursement date on "01 April 2025"
|
||||
When Admin successfully disburse the loan on "01 April 2025" with "1000" EUR transaction amount
|
||||
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| | | 01 April 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
| 1 | 30 | 01 May 2025 | | 751.24 | 248.76 | 3.33 | 0.0 | 0.0 | 252.09 | 0.0 | 0.0 | 0.0 | 252.09 |
|
||||
| 2 | 31 | 01 June 2025 | | 501.65 | 249.59 | 2.5 | 0.0 | 0.0 | 252.09 | 0.0 | 0.0 | 0.0 | 252.09 |
|
||||
| 3 | 30 | 01 July 2025 | | 251.23 | 250.42 | 1.67 | 0.0 | 0.0 | 252.09 | 0.0 | 0.0 | 0.0 | 252.09 |
|
||||
| 4 | 31 | 01 August 2025 | | 0.0 | 251.23 | 0.84 | 0.0 | 0.0 | 252.07 | 0.0 | 0.0 | 0.0 | 252.07 |
|
||||
| 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_EMI_360_30 | 1 January 2024 | 100 | 0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||
And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024"
|
||||
When Admin successfully disburse the loan on "1 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.33 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 2 | 29 | 01 March 2024 | | 66.66 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 3 | 31 | 01 April 2024 | | 49.99 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 4 | 30 | 01 May 2024 | | 33.32 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 5 | 31 | 01 June 2024 | | 16.65 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 6 | 30 | 01 July 2024 | | 0.0 | 16.65 | 0.0 | 0.0 | 0.0 | 16.65 | 0.0 | 0.0 | 0.0 | 16.65 |
|
||||
Then Loan Repayment schedule has the following data in Total row:
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 1000.0 | 8.34 | 0.0 | 0.0 | 1008.34 | 0.0 | 0.0 | 0.0 | 1008.34 |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 April 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||
When Admin sets the business date to "01 May 2025"
|
||||
And Admin runs inline COB job for Loan
|
||||
And Customer makes "AUTOPAY" repayment on "01 May 2025" with 252.09 EUR transaction amount
|
||||
Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| | | 01 April 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
| 1 | 30 | 01 May 2025 | 01 May 2025 | 751.24 | 248.76 | 3.33 | 0.0 | 0.0 | 252.09 | 252.09 | 0.0 | 0.0 | 0.0 |
|
||||
| 2 | 31 | 01 June 2025 | | 501.65 | 249.59 | 2.5 | 0.0 | 0.0 | 252.09 | 0.0 | 0.0 | 0.0 | 252.09 |
|
||||
| 3 | 30 | 01 July 2025 | | 251.23 | 250.42 | 1.67 | 0.0 | 0.0 | 252.09 | 0.0 | 0.0 | 0.0 | 252.09 |
|
||||
| 4 | 31 | 01 August 2025 | | 0.0 | 251.23 | 0.84 | 0.0 | 0.0 | 252.07 | 0.0 | 0.0 | 0.0 | 252.07 |
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
|
||||
When Admin creates and approves Loan reschedule with the following data:
|
||||
| rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
|
||||
| 02 February 2024 | 01 January 2024 | | | | | 10 |
|
||||
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.33 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 | 0.0 | 0.0 | 0.0 | 16.67 |
|
||||
| 2 | 29 | 01 March 2024 | | 66.94 | 16.39 | 0.69 | 0.0 | 0.0 | 17.08 | 0.0 | 0.0 | 0.0 | 17.08 |
|
||||
| 3 | 31 | 01 April 2024 | | 50.42 | 16.52 | 0.56 | 0.0 | 0.0 | 17.08 | 0.0 | 0.0 | 0.0 | 17.08 |
|
||||
| 4 | 30 | 01 May 2024 | | 33.76 | 16.66 | 0.42 | 0.0 | 0.0 | 17.08 | 0.0 | 0.0 | 0.0 | 17.08 |
|
||||
| 5 | 31 | 01 June 2024 | | 16.96 | 16.8 | 0.28 | 0.0 | 0.0 | 17.08 | 0.0 | 0.0 | 0.0 | 17.08 |
|
||||
| 6 | 30 | 01 July 2024 | | 0.0 | 16.96 | 0.14 | 0.0 | 0.0 | 17.1 | 0.0 | 0.0 | 0.0 | 17.1 |
|
||||
Then Loan Repayment schedule has the following data in Total row:
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 1000.0 | 8.34 | 0.0 | 0.0 | 1008.34 | 252.09 | 0.0 | 0.0 | 756.25 |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 April 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||
| 30 April 2025 | Accrual | 3.22 | 0.0 | 3.22 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 01 May 2025 | Repayment | 252.09 | 248.76 | 3.33 | 0.0 | 0.0 | 751.24 | false | false |
|
||||
When Admin sets the business date to "05 May 2025"
|
||||
And Admin runs inline COB job for Loan
|
||||
# --- Loan reschedule: Interest rate modification effective from next day ---
|
||||
#When Admin attempt Loan reschedule with changing interest rate from zero to 4 following data validation error should occur
|
||||
Then Loan reschedule with the following data results a 400 error and "LOAN_RESCHEDULE_NOT_ALLOWED_FROM_CURRENT_INTEREST_RATE_TO_ZERO" error message
|
||||
| rescheduleFromDate | submittedOnDate | adjustedDueDate | graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
|
||||
| 06 May 2025 | 05 May 2025 | | | | | 0 |
|
||||
When Admin sets the business date to "06 May 2025"
|
||||
And Customer makes "AUTOPAY" repayment on "06 May 2025" with 100.0 EUR transaction amount
|
||||
# TODO uncomment and update repayment schedule after fixing PS-2634
|
||||
# Then Loan Repayment schedule has 4 periods, with the following data for periods:
|
||||
# | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
# | | | 01 April 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||
# | 1 | 30 | 01 May 2025 | 01 May 2025 | 751.24 | 248.76 | 3.33 | 0.0 | 0.0 | 252.09 | 252.09 | 0.0 | 0.0 | 0.0 |
|
||||
# | 2 | 31 | 01 June 2025 | | 501.37 | 249.87 | 2.22 | 0.0 | 0.0 | 252.09 | 100.0 | 100.0 | 0.0 | 152.09 |
|
||||
# | 3 | 30 | 01 July 2025 | | 250.95 | 250.42 | 1.67 | 0.0 | 0.0 | 252.09 | 0.0 | 0.0 | 0.0 | 252.09 |
|
||||
# | 4 | 31 | 01 August 2025 | | 0.0 | 250.95 | 0.84 | 0.0 | 0.0 | 251.79 | 0.0 | 0.0 | 0.0 | 251.79 |
|
||||
# Then Loan Repayment schedule has the following data in Total row:
|
||||
# | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
# | 1000.0 | 8.06 | 0.0 | 0.0 | 1008.06 | 352.09 | 100.0 | 0.0 | 655.97 |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||
| 01 April 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false |
|
||||
| 30 April 2025 | Accrual | 3.22 | 0.0 | 3.22 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 01 May 2025 | Repayment | 252.09 | 248.76 | 3.33 | 0.0 | 0.0 | 751.24 | false | false |
|
||||
| 01 May 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 02 May 2025 | Accrual | 0.08 | 0.0 | 0.08 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 03 May 2025 | Accrual | 0.08 | 0.0 | 0.08 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 04 May 2025 | Accrual | 0.08 | 0.0 | 0.08 | 0.0 | 0.0 | 0.0 | false | false |
|
||||
| 06 May 2025 | Repayment | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 651.24 | false | false |
|
||||
Then Loan status will be "ACTIVE"
|
||||
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||
| 100.0 | 2.09 | 0.0 | 0.0 | 102.09 | 0.0 | 0.0 | 0.0 | 102.09 |
|
||||
|
||||
+23
-5
@@ -507,7 +507,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
MoneyHolder overpaymentHolder = ctx.getOverpaymentHolder();
|
||||
|
||||
if (ctx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
|
||||
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
&& loanTransaction.getLoan().isInterestRecalculationEnabled()) {
|
||||
var model = progressiveTransactionCtx.getModel();
|
||||
|
||||
// Copy and paste Logic from super.handleCreditBalanceRefund
|
||||
@@ -778,7 +778,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
protected void processCreditTransaction(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||
if (loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
if (loanTransaction.getLoan().isInterestRecalculationEnabled()) {
|
||||
processCreditTransactionWithEmiCalculator(loanTransaction, (ProgressiveTransactionCtx) ctx);
|
||||
} else if (hasNoCustomCreditAllocationRule(loanTransaction)) {
|
||||
super.processCreditTransaction(loanTransaction, ctx.getOverpaymentHolder(), ctx.getCurrency(), ctx.getInstallments());
|
||||
@@ -1224,8 +1224,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
|
||||
private void handleDisbursement(LoanTransaction disbursementTransaction, TransactionCtx transactionCtx) {
|
||||
// TODO: Fix this and enhance EMICalculator to support reamortization and reaging
|
||||
final Loan loan = disbursementTransaction.getLoan();
|
||||
if (loan.isInterestBearing()) {
|
||||
if (shouldUseEmiCalculation(transactionCtx, disbursementTransaction.getTransactionDate())) {
|
||||
handleDisbursementWithEMICalculator(disbursementTransaction, transactionCtx);
|
||||
} else {
|
||||
handleDisbursementWithoutEMICalculator(disbursementTransaction, transactionCtx);
|
||||
@@ -1356,7 +1355,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
private void handleCapitalizedIncome(LoanTransaction capitalizedIncomeTransaction, TransactionCtx transactionCtx) {
|
||||
if (capitalizedIncomeTransaction.getLoan().isInterestBearing()) {
|
||||
// TODO: Fix this and enhance EMICalculator to support reamortization and reaging
|
||||
if (shouldUseEmiCalculation(transactionCtx, capitalizedIncomeTransaction.getTransactionDate())) {
|
||||
handleCapitalizedIncomeWithEMICalculator(capitalizedIncomeTransaction, transactionCtx);
|
||||
} else {
|
||||
handleCapitalizedIncomeWithoutEMICalculator(capitalizedIncomeTransaction, transactionCtx);
|
||||
@@ -1476,6 +1476,24 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldUseEmiCalculation(TransactionCtx transactionCtx, LocalDate transactionDate) {
|
||||
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) {
|
||||
boolean hasActiveReAmortization = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
|
||||
.anyMatch(t -> t.getTypeOf().isReAmortize() && t.isNotReversed());
|
||||
boolean hasActiveReAge = progressiveTransactionCtx.getAlreadyProcessedTransactions().stream()
|
||||
.anyMatch(t -> t.getTypeOf().isReAge() && t.isNotReversed());
|
||||
if (hasActiveReAmortization) {
|
||||
return false;
|
||||
} else {
|
||||
return !hasActiveReAge || !DateUtils.isAfter(transactionDate, progressiveTransactionCtx.getModel().getMaturityDate());
|
||||
}
|
||||
}
|
||||
// From now on we are defaulting to using the EMICalculator on all progressive loans. However currently the
|
||||
// model is not aware of re-aging and re-amortization. So only these specific cases should ignore this
|
||||
// requirement. This method can be removed once these operations are supported by the EMI model.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleWriteOff(final LoanTransaction transaction, TransactionCtx ctx) {
|
||||
super.handleWriteOff(transaction, ctx.getCurrency(), ctx.getInstallments());
|
||||
}
|
||||
|
||||
+10
-3
@@ -24,7 +24,6 @@ import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.Loan
|
||||
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateAndRetrieveRescheduleFromDate;
|
||||
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateApprovalDate;
|
||||
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateForOverdueCharges;
|
||||
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateInterestRate;
|
||||
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateLoanIsActive;
|
||||
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleReasonComment;
|
||||
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleReasonId;
|
||||
@@ -78,8 +77,7 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche
|
||||
validateRescheduleReasonId(fromJsonHelper, jsonElement, dataValidatorBuilder);
|
||||
validateRescheduleReasonComment(fromJsonHelper, jsonElement, dataValidatorBuilder);
|
||||
LocalDate adjustedDueDate = validateAndRetrieveAdjustedDate(fromJsonHelper, jsonElement, rescheduleFromDate, dataValidatorBuilder);
|
||||
BigDecimal interestRate = validateInterestRate(loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate(), fromJsonHelper,
|
||||
jsonElement, dataValidatorBuilder);
|
||||
BigDecimal interestRate = validateInterestRate(fromJsonHelper, jsonElement, dataValidatorBuilder);
|
||||
validateUnsupportedParams(jsonElement, dataValidatorBuilder);
|
||||
|
||||
boolean hasInterestRateChange = interestRate != null;
|
||||
@@ -197,4 +195,13 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche
|
||||
"Interest rate change for the provided date is already exists.", rescheduleFromDate);
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal validateInterestRate(final FromJsonHelper fromJsonHelper, final JsonElement jsonElement,
|
||||
DataValidatorBuilder dataValidatorBuilder) {
|
||||
final BigDecimal interestRate = fromJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(RescheduleLoansApiConstants.newInterestRateParamName, jsonElement);
|
||||
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.newInterestRateParamName).value(interestRate).ignoreIfNull()
|
||||
.zeroOrPositiveAmount();
|
||||
return interestRate;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -182,7 +182,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
|
||||
}
|
||||
|
||||
private void recalculateLoanTransactions(Loan loan, LocalDate transactionDate, LoanTransaction transaction) {
|
||||
if (loan.isInterestBearingAndInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transactionDate)) {
|
||||
if (loan.isInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transactionDate)) {
|
||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||
} else {
|
||||
reprocessLoanTransactionsService.processLatestTransaction(transaction, loan);
|
||||
|
||||
+8
-2
@@ -161,6 +161,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
}
|
||||
|
||||
private void addCapitalizedIncome(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
scheduleModel.repaymentPeriods().stream().filter(rp -> !operation.getSubmittedOnDate().isAfter(rp.getFromDate()))
|
||||
.forEach(rp -> rp.setTotalCapitalizedIncomeAmount(
|
||||
MathUtil.nullToZero(rp.getTotalCapitalizedIncomeAmount()).add(operation.getAmount().getAmount())));
|
||||
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), scheduleModel.zero(),
|
||||
scheduleModel.zero(), operation.getAmount()).ifPresent((repaymentPeriod) -> {
|
||||
calculateEMIValueAndRateFactors(
|
||||
@@ -920,9 +924,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
// already repaid principals should be subtracted from total disbursed amount to calculate correct EMI.
|
||||
BigDecimal alreadyRepaidPrincipals = first.getPrevious()
|
||||
.map(rp -> rp.calculateTotalDisbursedAmountTillGivenPeriod(null).subtract(rp.getOutstandingLoanBalance().getAmount()))
|
||||
.map(rp -> rp.calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(null)
|
||||
.subtract(rp.getOutstandingLoanBalance().getAmount()))
|
||||
.orElse(BigDecimal.ZERO);
|
||||
Money total = Money.of(currency, first.calculateTotalDisbursedAmountTillGivenPeriod(first.getLastInterestPeriod()))
|
||||
Money total = Money
|
||||
.of(currency, first.calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(first.getLastInterestPeriod()))
|
||||
.plus(sumOfInterest).minus(alreadyRepaidPrincipals);
|
||||
|
||||
Money periodEmi = total.dividedBy(repaymentPeriods.size(), mc);
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
BigDecimal baseAmount = switch (method) {
|
||||
case FLAT -> MathUtil.nullToZero(getRepaymentPeriod().calculateTotalDisbursedAmountTillGivenPeriod(this));
|
||||
case FLAT -> MathUtil.nullToZero(getRepaymentPeriod().calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(this));
|
||||
case DECLINING_BALANCE -> getOutstandingLoanBalance().getAmount();
|
||||
default -> throw new UnsupportedOperationException("Method not implemented: " + method);
|
||||
};
|
||||
|
||||
+14
-5
@@ -84,6 +84,10 @@ public final class RepaymentPeriod {
|
||||
@Getter
|
||||
private BigDecimal totalDisbursedAmount;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private BigDecimal totalCapitalizedIncomeAmount;
|
||||
|
||||
@JsonExclude
|
||||
@Getter
|
||||
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail;
|
||||
@@ -389,16 +393,21 @@ public final class RepaymentPeriod {
|
||||
/**
|
||||
* @param tillPeriod
|
||||
* can be null. if null it calculates total disbursement including last interest period.
|
||||
* @return disbursed amount til interest period.
|
||||
* @return disbursed and capitalized income amount till interest period.
|
||||
*/
|
||||
public BigDecimal calculateTotalDisbursedAmountTillGivenPeriod(InterestPeriod tillPeriod) {
|
||||
BigDecimal res = MathUtil.nullToZero(getTotalDisbursedAmount());
|
||||
public BigDecimal calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(InterestPeriod tillPeriod) {
|
||||
BigDecimal res = MathUtil.nullToZero(getTotalDisbursedAmount()).add(MathUtil.nullToZero(getTotalCapitalizedIncomeAmount()));
|
||||
for (InterestPeriod interestPeriod : this.interestPeriods) {
|
||||
if (interestPeriod.equals(tillPeriod)) {
|
||||
break;
|
||||
}
|
||||
if (!interestPeriod.getDueDate().equals(getFromDate()) && interestPeriod.getDisbursementAmount() != null) {
|
||||
res = res.add(interestPeriod.getDisbursementAmount().getAmount());
|
||||
if (!interestPeriod.getDueDate().equals(getFromDate())) {
|
||||
if (interestPeriod.getDisbursementAmount() != null) {
|
||||
res = res.add(interestPeriod.getDisbursementAmount().getAmount());
|
||||
}
|
||||
if (interestPeriod.getCapitalizedIncomePrincipal() != null) {
|
||||
res = res.add(interestPeriod.getCapitalizedIncomePrincipal().getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
||||
-1
@@ -535,7 +535,6 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
|
||||
Loan loan = mock(Loan.class);
|
||||
when(loan.getLoanRepaymentScheduleDetail()).thenReturn(loanProductRelatedDetail);
|
||||
when(loan.isInterestBearing()).thenReturn(true);
|
||||
|
||||
LoanRepaymentScheduleInstallment installment1 = spy(
|
||||
new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(200.0),
|
||||
|
||||
+6
-4
@@ -95,16 +95,18 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
||||
AdvancedPaymentScheduleTransactionProcessor advancedProcessor, Loan loan, LoanTransaction loanTransaction) {
|
||||
Optional<ProgressiveLoanInterestScheduleModel> savedModel = modelRepository.getSavedModel(loan,
|
||||
loanTransaction.getTransactionDate());
|
||||
ProgressiveLoanInterestScheduleModel model = savedModel
|
||||
.orElseGet(() -> advancedProcessor.calculateInterestScheduleModel(loan.getId(), loanTransaction.getTransactionDate()));
|
||||
|
||||
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
|
||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
||||
new ChangedTransactionDetail(), savedModel.orElse(null), getTotalRefundInterestAmount(loan));
|
||||
new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan));
|
||||
progressiveContext.getAlreadyProcessedTransactions().addAll(loanTransactionService.retrieveListOfTransactionsForReprocessing(loan));
|
||||
progressiveContext.setChargedOff(loan.isChargedOff());
|
||||
progressiveContext.setContractTerminated(loan.isContractTermination());
|
||||
ChangedTransactionDetail result = advancedProcessor.processLatestTransaction(loanTransaction, progressiveContext);
|
||||
if (savedModel.isPresent() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
||||
modelRepository.writeInterestScheduleModel(loan, savedModel.get());
|
||||
if (!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
||||
modelRepository.writeInterestScheduleModel(loan, model);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -124,7 +126,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
transactionProcessingStrategyCode);
|
||||
if (loanRepaymentScheduleTransactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedProcessor
|
||||
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
&& loanTransaction.getLoan().isInterestRecalculationEnabled()) {
|
||||
return processLatestTransactionProgressiveInterestRecalculation(advancedProcessor, loanTransaction.getLoan(), loanTransaction);
|
||||
}
|
||||
return loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, ctx);
|
||||
|
||||
+22
-4
@@ -36,8 +36,11 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -145,10 +148,25 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
||||
|
||||
@Override
|
||||
public void processLatestTransaction(final LoanTransaction loanTransaction, final Loan loan) {
|
||||
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService.processLatestTransaction(
|
||||
loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail()));
|
||||
LoanRepaymentScheduleTransactionProcessor transactionProcessor = loanTransactionProcessingService
|
||||
.getTransactionProcessor(loan.getTransactionProcessingStrategyCode());
|
||||
|
||||
TransactionCtx transactionCtx;
|
||||
if (transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor advancedProcessor) {
|
||||
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan,
|
||||
loanTransaction.getTransactionDate());
|
||||
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);
|
||||
} else {
|
||||
transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail());
|
||||
}
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService
|
||||
.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction, transactionCtx);
|
||||
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||
.map(TransactionChangeData::getNewTransaction).peek(transaction -> transaction.updateLoan(loan)).toList();
|
||||
loan.getLoanTransactions().addAll(newTransactions);
|
||||
|
||||
-109
@@ -6000,115 +6000,6 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
});
|
||||
}
|
||||
|
||||
// uc156: Avoid Loan Reschedule to modify Interest Rate from X value to Zero
|
||||
// 1. Create a Loan product
|
||||
// 2. Submit, Approve and Disburse Loan with Nominal Interest equal to 4%
|
||||
// 3. Apply a Loan repayment
|
||||
// 4. Try to create Loan Reschedule with new Interest Rate equal to zero to get the exception
|
||||
@Test
|
||||
public void uc156() {
|
||||
final String operationDate = "1 April 2025";
|
||||
AtomicLong createdLoanId = new AtomicLong();
|
||||
runAt("1 April 2025", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
PostLoanProductsRequest product = create4IProgressive().interestRatePerPeriod(4.0).numberOfRepayments(4)//
|
||||
.installmentAmountInMultiplesOf(null)//
|
||||
.multiDisburseLoan(false)//
|
||||
.disallowExpectedDisbursements(null)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.overAppliedNumber(null)//
|
||||
;//
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
PostLoansRequest applicationRequest = applyLP2ProgressiveLoanRequest(clientId, loanProductResponse.getResourceId(),
|
||||
operationDate, 1000.0, 4.0, 4, null);
|
||||
|
||||
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
createdLoanId.set(loanResponse.getLoanId());
|
||||
|
||||
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
|
||||
.approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
|
||||
.dateFormat(DATETIME_PATTERN).locale("en").transactionAmount(BigDecimal.valueOf(1000.0)));
|
||||
});
|
||||
|
||||
runAt("1 May 2025", () -> {
|
||||
executeInlineCOB(createdLoanId.get());
|
||||
|
||||
loanTransactionHelper.makeLoanRepayment(createdLoanId.get(), new PostLoansLoanIdTransactionsRequest()
|
||||
.transactionDate("1 May 2025").dateFormat("dd MMMM yyyy").locale("en").transactionAmount(250.00));
|
||||
});
|
||||
|
||||
runAt("6 May 2025", () -> {
|
||||
executeInlineCOB(createdLoanId.get());
|
||||
|
||||
CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
|
||||
() -> loanRescheduleRequestHelper.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest()
|
||||
.loanId(createdLoanId.get()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("6 May 2025")
|
||||
.newInterestRate(BigDecimal.ZERO).rescheduleReasonId(1L).rescheduleFromDate("1 June 2025")));
|
||||
|
||||
Assertions.assertTrue(
|
||||
callFailedRuntimeException.getMessage().contains("The parameter `newInterestRate` must be greater than 0."));
|
||||
});
|
||||
}
|
||||
|
||||
// uc157: Avoid Loan Reschedule to modify Interest Rate from Zero to X value
|
||||
// 1. Create a Loan product
|
||||
// 2. Submit, Approve and Disburse Loan with Nominal Interest equal to 0 (zero)
|
||||
// 3. Apply a Loan repayment
|
||||
// 4. Try to create Loan Reschedule with new Interest Rate greater than zero to get the exception
|
||||
@Test
|
||||
public void uc157() {
|
||||
final String operationDate = "1 April 2025";
|
||||
AtomicLong createdLoanId = new AtomicLong();
|
||||
runAt("1 April 2025", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
PostLoanProductsRequest product = create4IProgressive().interestRatePerPeriod(0.0).numberOfRepayments(4)//
|
||||
.installmentAmountInMultiplesOf(null)//
|
||||
.multiDisburseLoan(false)//
|
||||
.disallowExpectedDisbursements(null)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.overAppliedNumber(null)//
|
||||
;//
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
PostLoansRequest applicationRequest = applyLP2ProgressiveLoanRequest(clientId, loanProductResponse.getResourceId(),
|
||||
operationDate, 1000.0, 0.0, 4, null);
|
||||
|
||||
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
createdLoanId.set(loanResponse.getLoanId());
|
||||
|
||||
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()
|
||||
.approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en"));
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(operationDate)
|
||||
.dateFormat(DATETIME_PATTERN).locale("en").transactionAmount(BigDecimal.valueOf(1000.0)));
|
||||
});
|
||||
|
||||
runAt("1 May 2025", () -> {
|
||||
executeInlineCOB(createdLoanId.get());
|
||||
|
||||
loanTransactionHelper.makeLoanRepayment(createdLoanId.get(), new PostLoansLoanIdTransactionsRequest()
|
||||
.transactionDate("1 May 2025").dateFormat("dd MMMM yyyy").locale("en").transactionAmount(250.00));
|
||||
});
|
||||
|
||||
runAt("6 May 2025", () -> {
|
||||
executeInlineCOB(createdLoanId.get());
|
||||
|
||||
CallFailedRuntimeException callFailedRuntimeException = Assertions.assertThrows(CallFailedRuntimeException.class,
|
||||
() -> loanRescheduleRequestHelper.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest()
|
||||
.loanId(createdLoanId.get()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("6 May 2025")
|
||||
.newInterestRate(BigDecimal.valueOf(4.0)).rescheduleReasonId(1L).rescheduleFromDate("1 June 2025")));
|
||||
|
||||
LOG.info("ERROR: {}", callFailedRuntimeException.getMessage());
|
||||
Assertions.assertTrue(
|
||||
callFailedRuntimeException.getMessage().contains("Loan rescheduling is not allowed from interest rate 0 (zero)"));
|
||||
});
|
||||
}
|
||||
|
||||
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||
|
||||
Reference in New Issue
Block a user