This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 3456e1eb8 FINERACT-2114: Reschedule validations enhancement
3456e1eb8 is described below
commit 3456e1eb81134f28f0649ab0e3b136422da08487
Author: Adam Saghy <[email protected]>
AuthorDate: Wed Aug 21 19:57:14 2024 +0200
FINERACT-2114: Reschedule validations enhancement
---
.../test/stepdef/loan/LoanRescheduleStepDef.java | 16 +-
.../resources/features/LoanDownPayment.feature | 8 +-
.../test/resources/features/LoanReschedule.feature | 38 +-
.../data/LoanRescheduleRequestDataValidator.java | 379 +------------------
...=> LoanRescheduleRequestDataValidatorImpl.java} | 403 ++++++++++-----------
.../domain/LoanRescheduleRequest.java | 17 +-
...gressiveLoanRescheduleRequestDataValidator.java | 188 ++++++++++
...nRescheduleRequestWritePlatformServiceImpl.java | 4 +-
.../integrationtests/BaseLoanIntegrationTest.java | 2 +-
...ChargeOffWithAdvancedPaymentAllocationTest.java | 29 --
.../LoanRescheduleRequestTest.java | 162 ++++++++-
.../LoanTransactionAccrualActivityPostingTest.java | 4 +-
.../common/LoanRescheduleRequestHelper.java | 4 +
13 files changed, 590 insertions(+), 664 deletions(-)
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
index a71454903..8ae467cf9 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
@@ -62,10 +62,10 @@ public class LoanRescheduleStepDef extends AbstractStepDef {
String rescheduleFromDate = rescheduleData.get(0);
String submittedOnDate = rescheduleData.get(1);
String adjustedDueDate = rescheduleData.get(2);
- Integer graceOfPrincipal = Integer.parseInt(rescheduleData.get(3)) ==
0 ? null : Integer.valueOf(rescheduleData.get(3));
- Integer graceOnInterest = Integer.parseInt(rescheduleData.get(4)) == 0
? null : Integer.valueOf(rescheduleData.get(4));
- Integer extraTerms = Integer.parseInt(rescheduleData.get(5)) == 0 ?
null : Integer.valueOf(rescheduleData.get(5));
- BigDecimal newInterestRate = new BigDecimal(rescheduleData.get(6));
+ Integer graceOfPrincipal = rescheduleData.get(3) != null ?
Integer.parseInt(rescheduleData.get(3)) : null;
+ Integer graceOnInterest = rescheduleData.get(4) != null ?
Integer.parseInt(rescheduleData.get(4)) : null;
+ Integer extraTerms = rescheduleData.get(5) != null ?
Integer.parseInt(rescheduleData.get(5)) : null;
+ BigDecimal newInterestRate = rescheduleData.get(6) != null ? new
BigDecimal(rescheduleData.get(6)) : null;
PostCreateRescheduleLoansRequest request = new
PostCreateRescheduleLoansRequest()//
.loanId(loanId)//
@@ -105,10 +105,10 @@ public class LoanRescheduleStepDef extends
AbstractStepDef {
String rescheduleFromDate = rescheduleData.get(0);
String submittedOnDate = rescheduleData.get(1);
String adjustedDueDate = rescheduleData.get(2);
- Integer graceOfPrincipal = Integer.parseInt(rescheduleData.get(3)) ==
0 ? null : Integer.valueOf(rescheduleData.get(3));
- Integer graceOnInterest = Integer.parseInt(rescheduleData.get(4)) == 0
? null : Integer.valueOf(rescheduleData.get(4));
- Integer extraTerms = Integer.parseInt(rescheduleData.get(5)) == 0 ?
null : Integer.valueOf(rescheduleData.get(5));
- BigDecimal newInterestRate = new BigDecimal(rescheduleData.get(6));
+ Integer graceOfPrincipal = rescheduleData.get(3) != null ?
Integer.parseInt(rescheduleData.get(3)) : null;
+ Integer graceOnInterest = rescheduleData.get(4) != null ?
Integer.parseInt(rescheduleData.get(4)) : null;
+ Integer extraTerms = rescheduleData.get(5) != null ?
Integer.parseInt(rescheduleData.get(5)) : null;
+ BigDecimal newInterestRate = rescheduleData.get(6) != null ? new
BigDecimal(rescheduleData.get(6)) : null;
PostCreateRescheduleLoansRequest request = new
PostCreateRescheduleLoansRequest()//
.loanId(loanId)//
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
index a2f316a2b..1c62109de 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanDownPayment.feature
@@ -434,7 +434,7 @@ Feature: Loan DownPayment
When Admin sets the business date to "10 January 2022"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 01 February 2022 | 10 January 2022 | 01 April 2022 | 0
| 0 | 0 | 0 |
+ | 01 February 2022 | 10 January 2022 | 01 April 2022 |
| | | |
When Admin sets the business date to "01 April 2022"
When Admin successfully disburse the loan on "01 April 2022" with "400"
EUR transaction amount
Then Loan Repayment schedule has 5 periods, with the following data for
periods:
@@ -490,7 +490,7 @@ Feature: Loan DownPayment
When Admin sets the business date to "10 January 2022"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 01 February 2022 | 10 January 2022 | 01 April 2022 | 0
| 0 | 0 | 0 |
+ | 01 February 2022 | 10 January 2022 | 01 April 2022 |
| | | |
Then Loan Repayment schedule has 4 periods, with the following data for
periods:
| Nr | Days | Date | Paid date | Balance of loan |
Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late
| Outstanding |
| | | 01 January 2022 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -544,7 +544,7 @@ Feature: Loan DownPayment
| 1000.0 | 0 | 0 | 50 | 1050.0 | 250.0 | 0
| 0 | 800 |
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 10 February 2022 | 10 January 2022 | 10 April 2022 | 0
| 0 | 0 | 0 |
+ | 10 February 2022 | 10 January 2022 | 10 April 2022 |
| | | |
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 |
| | | 10 January 2022 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -610,7 +610,7 @@ Feature: Loan DownPayment
| 1000.0 | 0 | 0 | 50 | 1050.0 | 250.0 | 0
| 0 | 800 |
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 10 February 2022 | 10 January 2022 | 10 April 2022 | 0
| 0 | 0 | 0 |
+ | 10 February 2022 | 10 January 2022 | 10 April 2022 |
| | | |
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 |
| | | 10 January 2022 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
diff --git
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
index 0dd01cd35..28d7e9e6a 100644
---
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
+++
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReschedule.feature
@@ -114,7 +114,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 July 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 31 July 2023 | 05 July 2023 | 31 August 2023 | 0
| 0 | 0 | 0 |
+ | 31 July 2023 | 05 July 2023 | 31 August 2023 |
| | | |
Then Loan Repayment schedule has 1 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 July 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -144,7 +144,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 July 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 01 September 2023 | 05 July 2023 | 15 September 2023 | 0
| 0 | 0 | 0 |
+ | 01 September 2023 | 05 July 2023 | 15 September 2023 |
| | | |
Then Loan Repayment schedule has 3 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 July 2023 | | 3000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -176,7 +176,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 July 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 01 September 2023 | 05 July 2023 | | 0
| 0 | 2 | 0 |
+ | 01 September 2023 | 05 July 2023 | |
| | 2 | |
Then Loan Repayment schedule has 5 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 July 2023 | | 3000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -210,7 +210,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 July 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 01 September 2023 | 05 July 2023 | | 1
| 0 | 0 | 0 |
+ | 01 September 2023 | 05 July 2023 | | 1
| | | |
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 July 2023 | | 3000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -244,7 +244,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 July 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 01 September 2023 | 05 July 2023 | | 0
| 2 | 0 | 0 |
+ | 01 September 2023 | 05 July 2023 | |
| 2 | | |
# Then Loan Repayment schedule has 3 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 July 2023 | | 3000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -276,7 +276,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 July 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 01 September 2023 | 05 July 2023 | | 0
| 0 | 0 | 6 |
+ | 01 September 2023 | 05 July 2023 | |
| | | 6 |
Then Loan Repayment schedule has 3 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 July 2023 | | 3000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -298,7 +298,7 @@ Feature: LoanReschedule
When Admin successfully disburse the loan on "01 October 2023" with "1000"
EUR transaction amount
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 01 October 2023 | | 0
| 0 | 2 | 0 |
+ | 16 October 2023 | 01 October 2023 | |
| | 2 | |
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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -326,7 +326,7 @@ Feature: LoanReschedule
When Admin sets the business date to "11 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 01 October 2023 | | 0
| 0 | 2 | 0 |
+ | 16 October 2023 | 01 October 2023 | |
| | 2 | |
Then Loan Repayment schedule has 7 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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -356,7 +356,7 @@ Feature: LoanReschedule
When Admin sets the business date to "11 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 01 October 2023 | | 0
| 0 | 2 | 0 |
+ | 16 October 2023 | 01 October 2023 | |
| | 2 | |
When Admin sets the business date to "20 October 2023"
When Admin successfully disburse the loan on "20 October 2023" with "400"
EUR transaction amount
Then Loan Repayment schedule has 7 periods, with the following data for
periods:
@@ -386,7 +386,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 31 October 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 05 October 2023 | 31 October 2023 |
| | | |
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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -410,7 +410,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 15 November 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 05 October 2023 | 15 November 2023 |
| | | |
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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -434,7 +434,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 15 November 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 05 October 2023 | 15 November 2023 |
| | | |
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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -464,7 +464,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 31 October 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 05 October 2023 | 31 October 2023 |
| | | |
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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -492,7 +492,7 @@ Feature: LoanReschedule
When Admin successfully disburse the loan on "01 October 2023" with "1000"
EUR transaction amount
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 01 October 2023 | 31 October 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 01 October 2023 | 31 October 2023 |
| | | |
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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -518,7 +518,7 @@ Feature: LoanReschedule
When Admin sets the business date to "11 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 01 October 2023 | 31 October 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 01 October 2023 | 31 October 2023 |
| | | |
Then Loan Repayment schedule has 5 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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -544,7 +544,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 31 October 2023 | 0
| 0 | 2 | 0 |
+ | 16 October 2023 | 05 October 2023 | 31 October 2023 |
| | 2 | |
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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -570,7 +570,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 31 October 2023 | 1
| 0 | 0 | 0 |
+ | 16 October 2023 | 05 October 2023 | 31 October 2023 | 1
| | | |
Then Loan Repayment schedule has 5 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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
@@ -597,7 +597,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
Then Loan reschedule with the following data results a 403 error and
"LOAN_CHARGED_OFF" error message
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 31 October 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 05 October 2023 | 31 October 2023 |
| | | |
Scenario: Verify that reschedule keeps the N+1 installment
@@ -623,7 +623,7 @@ Feature: LoanReschedule
When Admin sets the business date to "05 October 2023"
When Admin creates and approves Loan reschedule with the following data:
| rescheduleFromDate | submittedOnDate | adjustedDueDate |
graceOnPrincipal | graceOnInterest | extraTerms | newInterestRate |
- | 16 October 2023 | 05 October 2023 | 31 October 2023 | 0
| 0 | 0 | 0 |
+ | 16 October 2023 | 05 October 2023 | 31 October 2023 |
| | | |
Then Loan Repayment schedule has 5 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 October 2023 | | 1000.0 |
| | 0.0 | | 0.0 | 0.0 | | |
|
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
index be174c450..c41fb4da6 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
@@ -18,386 +18,15 @@
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
-import com.google.gson.JsonElement;
-import com.google.gson.reflect.TypeToken;
-import java.lang.reflect.Type;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.ApiParameterError;
-import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
-import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
-import
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
-import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
-import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
-import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
-import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
-import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
-import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
-import org.springframework.stereotype.Component;
-@Component
-@AllArgsConstructor
-public class LoanRescheduleRequestDataValidator {
+public interface LoanRescheduleRequestDataValidator {
- private final FromJsonHelper fromJsonHelper;
- private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new
HashSet<>(
- Arrays.asList(RescheduleLoansApiConstants.localeParamName,
RescheduleLoansApiConstants.dateFormatParamName,
- RescheduleLoansApiConstants.graceOnPrincipalParamName,
-
RescheduleLoansApiConstants.recurringMoratoriumOnPrincipalPeriodsParamName,
- RescheduleLoansApiConstants.graceOnInterestParamName,
RescheduleLoansApiConstants.extraTermsParamName,
- RescheduleLoansApiConstants.rescheduleFromDateParamName,
RescheduleLoansApiConstants.newInterestRateParamName,
- RescheduleLoansApiConstants.rescheduleReasonIdParamName,
RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
- RescheduleLoansApiConstants.submittedOnDateParamName,
RescheduleLoansApiConstants.loanIdParamName,
- RescheduleLoansApiConstants.adjustedDueDateParamName,
RescheduleLoansApiConstants.recalculateInterestParamName,
- RescheduleLoansApiConstants.endDateParamName,
RescheduleLoansApiConstants.emiParamName));
+ void validateForCreateAction(JsonCommand jsonCommand, Loan loan);
- private static final Set<String> REJECT_REQUEST_DATA_PARAMETERS = new
HashSet<>(
- Arrays.asList(RescheduleLoansApiConstants.localeParamName,
RescheduleLoansApiConstants.dateFormatParamName,
- RescheduleLoansApiConstants.rejectedOnDateParam));
+ void validateForApproveAction(JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest);
- private static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new
HashSet<>(
- Arrays.asList(RescheduleLoansApiConstants.localeParamName,
RescheduleLoansApiConstants.dateFormatParamName,
- RescheduleLoansApiConstants.approvedOnDateParam));
-
- /**
- * Validates the request to create a new loan reschedule entry
- *
- * @param jsonCommand
- * the JSON command object (instance of the JsonCommand class)
- *
- **/
- public void validateForCreateAction(final JsonCommand jsonCommand, final
Loan loan) {
-
- final String jsonString = jsonCommand.json();
-
- if (StringUtils.isBlank(jsonString)) {
- throw new InvalidJsonException();
- }
-
- final Type typeToken = new TypeToken<Map<String, Object>>()
{}.getType();
- this.fromJsonHelper.checkForUnsupportedParameters(typeToken,
jsonString, CREATE_REQUEST_DATA_PARAMETERS);
-
- final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
- final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
-
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
-
- final JsonElement jsonElement = jsonCommand.parsedJson();
-
- if (!loan.getStatus().isActive()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
"Loan is not active");
- }
-
- final Long loanId =
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.loanIdParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanIdParamName).value(loanId).notNull()
- .integerGreaterThanZero();
-
- final LocalDate submittedOnDate =
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName,
- jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
-
- if (submittedOnDate != null &&
DateUtils.isAfter(loan.getDisbursementDate(), submittedOnDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
- .failWithCode("before.loan.disbursement.date", "Submission
date cannot be before the loan disbursement date");
- }
-
- final LocalDate rescheduleFromDate = this.fromJsonHelper
-
.extractLocalDateNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull();
-
- final Integer graceOnPrincipal = this.fromJsonHelper
-
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal).ignoreIfNull()
- .integerGreaterThanZero();
-
- final Integer graceOnInterest = this.fromJsonHelper
-
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnInterestParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull()
- .integerGreaterThanZero();
-
- final Integer extraTerms =
this.fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName,
- jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraTermsParamName).value(extraTerms).ignoreIfNull()
- .integerGreaterThanZero();
-
- final Long rescheduleReasonId =
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName,
- jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull()
- .integerGreaterThanZero();
-
- final String rescheduleReasonComment = this.fromJsonHelper
-
.extractStringNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
- .ignoreIfNull().notExceedingLengthOf(500);
-
- final LocalDate adjustedDueDate =
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName,
- jsonElement);
-
- if (adjustedDueDate != null && DateUtils.isBefore(adjustedDueDate,
rescheduleFromDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
- "adjustedDueDate.before.rescheduleFromDate", "Adjusted due
date cannot be before the reschedule from date");
- }
-
- if
(loan.getLoanProduct().getLoanProductRelatedDetail().getLoanScheduleType() ==
LoanScheduleType.CUMULATIVE) {
- final LocalDate endDate =
jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.endDateParamName);
- final BigDecimal emi =
jsonCommand.bigDecimalValueOfParameterNamed(RescheduleLoansApiConstants.emiParamName);
- validateForCumulativeLoan(dataValidatorBuilder, loan, jsonElement,
rescheduleFromDate, endDate, emi);
- } else {
- validateForProgressiveLoan(dataValidatorBuilder, loan,
jsonElement, rescheduleFromDate, adjustedDueDate);
- }
-
- if (!dataValidationErrors.isEmpty()) {
- throw new PlatformApiDataValidationException(dataValidationErrors);
- }
- }
-
- private void validateForProgressiveLoan(final DataValidatorBuilder
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
- final LocalDate rescheduleFromDate, final LocalDate
adjustedDueDate) {
- final var unsupportedFields =
List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, //
- RescheduleLoansApiConstants.graceOnInterestParamName, //
- RescheduleLoansApiConstants.extraTermsParamName, //
- RescheduleLoansApiConstants.emiParamName//
- );
-
- for (var unsupportedField : unsupportedFields) {
- if (this.fromJsonHelper.parameterHasValue(unsupportedField,
jsonElement)) {
-
dataValidatorBuilder.reset().parameter(unsupportedField).failWithCode(
-
RescheduleLoansApiConstants.rescheduleSelectedOperationNotSupportedErrorCode,
- "Selected operation is not supported by Progressive
Loan at a time during Loan Rescheduling");
- return;
- }
- }
-
- final LocalDate businessDate = DateUtils.getBusinessLocalDate();
- LoanRepaymentScheduleInstallment installment = null;
- if (rescheduleFromDate != null) {
- boolean hasInterestRateChange =
this.fromJsonHelper.parameterHasValue(RescheduleLoansApiConstants.newInterestRateParamName,
- jsonElement);
- if (hasInterestRateChange && adjustedDueDate != null) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode(
-
RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode,
- "Only one operation is supported at a time during Loan
Rescheduling");
- return;
- }
-
- if (hasInterestRateChange &&
!rescheduleFromDate.isAfter(businessDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
-
"loan.reschedule.interestratechange.reschedulefrom.shouldbefuture",
- "Loan Reschedule From date should be in the future.");
- }
-
- if (adjustedDueDate != null) {
- installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
- } else if (hasInterestRateChange) {
- installment =
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
- }
-
- validateReschedulingInstallment(dataValidatorBuilder, installment);
- }
-
- validateForOverdueCharges(dataValidatorBuilder, loan, installment);
- }
-
- private void validateForCumulativeLoan(final DataValidatorBuilder
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
- final LocalDate rescheduleFromDate, final LocalDate endDate, final
BigDecimal emi) {
- if (emi != null || endDate != null) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName).value(endDate).notNull();
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount();
-
- if (endDate != null) {
- LoanRepaymentScheduleInstallment endInstallment =
loan.getRepaymentScheduleInstallment(endDate);
-
- if (endInstallment == null) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName)
-
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment
schedule installment does not exist");
- }
- }
- }
-
- // at least one of the following must be provided => graceOnPrincipal,
- // graceOnInterest, extraTerms, newInterestRate
- if
(!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName,
jsonElement)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
- }
-
- final LoanRepaymentScheduleInstallment installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
- if (rescheduleFromDate != null) {
- validateReschedulingInstallment(dataValidatorBuilder, installment);
- }
-
- if (loan.isMultiDisburmentLoan()) {
- if (!loan.loanProduct().isDisallowExpectedDisbursements()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-
RescheduleLoansApiConstants.rescheduleForMultiDisbursementNotSupportedErrorCode,
- "Loan rescheduling is not supported for
multidisbursement tranche loans");
- }
- }
-
- validateForOverdueCharges(dataValidatorBuilder, loan, installment);
- }
-
- private static void validateReschedulingInstallment(DataValidatorBuilder
dataValidatorBuilder,
- LoanRepaymentScheduleInstallment installment) {
- if (installment == null) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
-
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment
schedule installment does not exist");
- }
-
- if (installment != null && installment.isObligationsMet()) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
-
.failWithCode("repayment.schedule.installment.obligation.met", "Repayment
schedule installment obligation met");
- }
- }
-
- private void validateForOverdueCharges(final DataValidatorBuilder
dataValidatorBuilder, final Loan loan,
- final LoanRepaymentScheduleInstallment installment) {
- if (installment != null) {
- LocalDate rescheduleFromDate = installment.getFromDate();
- Collection<LoanCharge> charges = loan.getLoanCharges();
- for (LoanCharge loanCharge : charges) {
- if (loanCharge.isOverdueInstallmentCharge() &&
DateUtils.isAfter(loanCharge.getDueLocalDate(), rescheduleFromDate)) {
-
dataValidatorBuilder.failWithCodeNoParameterAddedToErrorCode("not.allowed.due.to.overdue.charges");
- break;
- }
- }
- }
- }
-
- /**
- * Validates a user request to approve a loan reschedule request
- *
- * @param jsonCommand
- * the JSON command object (instance of the JsonCommand class)
- *
- **/
- public void validateForApproveAction(final JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest) {
- final String jsonString = jsonCommand.json();
-
- if (StringUtils.isBlank(jsonString)) {
- throw new InvalidJsonException();
- }
-
- final Type typeToken = new TypeToken<Map<String, Object>>()
{}.getType();
- this.fromJsonHelper.checkForUnsupportedParameters(typeToken,
jsonString, APPROVE_REQUEST_DATA_PARAMETERS);
-
- final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
- final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
-
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
-
- final JsonElement jsonElement = jsonCommand.parsedJson();
-
- final LocalDate approvedOnDate =
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam,
- jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull();
-
- if (approvedOnDate != null &&
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), approvedOnDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).failWithCode("before.submission.date",
- "Approval date cannot be before the request submission
date.");
- }
-
- LoanRescheduleRequestStatusEnumData
loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
- .status(loanRescheduleRequest.getStatusEnum());
-
- if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("request.is.not.in.submitted.and.pending.state",
- "Loan reschedule request approval is not allowed. "
- + "Loan reschedule request is not in submitted and
pending approval state.");
- }
-
- LocalDate rescheduleFromDate =
loanRescheduleRequest.getRescheduleFromDate();
- final Loan loan = loanRescheduleRequest.getLoan();
- LoanRepaymentScheduleInstallment installment = null;
- if (loan != null) {
-
- if (!loan.getStatus().isActive()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
"Loan is not active");
- }
-
- if (rescheduleFromDate != null) {
- final boolean isProgressiveLoanSchedule =
loan.getLoanProductRelatedDetail()
- .getLoanScheduleType() == LoanScheduleType.PROGRESSIVE;
- installment = isProgressiveLoanSchedule ?
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate)
- :
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
- if (installment == null) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-
"loan.repayment.schedule.installment.does.not.exist", "Repayment schedule
installment does not exist");
- }
-
- if (installment != null && installment.isObligationsMet()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
- "loan.repayment.schedule.installment." +
"obligation.met", "Repayment schedule installment obligation met");
- }
- }
- }
-
- validateForOverdueCharges(dataValidatorBuilder, loan, installment);
-
- if (!dataValidationErrors.isEmpty()) {
- throw new PlatformApiDataValidationException(dataValidationErrors);
- }
- }
-
- /**
- * Validates a user request to reject a loan reschedule request
- *
- * @param jsonCommand
- * the JSON command object (instance of the JsonCommand class)
- *
- **/
- public void validateForRejectAction(final JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest) {
- final String jsonString = jsonCommand.json();
-
- if (StringUtils.isBlank(jsonString)) {
- throw new InvalidJsonException();
- }
-
- final Type typeToken = new TypeToken<Map<String, Object>>()
{}.getType();
- this.fromJsonHelper.checkForUnsupportedParameters(typeToken,
jsonString, REJECT_REQUEST_DATA_PARAMETERS);
-
- final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
- final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
-
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
-
- final JsonElement jsonElement = jsonCommand.parsedJson();
-
- final LocalDate rejectedOnDate =
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.rejectedOnDateParam,
- jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).value(rejectedOnDate).notNull();
-
- if (rejectedOnDate != null &&
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), rejectedOnDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).failWithCode("before.submission.date",
- "Rejection date cannot be before the request submission
date.");
- }
-
- LoanRescheduleRequestStatusEnumData
loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
- .status(loanRescheduleRequest.getStatusEnum());
-
- if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("request.is.not.in.submitted.and.pending.state",
- "Loan reschedule request rejection is not allowed. "
- + "Loan reschedule request is not in submitted and
pending approval state.");
- }
-
- if (!dataValidationErrors.isEmpty()) {
- throw new PlatformApiDataValidationException(dataValidationErrors);
- }
- }
+ void validateForRejectAction(JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest);
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java
similarity index 57%
copy from
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
copy to
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java
index be174c450..4d22bde43 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidatorImpl.java
@@ -45,14 +45,14 @@ import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
-@Component
+@Component("loanRescheduleRequestDataValidator")
@AllArgsConstructor
-public class LoanRescheduleRequestDataValidator {
+public class LoanRescheduleRequestDataValidatorImpl implements
LoanRescheduleRequestDataValidator {
- private final FromJsonHelper fromJsonHelper;
- private static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new
HashSet<>(
+ public static final Set<String> CREATE_REQUEST_DATA_PARAMETERS = new
HashSet<>(
Arrays.asList(RescheduleLoansApiConstants.localeParamName,
RescheduleLoansApiConstants.dateFormatParamName,
RescheduleLoansApiConstants.graceOnPrincipalParamName,
RescheduleLoansApiConstants.recurringMoratoriumOnPrincipalPeriodsParamName,
@@ -62,198 +62,144 @@ public class LoanRescheduleRequestDataValidator {
RescheduleLoansApiConstants.submittedOnDateParamName,
RescheduleLoansApiConstants.loanIdParamName,
RescheduleLoansApiConstants.adjustedDueDateParamName,
RescheduleLoansApiConstants.recalculateInterestParamName,
RescheduleLoansApiConstants.endDateParamName,
RescheduleLoansApiConstants.emiParamName));
-
private static final Set<String> REJECT_REQUEST_DATA_PARAMETERS = new
HashSet<>(
Arrays.asList(RescheduleLoansApiConstants.localeParamName,
RescheduleLoansApiConstants.dateFormatParamName,
RescheduleLoansApiConstants.rejectedOnDateParam));
-
- private static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new
HashSet<>(
+ public static final Set<String> APPROVE_REQUEST_DATA_PARAMETERS = new
HashSet<>(
Arrays.asList(RescheduleLoansApiConstants.localeParamName,
RescheduleLoansApiConstants.dateFormatParamName,
RescheduleLoansApiConstants.approvedOnDateParam));
+ private final FromJsonHelper fromJsonHelper;
+ @Qualifier("progressiveLoanRescheduleRequestDataValidatorImpl")
+ private final LoanRescheduleRequestDataValidator
progressiveLoanRescheduleRequestDataValidatorDelegate;
+
+ public static BigDecimal validateInterestRate(FromJsonHelper
fromJsonHelper, JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final BigDecimal interestRate = fromJsonHelper
+
.extractBigDecimalWithLocaleNamed(RescheduleLoansApiConstants.newInterestRateParamName,
jsonElement);
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.newInterestRateParamName).value(interestRate).ignoreIfNull()
+ .positiveAmount();
+ return interestRate;
+ }
- /**
- * Validates the request to create a new loan reschedule entry
- *
- * @param jsonCommand
- * the JSON command object (instance of the JsonCommand class)
- *
- **/
- public void validateForCreateAction(final JsonCommand jsonCommand, final
Loan loan) {
-
- final String jsonString = jsonCommand.json();
-
- if (StringUtils.isBlank(jsonString)) {
- throw new InvalidJsonException();
+ private static void validateMultiDisburseLoan(Loan loan,
DataValidatorBuilder dataValidatorBuilder) {
+ if (loan.isMultiDisburmentLoan()) {
+ if (!loan.loanProduct().isDisallowExpectedDisbursements()) {
+
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
+
RescheduleLoansApiConstants.rescheduleForMultiDisbursementNotSupportedErrorCode,
+ "Loan rescheduling is not supported for
multidisbursement tranche loans");
+ }
}
+ }
- final Type typeToken = new TypeToken<Map<String, Object>>()
{}.getType();
- this.fromJsonHelper.checkForUnsupportedParameters(typeToken,
jsonString, CREATE_REQUEST_DATA_PARAMETERS);
-
- final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
- final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
-
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+ public static void validateEMIAndEndDate(FromJsonHelper fromJsonHelper,
Loan loan, JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final LocalDate endDate =
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.endDateParamName,
jsonElement);
+ final BigDecimal emi =
fromJsonHelper.extractBigDecimalWithLocaleNamed(RescheduleLoansApiConstants.emiParamName,
jsonElement);
+ if (emi != null || endDate != null) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName).value(endDate).notNull();
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount();
- final JsonElement jsonElement = jsonCommand.parsedJson();
+ if (endDate != null) {
+ LoanRepaymentScheduleInstallment endInstallment =
loan.getRepaymentScheduleInstallment(endDate);
- if (!loan.getStatus().isActive()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
"Loan is not active");
+ if (endInstallment == null) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName)
+
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment
schedule installment does not exist");
+ }
+ }
}
+ }
- final Long loanId =
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.loanIdParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanIdParamName).value(loanId).notNull()
- .integerGreaterThanZero();
-
- final LocalDate submittedOnDate =
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName,
+ public static LocalDate validateAndRetrieveAdjustedDate(FromJsonHelper
fromJsonHelper, JsonElement jsonElement,
+ LocalDate rescheduleFromDate, DataValidatorBuilder
dataValidatorBuilder) {
+ final LocalDate adjustedDueDate =
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
- if (submittedOnDate != null &&
DateUtils.isAfter(loan.getDisbursementDate(), submittedOnDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
- .failWithCode("before.loan.disbursement.date", "Submission
date cannot be before the loan disbursement date");
+ if (adjustedDueDate != null && DateUtils.isBefore(adjustedDueDate,
rescheduleFromDate)) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
+ "adjustedDueDate.before.rescheduleFromDate", "Adjusted due
date cannot be before the reschedule from date");
}
+ return adjustedDueDate;
+ }
- final LocalDate rescheduleFromDate = this.fromJsonHelper
-
.extractLocalDateNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull();
-
- final Integer graceOnPrincipal = this.fromJsonHelper
-
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal).ignoreIfNull()
- .integerGreaterThanZero();
+ public static void validateRescheduleReasonComment(FromJsonHelper
fromJsonHelper, JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final String rescheduleReasonComment = fromJsonHelper
+
.extractStringNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
jsonElement);
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
+ .ignoreIfNull().notExceedingLengthOf(500);
+ }
- final Integer graceOnInterest = this.fromJsonHelper
-
.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnInterestParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull()
+ public static void validateRescheduleReasonId(FromJsonHelper
fromJsonHelper, JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final Long rescheduleReasonId =
fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName,
+ jsonElement);
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull()
.integerGreaterThanZero();
+ }
- final Integer extraTerms =
this.fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName,
+ public static void validateExtraTerms(FromJsonHelper fromJsonHelper,
JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final Integer extraTerms =
fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName,
jsonElement);
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraTermsParamName).value(extraTerms).ignoreIfNull()
.integerGreaterThanZero();
+ }
- final Long rescheduleReasonId =
this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName,
+ public static void validateGraceOnInterest(FromJsonHelper fromJsonHelper,
JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final Integer graceOnInterest =
fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnInterestParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull()
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull()
.integerGreaterThanZero();
+ }
- final String rescheduleReasonComment = this.fromJsonHelper
-
.extractStringNamed(RescheduleLoansApiConstants.rescheduleReasonCommentParamName,
jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
- .ignoreIfNull().notExceedingLengthOf(500);
-
- final LocalDate adjustedDueDate =
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName,
+ public static void validateGraceOnPrincipal(FromJsonHelper fromJsonHelper,
JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final Integer graceOnPrincipal =
fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.graceOnPrincipalParamName,
jsonElement);
-
- if (adjustedDueDate != null && DateUtils.isBefore(adjustedDueDate,
rescheduleFromDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
- "adjustedDueDate.before.rescheduleFromDate", "Adjusted due
date cannot be before the reschedule from date");
- }
-
- if
(loan.getLoanProduct().getLoanProductRelatedDetail().getLoanScheduleType() ==
LoanScheduleType.CUMULATIVE) {
- final LocalDate endDate =
jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.endDateParamName);
- final BigDecimal emi =
jsonCommand.bigDecimalValueOfParameterNamed(RescheduleLoansApiConstants.emiParamName);
- validateForCumulativeLoan(dataValidatorBuilder, loan, jsonElement,
rescheduleFromDate, endDate, emi);
- } else {
- validateForProgressiveLoan(dataValidatorBuilder, loan,
jsonElement, rescheduleFromDate, adjustedDueDate);
- }
-
- if (!dataValidationErrors.isEmpty()) {
- throw new PlatformApiDataValidationException(dataValidationErrors);
- }
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal).ignoreIfNull()
+ .integerGreaterThanZero();
}
- private void validateForProgressiveLoan(final DataValidatorBuilder
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
- final LocalDate rescheduleFromDate, final LocalDate
adjustedDueDate) {
- final var unsupportedFields =
List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, //
- RescheduleLoansApiConstants.graceOnInterestParamName, //
- RescheduleLoansApiConstants.extraTermsParamName, //
- RescheduleLoansApiConstants.emiParamName//
- );
-
- for (var unsupportedField : unsupportedFields) {
- if (this.fromJsonHelper.parameterHasValue(unsupportedField,
jsonElement)) {
-
dataValidatorBuilder.reset().parameter(unsupportedField).failWithCode(
-
RescheduleLoansApiConstants.rescheduleSelectedOperationNotSupportedErrorCode,
- "Selected operation is not supported by Progressive
Loan at a time during Loan Rescheduling");
- return;
- }
- }
-
- final LocalDate businessDate = DateUtils.getBusinessLocalDate();
- LoanRepaymentScheduleInstallment installment = null;
- if (rescheduleFromDate != null) {
- boolean hasInterestRateChange =
this.fromJsonHelper.parameterHasValue(RescheduleLoansApiConstants.newInterestRateParamName,
- jsonElement);
- if (hasInterestRateChange && adjustedDueDate != null) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode(
-
RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode,
- "Only one operation is supported at a time during Loan
Rescheduling");
- return;
- }
-
- if (hasInterestRateChange &&
!rescheduleFromDate.isAfter(businessDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
-
"loan.reschedule.interestratechange.reschedulefrom.shouldbefuture",
- "Loan Reschedule From date should be in the future.");
- }
-
- if (adjustedDueDate != null) {
- installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
- } else if (hasInterestRateChange) {
- installment =
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
- }
-
- validateReschedulingInstallment(dataValidatorBuilder, installment);
- }
-
- validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+ public static LocalDate
validateAndRetrieveRescheduleFromDate(FromJsonHelper fromJsonHelper,
JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final LocalDate rescheduleFromDate =
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName,
+ jsonElement);
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull();
+ return rescheduleFromDate;
}
- private void validateForCumulativeLoan(final DataValidatorBuilder
dataValidatorBuilder, final Loan loan, final JsonElement jsonElement,
- final LocalDate rescheduleFromDate, final LocalDate endDate, final
BigDecimal emi) {
- if (emi != null || endDate != null) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName).value(endDate).notNull();
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.emiParamName).value(emi).notNull().positiveAmount();
-
- if (endDate != null) {
- LoanRepaymentScheduleInstallment endInstallment =
loan.getRepaymentScheduleInstallment(endDate);
+ public static void validateSubmittedOnDate(FromJsonHelper fromJsonHelper,
Loan loan, JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ final LocalDate submittedOnDate =
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName,
+ jsonElement);
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull();
- if (endInstallment == null) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.endDateParamName)
-
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment
schedule installment does not exist");
- }
- }
+ if (submittedOnDate != null &&
DateUtils.isAfter(loan.getDisbursementDate(), submittedOnDate)) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName)
+ .failWithCode("before.loan.disbursement.date", "Submission
date cannot be before the loan disbursement date");
}
+ }
- // at least one of the following must be provided => graceOnPrincipal,
- // graceOnInterest, extraTerms, newInterestRate
- if
(!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName,
jsonElement)
- &&
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName,
jsonElement)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
+ public static void validateLoanIsActive(Loan loan, DataValidatorBuilder
dataValidatorBuilder) {
+ if (!loan.getStatus().isActive()) {
+
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
"Loan is not active");
}
+ }
- final LoanRepaymentScheduleInstallment installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
- if (rescheduleFromDate != null) {
- validateReschedulingInstallment(dataValidatorBuilder, installment);
- }
+ public static void validateSupportedParameters(JsonCommand jsonCommand,
Set<String> createRequestDataParameters) {
+ final String jsonString = jsonCommand.json();
- if (loan.isMultiDisburmentLoan()) {
- if (!loan.loanProduct().isDisallowExpectedDisbursements()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-
RescheduleLoansApiConstants.rescheduleForMultiDisbursementNotSupportedErrorCode,
- "Loan rescheduling is not supported for
multidisbursement tranche loans");
- }
+ if (StringUtils.isBlank(jsonString)) {
+ throw new InvalidJsonException();
}
- validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+ final Type typeToken = new TypeToken<Map<String, Object>>()
{}.getType();
+ jsonCommand.checkForUnsupportedParameters(typeToken, jsonString,
createRequestDataParameters);
}
- private static void validateReschedulingInstallment(DataValidatorBuilder
dataValidatorBuilder,
+ public static void validateReschedulingInstallment(DataValidatorBuilder
dataValidatorBuilder,
LoanRepaymentScheduleInstallment installment) {
if (installment == null) {
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName)
@@ -266,7 +212,7 @@ public class LoanRescheduleRequestDataValidator {
}
}
- private void validateForOverdueCharges(final DataValidatorBuilder
dataValidatorBuilder, final Loan loan,
+ public static void validateForOverdueCharges(final DataValidatorBuilder
dataValidatorBuilder, final Loan loan,
final LoanRepaymentScheduleInstallment installment) {
if (installment != null) {
LocalDate rescheduleFromDate = installment.getFromDate();
@@ -281,37 +227,99 @@ public class LoanRescheduleRequestDataValidator {
}
/**
- * Validates a user request to approve a loan reschedule request
+ * Validates the request to create a new loan reschedule entry
*
* @param jsonCommand
* the JSON command object (instance of the JsonCommand class)
+ **/
+ @Override
+ public void validateForCreateAction(final JsonCommand jsonCommand, final
Loan loan) {
+ if (loan.getLoanProductRelatedDetail().getLoanScheduleType() ==
LoanScheduleType.PROGRESSIVE) {
+
progressiveLoanRescheduleRequestDataValidatorDelegate.validateForCreateAction(jsonCommand,
loan);
+ } else {
+ validateSupportedParameters(jsonCommand,
CREATE_REQUEST_DATA_PARAMETERS);
+ final List<ApiParameterError> dataValidationErrors = new
ArrayList<>();
+ final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
+
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+
+ final JsonElement jsonElement = jsonCommand.parsedJson();
+ validateLoanIsActive(loan, dataValidatorBuilder);
+ validateSubmittedOnDate(fromJsonHelper, loan, jsonElement,
dataValidatorBuilder);
+ final LocalDate rescheduleFromDate =
validateAndRetrieveRescheduleFromDate(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateInterestRate(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateGraceOnPrincipal(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateGraceOnInterest(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateExtraTerms(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateRescheduleReasonId(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateRescheduleReasonComment(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateAndRetrieveAdjustedDate(fromJsonHelper, jsonElement,
rescheduleFromDate, dataValidatorBuilder);
+ validateEMIAndEndDate(fromJsonHelper, loan, jsonElement,
dataValidatorBuilder);
+ validateIsThereAnyIncomingChange(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateMultiDisburseLoan(loan, dataValidatorBuilder);
+
+ LoanRepaymentScheduleInstallment installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+ validateReschedulingInstallment(dataValidatorBuilder, installment);
+ validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+
+ if (!dataValidationErrors.isEmpty()) {
+ throw new
PlatformApiDataValidationException(dataValidationErrors);
+ }
+ }
+ }
+
+ private void validateIsThereAnyIncomingChange(FromJsonHelper
fromJsonHelper, JsonElement jsonElement,
+ DataValidatorBuilder dataValidatorBuilder) {
+ // at least one of the following must be provided => graceOnPrincipal,
+ // graceOnInterest, extraTerms, newInterestRate
+ if
(!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName,
jsonElement)
+ &&
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName,
jsonElement)
+ &&
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName,
jsonElement)
+ &&
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName,
jsonElement)
+ &&
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName,
jsonElement)
+ &&
!fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName,
jsonElement)) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
+ }
+ }
+
+ /**
+ * Validates a user request to approve a loan reschedule request
*
+ * @param jsonCommand
+ * the JSON command object (instance of the JsonCommand class)
**/
+ @Override
public void validateForApproveAction(final JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest) {
- final String jsonString = jsonCommand.json();
+ if
(loanRescheduleRequest.getLoan().getLoanRepaymentScheduleDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE))
{
+
progressiveLoanRescheduleRequestDataValidatorDelegate.validateForApproveAction(jsonCommand,
loanRescheduleRequest);
+ } else {
+ validateSupportedParameters(jsonCommand,
APPROVE_REQUEST_DATA_PARAMETERS);
- if (StringUtils.isBlank(jsonString)) {
- throw new InvalidJsonException();
- }
+ final List<ApiParameterError> dataValidationErrors = new
ArrayList<>();
+ final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
+
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
- final Type typeToken = new TypeToken<Map<String, Object>>()
{}.getType();
- this.fromJsonHelper.checkForUnsupportedParameters(typeToken,
jsonString, APPROVE_REQUEST_DATA_PARAMETERS);
+ final JsonElement jsonElement = jsonCommand.parsedJson();
+ validateApprovalDate(fromJsonHelper, loanRescheduleRequest,
jsonElement, dataValidatorBuilder);
+ validateRescheduleRequestStatus(loanRescheduleRequest,
dataValidatorBuilder);
- final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
- final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
-
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+ LocalDate rescheduleFromDate =
loanRescheduleRequest.getRescheduleFromDate();
+ final Loan loan = loanRescheduleRequest.getLoan();
+ LoanRepaymentScheduleInstallment installment;
- final JsonElement jsonElement = jsonCommand.parsedJson();
+ validateLoanIsActive(loan, dataValidatorBuilder);
+ installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
- final LocalDate approvedOnDate =
this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam,
- jsonElement);
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull();
+ validateReschedulingInstallment(dataValidatorBuilder, installment);
+ validateForOverdueCharges(dataValidatorBuilder, loan, installment);
- if (approvedOnDate != null &&
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), approvedOnDate)) {
-
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).failWithCode("before.submission.date",
- "Approval date cannot be before the request submission
date.");
+ if (!dataValidationErrors.isEmpty()) {
+ throw new
PlatformApiDataValidationException(dataValidationErrors);
+ }
}
+ }
+ public static void validateRescheduleRequestStatus(LoanRescheduleRequest
loanRescheduleRequest,
+ DataValidatorBuilder dataValidatorBuilder) {
LoanRescheduleRequestStatusEnumData
loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations
.status(loanRescheduleRequest.getStatusEnum());
@@ -320,38 +328,16 @@ public class LoanRescheduleRequestDataValidator {
"Loan reschedule request approval is not allowed. "
+ "Loan reschedule request is not in submitted and
pending approval state.");
}
+ }
- LocalDate rescheduleFromDate =
loanRescheduleRequest.getRescheduleFromDate();
- final Loan loan = loanRescheduleRequest.getLoan();
- LoanRepaymentScheduleInstallment installment = null;
- if (loan != null) {
-
- if (!loan.getStatus().isActive()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active",
"Loan is not active");
- }
-
- if (rescheduleFromDate != null) {
- final boolean isProgressiveLoanSchedule =
loan.getLoanProductRelatedDetail()
- .getLoanScheduleType() == LoanScheduleType.PROGRESSIVE;
- installment = isProgressiveLoanSchedule ?
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate)
- :
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
-
- if (installment == null) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-
"loan.repayment.schedule.installment.does.not.exist", "Repayment schedule
installment does not exist");
- }
-
- if (installment != null && installment.isObligationsMet()) {
-
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
- "loan.repayment.schedule.installment." +
"obligation.met", "Repayment schedule installment obligation met");
- }
- }
- }
-
- validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+ public static void validateApprovalDate(FromJsonHelper fromJsonHelper,
LoanRescheduleRequest loanRescheduleRequest,
+ JsonElement jsonElement, DataValidatorBuilder
dataValidatorBuilder) {
+ final LocalDate approvedOnDate =
fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam,
jsonElement);
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull();
- if (!dataValidationErrors.isEmpty()) {
- throw new PlatformApiDataValidationException(dataValidationErrors);
+ if (approvedOnDate != null &&
DateUtils.isAfter(loanRescheduleRequest.getSubmittedOnDate(), approvedOnDate)) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).failWithCode("before.submission.date",
+ "Approval date cannot be before the request submission
date.");
}
}
@@ -360,17 +346,10 @@ public class LoanRescheduleRequestDataValidator {
*
* @param jsonCommand
* the JSON command object (instance of the JsonCommand class)
- *
**/
+ @Override
public void validateForRejectAction(final JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest) {
- final String jsonString = jsonCommand.json();
-
- if (StringUtils.isBlank(jsonString)) {
- throw new InvalidJsonException();
- }
-
- final Type typeToken = new TypeToken<Map<String, Object>>()
{}.getType();
- this.fromJsonHelper.checkForUnsupportedParameters(typeToken,
jsonString, REJECT_REQUEST_DATA_PARAMETERS);
+ validateSupportedParameters(jsonCommand,
REJECT_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
index d7eb19a7c..b8211af7e 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleRequest.java
@@ -272,14 +272,15 @@ public class LoanRescheduleRequest extends
AbstractPersistableCustom<Long> {
return this.loanRescheduleRequestToTermVariationMappings;
}
+ public LoanTermVariations
getInterestRateFromInstallmentTermVariationIfExists() {
+ return this.loanRescheduleRequestToTermVariationMappings.stream()
+
.map(LoanRescheduleRequestToTermVariationMapping::getLoanTermVariations)
+ .filter(loanTermVariations ->
loanTermVariations.getTermType().isInterestRateFromInstallment()).findFirst().orElse(null);
+ }
+
public LoanTermVariations getDueDateTermVariationIfExists() {
- if (this.loanRescheduleRequestToTermVariationMappings != null &&
this.loanRescheduleRequestToTermVariationMappings.size() > 0) {
- for (LoanRescheduleRequestToTermVariationMapping mapping :
this.loanRescheduleRequestToTermVariationMappings) {
- if
(mapping.getLoanTermVariations().getTermType().isDueDateVariation()) {
- return mapping.getLoanTermVariations();
- }
- }
- }
- return null;
+ return this.loanRescheduleRequestToTermVariationMappings.stream()
+
.map(LoanRescheduleRequestToTermVariationMapping::getLoanTermVariations)
+ .filter(loanTermVariations ->
loanTermVariations.getTermType().isDueDateVariation()).findFirst().orElse(null);
}
}
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java
new file mode 100644
index 000000000..8736ab5c5
--- /dev/null
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java
@@ -0,0 +1,188 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
+
+import static
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.APPROVE_REQUEST_DATA_PARAMETERS;
+import static
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.CREATE_REQUEST_DATA_PARAMETERS;
+import static
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateAndRetrieveAdjustedDate;
+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;
+import static
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleRequestStatus;
+import static
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateReschedulingInstallment;
+import static
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateSubmittedOnDate;
+import static
org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateSupportedParameters;
+
+import com.google.gson.JsonElement;
+import jakarta.persistence.criteria.Predicate;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+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.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
+import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
+import
org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
+import org.springframework.stereotype.Component;
+
+@Component("progressiveLoanRescheduleRequestDataValidatorImpl")
+@AllArgsConstructor
+public class ProgressiveLoanRescheduleRequestDataValidator implements
LoanRescheduleRequestDataValidator {
+
+ private final FromJsonHelper fromJsonHelper;
+ private final LoanRescheduleRequestRepository
loanRescheduleRequestRepository;
+
+ @Override
+ public void validateForCreateAction(JsonCommand jsonCommand, Loan loan) {
+ validateSupportedParameters(jsonCommand,
CREATE_REQUEST_DATA_PARAMETERS);
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
+
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+
+ final JsonElement jsonElement = jsonCommand.parsedJson();
+
+ validateLoanIsActive(loan, dataValidatorBuilder);
+ validateSubmittedOnDate(fromJsonHelper, loan, jsonElement,
dataValidatorBuilder);
+ final LocalDate rescheduleFromDate =
validateAndRetrieveRescheduleFromDate(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateRescheduleReasonId(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ validateRescheduleReasonComment(fromJsonHelper, jsonElement,
dataValidatorBuilder);
+ LocalDate adjustedDueDate =
validateAndRetrieveAdjustedDate(fromJsonHelper, jsonElement,
rescheduleFromDate, dataValidatorBuilder);
+ BigDecimal interestRate = validateInterestRate(fromJsonHelper,
jsonElement, dataValidatorBuilder);
+ validateUnsupportedParams(jsonElement, dataValidatorBuilder);
+
+ boolean hasInterestRateChange = interestRate != null;
+ boolean hasAdjustDueDateChange = adjustedDueDate != null;
+
+ if (hasInterestRateChange && hasAdjustDueDateChange) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode(
+
RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode,
+ "Only one operation is supported at a time during Loan
Rescheduling");
+ }
+
+ final LocalDate businessDate = DateUtils.getBusinessLocalDate();
+ if (rescheduleFromDate != null) {
+ if (hasInterestRateChange &&
!rescheduleFromDate.isAfter(businessDate)) {
+
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
+
"loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future",
+ "Loan Reschedule From date should be in the future.");
+ }
+ if (hasInterestRateChange) {
+ validateInterestRateChangeRescheduleFromDate(loan,
rescheduleFromDate);
+ }
+ }
+
+ LoanRepaymentScheduleInstallment installment;
+ if (hasInterestRateChange) {
+ installment =
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
+ } else {
+ installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+ }
+
+ validateReschedulingInstallment(dataValidatorBuilder, installment);
+ validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+
+ if (!dataValidationErrors.isEmpty()) {
+ throw new PlatformApiDataValidationException(dataValidationErrors);
+ }
+ }
+
+ private void validateUnsupportedParams(JsonElement jsonElement,
DataValidatorBuilder dataValidatorBuilder) {
+ final var unsupportedFields =
List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, //
+ RescheduleLoansApiConstants.graceOnInterestParamName, //
+ RescheduleLoansApiConstants.extraTermsParamName, //
+ RescheduleLoansApiConstants.emiParamName//
+ );
+
+ for (var unsupportedField : unsupportedFields) {
+ if (this.fromJsonHelper.parameterHasValue(unsupportedField,
jsonElement)) {
+
dataValidatorBuilder.reset().parameter(unsupportedField).failWithCode(
+
RescheduleLoansApiConstants.rescheduleSelectedOperationNotSupportedErrorCode,
+ "Selected operation is not supported by Progressive
Loan at a time during Loan Rescheduling");
+ }
+ }
+ }
+
+ @Override
+ public void validateForApproveAction(JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest) {
+ validateSupportedParameters(jsonCommand,
APPROVE_REQUEST_DATA_PARAMETERS);
+
+ final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+ final DataValidatorBuilder dataValidatorBuilder = new
DataValidatorBuilder(dataValidationErrors)
+
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
+ final JsonElement jsonElement = jsonCommand.parsedJson();
+ validateApprovalDate(fromJsonHelper, loanRescheduleRequest,
jsonElement, dataValidatorBuilder);
+ validateRescheduleRequestStatus(loanRescheduleRequest,
dataValidatorBuilder);
+ LocalDate rescheduleFromDate =
loanRescheduleRequest.getRescheduleFromDate();
+ final Loan loan = loanRescheduleRequest.getLoan();
+ LoanRepaymentScheduleInstallment installment;
+ validateLoanIsActive(loan, dataValidatorBuilder);
+
+ if
(loanRescheduleRequest.getInterestRateFromInstallmentTermVariationIfExists() !=
null) {
+ installment =
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
+ if (!rescheduleFromDate.isAfter(DateUtils.getBusinessLocalDate()))
{
+ throw new GeneralPlatformDomainRuleException(
+
"loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future",
+ "Loan Reschedule From date should be in the future.",
rescheduleFromDate);
+ }
+ } else {
+ installment =
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
+ }
+ validateReschedulingInstallment(dataValidatorBuilder, installment);
+ validateForOverdueCharges(dataValidatorBuilder, loan, installment);
+
+ if (!dataValidationErrors.isEmpty()) {
+ throw new PlatformApiDataValidationException(dataValidationErrors);
+ }
+ }
+
+ @Override
+ public void validateForRejectAction(JsonCommand jsonCommand,
LoanRescheduleRequest loanRescheduleRequest) {
+ throw new UnsupportedOperationException("Nothing to override here");
+ }
+
+ private void validateInterestRateChangeRescheduleFromDate(Loan loan,
LocalDate rescheduleFromDate) {
+ boolean alreadyExistInterestRateChange =
loanRescheduleRequestRepository.exists((root, query, criteriaBuilder) -> {
+ Predicate loanPredicate = criteriaBuilder.equal(root.get("loan"),
loan);
+ Predicate statusPredicate = root.get("statusEnum")
+
.in(List.of(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
LoanStatus.APPROVED.getValue()));
+ Predicate datePredicate =
criteriaBuilder.equal(root.get("rescheduleFromDate"), rescheduleFromDate);
+ return criteriaBuilder.and(loanPredicate, statusPredicate,
datePredicate);
+ });
+ if (alreadyExistInterestRateChange) {
+ throw new
GeneralPlatformDomainRuleException("loan.reschedule.interest.rate.change.already.exists",
+ "Interest rate change for the provided date is already
exists.", rescheduleFromDate);
+ }
+ }
+}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
index 751c3d41f..235155aaa 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java
@@ -64,7 +64,6 @@ import
org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
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.loanschedule.data.LoanScheduleDTO;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator;
@@ -87,6 +86,7 @@ import
org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusi
import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.orm.jpa.JpaSystemException;
@@ -101,11 +101,11 @@ public class
LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
private final CodeValueRepositoryWrapper codeValueRepositoryWrapper;
private final PlatformSecurityContext platformSecurityContext;
+ @Qualifier("loanRescheduleRequestDataValidator")
private final LoanRescheduleRequestDataValidator
loanRescheduleRequestDataValidator;
private final LoanRescheduleRequestRepository
loanRescheduleRequestRepository;
private final LoanRepaymentScheduleHistoryRepository
loanRepaymentScheduleHistoryRepository;
private final LoanScheduleHistoryWritePlatformService
loanScheduleHistoryWritePlatformService;
- private final LoanTransactionRepository loanTransactionRepository;
private final JournalEntryWritePlatformService
journalEntryWritePlatformService;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanAssembler loanAssembler;
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index 15d557e88..0228e044a 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -333,7 +333,7 @@ public abstract class BaseLoanIntegrationTest {
}).toList();
}
- private AdvancedPaymentData createDefaultPaymentAllocation(String
futureInstallmentAllocationRule) {
+ public AdvancedPaymentData createDefaultPaymentAllocation(String
futureInstallmentAllocationRule) {
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
advancedPaymentData.setTransactionType("DEFAULT");
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
index 7f478bbcc..cf2877dca 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java
@@ -32,11 +32,9 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.AllowAttributeOverrides;
import org.apache.fineract.client.models.ChargeData;
@@ -48,7 +46,6 @@ import
org.apache.fineract.client.models.GetLoanTransactionRelation;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.JournalEntryTransactionItem;
-import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
@@ -73,7 +70,6 @@ import
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHe
import org.apache.fineract.integrationtests.common.system.CodeHelper;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
-import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -751,29 +747,4 @@ public class
LoanAccountChargeOffWithAdvancedPaymentAllocationTest extends BaseL
return Long.valueOf(fundId);
}
-
- private AdvancedPaymentData createDefaultPaymentAllocation(String
futureInstallmentAllocationRule) {
- AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
- advancedPaymentData.setTransactionType("DEFAULT");
-
advancedPaymentData.setFutureInstallmentAllocationRule(futureInstallmentAllocationRule);
-
- List<PaymentAllocationOrder> paymentAllocationOrders =
getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
- PaymentAllocationType.PAST_DUE_FEE,
PaymentAllocationType.PAST_DUE_PRINCIPAL,
PaymentAllocationType.PAST_DUE_INTEREST,
- PaymentAllocationType.DUE_PENALTY,
PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
- PaymentAllocationType.DUE_INTEREST,
PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
- PaymentAllocationType.IN_ADVANCE_PRINCIPAL,
PaymentAllocationType.IN_ADVANCE_INTEREST);
-
- advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
- return advancedPaymentData;
- }
-
- private List<PaymentAllocationOrder>
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
- AtomicInteger integer = new AtomicInteger(1);
- return Arrays.stream(paymentAllocationTypes).map(pat -> {
- PaymentAllocationOrder paymentAllocationOrder = new
PaymentAllocationOrder();
- paymentAllocationOrder.setPaymentAllocationRule(pat.name());
- paymentAllocationOrder.setOrder(integer.getAndIncrement());
- return paymentAllocationOrder;
- }).toList();
- }
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
index 361b6b745..0f3b14c58 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java
@@ -19,6 +19,7 @@
package org.apache.fineract.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.restassured.builder.RequestSpecBuilder;
@@ -31,19 +32,31 @@ import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.fineract.client.models.AdvancedPaymentData;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
+import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdRequest;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
+import org.apache.fineract.client.util.CallFailedRuntimeException;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
import
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import
org.apache.fineract.integrationtests.common.loans.LoanRescheduleRequestTestBuilder;
-import
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,8 +64,7 @@ import org.slf4j.LoggerFactory;
* Test the creation, approval and rejection of a loan reschedule request
**/
@SuppressWarnings({ "rawtypes" })
-@ExtendWith(LoanTestLifecycleExtension.class)
-public class LoanRescheduleRequestTest {
+public class LoanRescheduleRequestTest extends BaseLoanIntegrationTest {
private static final Logger LOG =
LoggerFactory.getLogger(LoanRescheduleRequestTest.class);
private ResponseSpecification responseSpec;
@@ -235,6 +247,99 @@ public class LoanRescheduleRequestTest {
LOG.info("Successfully approved loan reschedule request (ID: {})",
this.loanRescheduleRequestId);
}
+ @Test
+ public void testInterestRateChangeForProgressiveLoan() {
+ PostClientsResponse client =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+ final Account assetAccount = accountHelper.createAssetAccount();
+ final Account incomeAccount = accountHelper.createIncomeAccount();
+ final Account expenseAccount = accountHelper.createExpenseAccount();
+ final Account overpaymentAccount =
accountHelper.createLiabilityAccount();
+
+ Integer commonLoanProductId = createLoanProduct("500", "15", "4",
true, "25", true, LoanScheduleType.PROGRESSIVE,
+ LoanScheduleProcessingType.HORIZONTAL, assetAccount,
incomeAccount, expenseAccount, overpaymentAccount);
+ AtomicReference<PostCreateRescheduleLoansResponse> rescheduleResponse
= new AtomicReference<>();
+ AtomicReference<PostLoansResponse> loanResponse = new
AtomicReference<>();
+ // Do not allow interest rate change on not active loan
+ // Do not allow interest rate change twice on the same day
+ runAt("15 February 2023", () -> {
+
+ loanResponse.set(applyForLoanApplication(client.getClientId(),
commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3,
+ BigDecimal.ZERO, "01 January 2023", "01 January 2023"));
+
+ loanTransactionHelper.approveLoan(loanResponse.get().getLoanId(),
+ new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN)
+ .approvedOnDate("01 January 2023").locale("en"));
+
+ CallFailedRuntimeException exception =
assertThrows(CallFailedRuntimeException.class,
+ () -> loanRescheduleRequestHelper
+ .createLoanRescheduleRequest(new
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023")
+
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16
February 2023")));
+ assertEquals(400, exception.getResponse().code());
+ assertTrue(exception.getMessage().contains("loan.is.not.active"));
+
+ loanTransactionHelper.disburseLoan(loanResponse.get().getLoanId(),
+ new PostLoansLoanIdRequest().actualDisbursementDate("15
February 2023").dateFormat(DATETIME_PATTERN)
+
.transactionAmount(BigDecimal.valueOf(500.00)).locale("en"));
+
+
rescheduleResponse.set(loanRescheduleRequestHelper.createLoanRescheduleRequest(new
PostCreateRescheduleLoansRequest()
+
.loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15
February 2023")
+
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16
February 2023")));
+
+ exception = assertThrows(CallFailedRuntimeException.class,
+ () -> loanRescheduleRequestHelper
+ .createLoanRescheduleRequest(new
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023")
+
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16
February 2023")));
+ assertEquals(403, exception.getResponse().code());
+
assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.already.exists"));
+ });
+ // Do not allow approve an interest rate change if the reschedule from
date is not in the future
+ // Do not allow create interest rate change if a previous interest
rate change got already approved for that
+ // date
+ runAt("16 February 2023", () -> {
+ CallFailedRuntimeException exception =
assertThrows(CallFailedRuntimeException.class,
+ () ->
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleResponse.get().getResourceId(),
+ new
PostUpdateRescheduleLoansRequest().approvedOnDate("16 February
2024").locale("en")
+ .dateFormat(DATETIME_PATTERN)));
+ assertEquals(403, exception.getResponse().code());
+
assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future"));
+
+ PostCreateRescheduleLoansResponse rescheduleLoansResponse =
loanRescheduleRequestHelper
+ .createLoanRescheduleRequest(new
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February
2023").newInterestRate(BigDecimal.ONE)
+ .rescheduleReasonId(1L).rescheduleFromDate("17
February 2023"));
+
+
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(),
+ new PostUpdateRescheduleLoansRequest().approvedOnDate("17
February 2024").locale("en").dateFormat(DATETIME_PATTERN));
+
+ exception = assertThrows(CallFailedRuntimeException.class,
+ () -> loanRescheduleRequestHelper
+ .createLoanRescheduleRequest(new
PostCreateRescheduleLoansRequest().loanId(rescheduleLoansResponse.getLoanId())
+
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February 2023")
+
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("17
February 2023")));
+ assertEquals(403, exception.getResponse().code());
+
assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.already.exists"));
+
+ });
+
+ // Allow new interest rate change if the previous got rejected
+ runAt("17 February 2023", () -> {
+ PostCreateRescheduleLoansResponse rescheduleLoansResponse =
loanRescheduleRequestHelper
+ .createLoanRescheduleRequest(new
PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
+
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("18 February
2023").newInterestRate(BigDecimal.ONE)
+ .rescheduleReasonId(1L).rescheduleFromDate("18
February 2023"));
+
+
loanRescheduleRequestHelper.rejectLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(),
+ new PostUpdateRescheduleLoansRequest().rejectedOnDate("18
February 2024").locale("en").dateFormat(DATETIME_PATTERN));
+
+ loanRescheduleRequestHelper.createLoanRescheduleRequest(new
PostCreateRescheduleLoansRequest()
+
.loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("18
February 2023")
+
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("18
February 2023"));
+
+ });
+ }
+
/**
* create new loan reschedule request
**/
@@ -255,4 +360,53 @@ public class LoanRescheduleRequestTest {
public void testCreateLoanRescheduleChangeEMIRequest() {
this.createLoanRescheduleChangeEMIRequest();
}
+
+ private PostLoansResponse applyForLoanApplication(final Long clientId,
final Integer loanProductId, final BigDecimal principal,
+ final int loanTermFrequency, final int repaymentAfterEvery, final
int numberOfRepayments, final BigDecimal interestRate,
+ final String expectedDisbursementDate, final String
submittedOnDate, String transactionProcessorCode,
+ String loanScheduleProcessingType) {
+ LOG.info("--------------------------------APPLYING FOR LOAN
APPLICATION--------------------------------");
+ return loanTransactionHelper.applyLoan(new
PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
+
.expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN)
+
.transactionProcessingStrategyCode(transactionProcessorCode).locale("en").submittedOnDate(submittedOnDate)
+
.amortizationType(1).interestRatePerPeriod(interestRate).interestCalculationPeriodType(1).interestType(0)
+
.repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery).repaymentFrequencyType(0)
+
.numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency).loanTermFrequencyType(0).principal(principal)
+
.loanType("individual").loanScheduleProcessingType(loanScheduleProcessingType)
+ .maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
+ }
+
+ private PostLoansResponse applyForLoanApplication(final Long clientId,
final Integer loanProductId, final BigDecimal principal,
+ final int loanTermFrequency, final int repaymentAfterEvery, final
int numberOfRepayments, final BigDecimal interestRate,
+ final String expectedDisbursementDate, final String
submittedOnDate) {
+ return applyForLoanApplication(clientId, loanProductId, principal,
loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
+ interestRate, expectedDisbursementDate, submittedOnDate,
LoanScheduleProcessingType.HORIZONTAL);
+ }
+
+ private PostLoansResponse applyForLoanApplication(final Long clientId,
final Integer loanProductId, final BigDecimal principal,
+ final int loanTermFrequency, final int repaymentAfterEvery, final
int numberOfRepayments, final BigDecimal interestRate,
+ final String expectedDisbursementDate, final String
submittedOnDate, LoanScheduleProcessingType loanScheduleProcessingType) {
+ LOG.info("--------------------------------APPLYING FOR LOAN
APPLICATION--------------------------------");
+ return applyForLoanApplication(clientId, loanProductId, principal,
loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
+ interestRate, expectedDisbursementDate, submittedOnDate,
+
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY,
loanScheduleProcessingType.name());
+ }
+
+ private Integer createLoanProduct(final String principal, final String
repaymentAfterEvery, final String numberOfRepayments,
+ boolean downPaymentEnabled, String downPaymentPercentage, boolean
autoPayForDownPayment, LoanScheduleType loanScheduleType,
+ LoanScheduleProcessingType loanScheduleProcessingType, final
Account... accounts) {
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation("NEXT_INSTALLMENT");
+ LOG.info("------------------------------CREATING NEW LOAN PRODUCT
---------------------------------------");
+ final String loanProductJSON = new
LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
+
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
+ .withEnableDownPayment(downPaymentEnabled,
downPaymentPercentage, autoPayForDownPayment).withinterestRatePerPeriod("0")
+ .withInterestRateFrequencyTypeAsMonths()
+
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
+
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
+
.addAdvancedPaymentAllocation(defaultAllocation).withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
+
.withInterestTypeAsDecliningBalance().withMultiDisburse().withDisallowExpectedDisbursements(true)
+
.withLoanScheduleType(loanScheduleType).withLoanScheduleProcessingType(loanScheduleProcessingType).withDaysInMonth("30")
+ .withDaysInYear("365").withMoratorium("0", "0").build(null);
+ return loanTransactionHelper.getLoanProductId(loanProductJSON);
+ }
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
index f9b15df13..2c593c0c5 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java
@@ -939,7 +939,7 @@ public class LoanTransactionAccrualActivityPostingTest
extends BaseLoanIntegrati
AtomicReference<Long> loanId = new AtomicReference<>();
runAt(creationBusinessDay, () -> {
Long localLoanProductId = loanTransactionHelper
-
.createLoanProduct(loanProductsRequestPin30InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity())
+
.createLoanProduct(loanProductsRequestInterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity())
.getResourceId();
loanId.set(applyForLoanApplication(client.getClientId(),
localLoanProductId, BigDecimal.valueOf(1000), disbursementDay));
loanTransactionHelper.approveLoan(loanId.get(), new
PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
@@ -964,7 +964,7 @@ public class LoanTransactionAccrualActivityPostingTest
extends BaseLoanIntegrati
});
}
- private PostLoanProductsRequest
loanProductsRequestPin30InterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity()
{
+ private PostLoanProductsRequest
loanProductsRequestInterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity()
{
String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
String shortName = Utils.uniqueRandomStringGenerator("", 4);
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
index 901e52dfe..432a99301 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java
@@ -83,4 +83,8 @@ public class LoanRescheduleRequestHelper extends
IntegrationTest {
public PostUpdateRescheduleLoansResponse approveLoanRescheduleRequest(Long
scheduleId, PostUpdateRescheduleLoansRequest request) {
return
ok(fineract().rescheduleLoans.updateLoanRescheduleRequest(scheduleId, request,
"approve"));
}
+
+ public PostUpdateRescheduleLoansResponse rejectLoanRescheduleRequest(Long
scheduleId, PostUpdateRescheduleLoansRequest request) {
+ return
ok(fineract().rescheduleLoans.updateLoanRescheduleRequest(scheduleId, request,
"reject"));
+ }
}