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 04533f03c FINERACT-2117: Early and late payment - In advance payment
strategy: Adjust last, unpaid period
04533f03c is described below
commit 04533f03c64d1c79b8633aa5f9c2cb8664dde57f
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Thu Aug 15 10:32:15 2024 -0600
FINERACT-2117: Early and late payment - In advance payment strategy: Adjust
last, unpaid period
---
.../test/data/AdvancePaymentsAdjustmentType.java | 2 +-
.../domain/LoanRescheduleStrategyMethod.java | 5 +-
.../LoanDropdownReadPlatformServiceImpl.java | 3 +-
.../loanproduct/service/LoanEnumerations.java | 3 +
.../serialization/LoanProductDataValidator.java | 23 +++++++-
.../products/api/SelfLoanProductsApiResource.java | 2 +-
.../main/resources/static/legacy-docs/apiLive.htm | 4 +-
...PaymentAllocationLoanRepaymentScheduleTest.java | 64 ++++++++++++++++++++++
.../integrationtests/BaseLoanIntegrationTest.java | 23 +++++++-
9 files changed, 120 insertions(+), 9 deletions(-)
diff --git
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java
index 6b194bfcb..e236191a4 100644
---
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java
+++
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/AdvancePaymentsAdjustmentType.java
@@ -20,7 +20,7 @@ package org.apache.fineract.test.data;
public enum AdvancePaymentsAdjustmentType {
- RESCHEDULE_NEXT_REPAYMENTS(1), REDUCE_NUMBER_OF_INSTALLMENTS(2),
REDUCE_EMI_AMOUNT(3);
+ RESCHEDULE_NEXT_REPAYMENTS(1), REDUCE_NUMBER_OF_INSTALLMENTS(2),
REDUCE_EMI_AMOUNT(3), ADJUST_LAST_UNPAID_PERIOD(4);
public final Integer value;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java
index fc3438911..293f0309a 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java
@@ -27,6 +27,7 @@ import java.util.Map;
* <li>RESCHEDULE_NEXT_REPAYMENTS</li>
* <li>REDUCE_NUMBER_OF_INSTALLMENTS</li>
* <li>REDUCE_EMI_AMOUNT</li>
+ * <li>ADJUST_LAST_UNPAID_PERIOD</li>
* </ul>
*/
@@ -35,7 +36,9 @@ public enum LoanRescheduleStrategyMethod {
INVALID(0, "loanRescheduleStrategyMethod.invalid"), //
RESCHEDULE_NEXT_REPAYMENTS(1,
"loanRescheduleStrategyMethod.reschedule.next.repayments"), //
REDUCE_NUMBER_OF_INSTALLMENTS(2,
"loanRescheduleStrategyMethod.reduce.number.of.installments"), //
- REDUCE_EMI_AMOUNT(3, "loanRescheduleStrategyMethod.reduce.emi.amount");
+ REDUCE_EMI_AMOUNT(3, "loanRescheduleStrategyMethod.reduce.emi.amount"), //
+ ADJUST_LAST_UNPAID_PERIOD(4,
"loanRescheduleStrategyMethod.adjust.last.unpaid.period"), //
+ ;
private final Integer value;
private final String code;
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
index 5dc8f59e6..aa3b6ac00 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java
@@ -159,7 +159,8 @@ public class LoanDropdownReadPlatformServiceImpl implements
LoanDropdownReadPlat
return
Arrays.asList(rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT),
rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS),
-
rescheduleStrategyType(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS));
+
rescheduleStrategyType(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS),
+
rescheduleStrategyType(LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD));
}
@Override
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
index 434a219b1..4db9c5cdc 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
@@ -492,6 +492,9 @@ public final class LoanEnumerations {
case RESCHEDULE_NEXT_REPAYMENTS ->
new
EnumOptionData(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getValue().longValue(),
LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getCode(), "Reschedule
next repayments");
+ case ADJUST_LAST_UNPAID_PERIOD ->
+ new
EnumOptionData(LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.getValue().longValue(),
+
LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.getCode(), "Adjust last,
unpaid period");
default -> new
EnumOptionData(LoanRescheduleStrategyMethod.INVALID.getValue().longValue(),
LoanRescheduleStrategyMethod.INVALID.getCode(), "Invalid");
};
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 30ae80d3d..b55f40c68 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -61,6 +61,7 @@ import
org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCa
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
+import
org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import
org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import
org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
@@ -1042,7 +1043,27 @@ public final class LoanProductDataValidator {
final Integer rescheduleStrategyMethod = this.fromApiJsonHelper
.extractIntegerNamed(LoanProductConstants.rescheduleStrategyMethodParameterName,
element, Locale.getDefault());
baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).value(rescheduleStrategyMethod)
- .notNull().inMinMaxRange(1, 3);
+ .notNull().inMinMaxRange(1, 4);
+ final LoanRescheduleStrategyMethod loanRescheduleStrategyMethod =
LoanRescheduleStrategyMethod
+ .fromInt(rescheduleStrategyMethod);
+
+ String loanScheduleType = LoanScheduleType.CUMULATIVE.toString();
+ if
(fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE,
element)) {
+ loanScheduleType =
fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE,
element);
+ }
+ if
(LoanScheduleType.CUMULATIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
+ &&
LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.equals(loanRescheduleStrategyMethod))
{
+
baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).failWithCode(
+
"reschedule.strategy.method.not.supported.for.loan.schedule.type.cumulative",
+ "Adjust last, unpaid period is only supported for
Progressive loan schedule type");
+ }
+
+ if
(LoanScheduleType.PROGRESSIVE.equals(LoanScheduleType.valueOf(loanScheduleType))
+ &&
!LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.equals(loanRescheduleStrategyMethod))
{
+
baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).failWithCode(
+
"reschedule.strategy.method.not.supported.for.loan.schedule.type.progressive",
+ loanScheduleType.toString() + " reschedule strategy
type is not supported for Progressive loan schedule type");
+ }
}
RecalculationFrequencyType frequencyType = null;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfLoanProductsApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfLoanProductsApiResource.java
index 4f2d33ffa..7569a0860 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfLoanProductsApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/products/api/SelfLoanProductsApiResource.java
@@ -99,7 +99,7 @@ import org.springframework.stereotype.Component;
+ "Specifies which amount portion should be added to principal for
interest recalculation. \n"
+ "Example Values:0=NONE(Only on principal),
1=INTEREST(Principal+Interest), 2=FEE(Principal+Fee), 3=FEE And INTEREST
(Principal+Fee+Interest)\n"
+ "rescheduleStrategyMethod\n" + "Specifies what action should perform
on loan repayment schedule for advance payments. \n"
- + "Example Values:1=Reschedule next repayments, 2=Reduce number of
installments, 3=Reduce EMI amount\n"
+ + "Example Values:1=Reschedule next repayments, 2=Reduce number of
installments, 3=Reduce EMI amount, 4=Adjust last, unpaid period\n"
+ "recalculationCompoundingFrequencyType\n"
+ "Specifies effective date from which the compounding of interest or
fee amounts will be considered in recalculation on late payment.\n"
+ "Example Values:1=Same as repayment period, 2=Daily, 3=Weekly,
4=Monthly\n" + "recalculationCompoundingFrequencyInterval\n"
diff --git
a/fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm
b/fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm
index 600844d55..ae475f2e8 100644
--- a/fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm
+++ b/fineract-provider/src/main/resources/static/legacy-docs/apiLive.htm
@@ -15845,7 +15845,7 @@ An enumeration that indicates the type of transaction
processing strategy to be
</tr>
<tr>
<td
class=fielddesc>Specifies what action should perform on loan repayment schedule
for advance payments. <br>
- <span>Example
Values:</span>1=Reschedule next repayments, 2=Reduce number of installments,
3=Reduce EMI amount</td>
+ <span>Example
Values:</span>1=Reschedule next repayments, 2=Reduce number of installments,
3=Reduce EMI amount, 4=Adjust last, unpaid period</td>
</tr>
<tr class=alt>
<td>recalculationCompoundingFrequencyType</td>
@@ -51748,7 +51748,7 @@ An enumeration that indicates the type of transaction
processing strategy to be
</tr>
<tr>
<td
class=fielddesc>Specifies what action should perform on loan repayment schedule
for advance payments. <br>
- <span>Example
Values:</span>1=Reschedule next repayments, 2=Reduce number of installments,
3=Reduce EMI amount</td>
+ <span>Example
Values:</span>1=Reschedule next repayments, 2=Reduce number of installments,
3=Reduce EMI amount, 4=Adjust last, unpaid period</td>
</tr>
<tr class=alt>
<td>recalculationCompoundingFrequencyType</td>
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index 9f5a5f150..4c83df782 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -44,6 +44,7 @@ import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.BusinessDateRequest;
import org.apache.fineract.client.models.CreditAllocationData;
import org.apache.fineract.client.models.CreditAllocationOrder;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
@@ -5095,6 +5096,69 @@ public class
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
+ // uc148a: Advanced payment allocation, with Interest Recalculation in
Loan Product and Adjust last, unpaid period
+ // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+ // 1. Create a Loan product with Adv. Pment. Alloc. with Interest
Recalculation enabled and Adjust last, unpaid
+ // period
+ @Test
+ public void uc148a() {
+ runAt("23 March 2024", () -> {
+ final Integer rescheduleStrategyMethod = 4; // Adjust last, unpaid
period
+ PostLoanProductsRequest loanProduct =
createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocationAndInterestRecalculation(
+ (double) 80.0, rescheduleStrategyMethod);
+
+ final PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProduct);
+ assertNotNull(loanProductResponse);
+
+ final GetLoanProductsProductIdResponse loanProductDetails =
loanProductHelper
+
.retrieveLoanProductById(loanProductResponse.getResourceId());
+ assertNotNull(loanProductDetails);
+ assertTrue(loanProductDetails.getIsInterestRecalculationEnabled());
+ assertEquals(rescheduleStrategyMethod.longValue(),
+
loanProductDetails.getInterestRecalculationData().getRescheduleStrategyType().getId());
+ });
+ }
+
+ // uc148b: Negative Test: Advanced payment allocation, with Interest
Recalculation in Loan Product but try to use
+ // Reduce EMI amount
+ // ADVANCED_PAYMENT_ALLOCATION_STRATEGY
+ // 1. Try to Create a Loan product with Adv. Pment. Alloc. with Interest
Recalculation enabled and use Reduce EMI
+ // amount
+ @Test
+ public void uc148b() {
+ runAt("23 March 2024", () -> {
+ final Integer rescheduleStrategyMethod = 3; // Reduce EMI amount
+ PostLoanProductsRequest loanProduct =
createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocationAndInterestRecalculation(
+ (double) 80.0, rescheduleStrategyMethod);
+
+ CallFailedRuntimeException callFailedRuntimeException =
Assertions.assertThrows(CallFailedRuntimeException.class,
+ () -> loanProductHelper.createLoanProduct(loanProduct));
+
+
Assertions.assertTrue(callFailedRuntimeException.getMessage().contains("is not
supported for Progressive loan schedule type"));
+ });
+ }
+
+ // uc148c: Negative Test: Comulative, with Interest Recalculation in Loan
Product but try to use
+ // Adjust last, unpaid period
+ // COMULATIVE
+ // 1. Try to Create a Loan product with Comulative with Interest
Recalculation enabled and use Adjust last, unpaid
+ // period
+ @Test
+ public void uc148c() {
+ runAt("23 March 2024", () -> {
+ final Integer rescheduleStrategyMethod = 4; // Adjust last, unpaid
period
+ PostLoanProductsRequest loanProduct =
createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocationAndInterestRecalculation(
+ (double) 80.0,
rescheduleStrategyMethod).transactionProcessingStrategyCode(LoanProductTestBuilder.DEFAULT_STRATEGY)//
+ .loanScheduleType(LoanScheduleType.CUMULATIVE.toString());
+
+ CallFailedRuntimeException callFailedRuntimeException =
Assertions.assertThrows(CallFailedRuntimeException.class,
+ () -> loanProductHelper.createLoanProduct(loanProduct));
+
+ Assertions.assertTrue(callFailedRuntimeException.getMessage()
+ .contains("Adjust last, unpaid period is only supported
for Progressive loan schedule type"));
+ });
+ }
+
private Long
applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long
clientId, Long loanProductId,
Integer numberOfRepayments, String loanDisbursementDate, double
amount) {
LOG.info("------------------------------APPLY AND APPROVE LOAN
---------------------------------------");
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 94cb0e7ba..fc0e6f6e6 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
@@ -205,8 +205,12 @@ public abstract class BaseLoanIntegrationTest {
return Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey();
}
- // Loan product with proper accounting setup
protected PostLoanProductsRequest
createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() {
+ return createOnePeriod30DaysPeriodicAccrualProduct((double) 0);
+ }
+
+ // Loan product with proper accounting setup
+ protected PostLoanProductsRequest
createOnePeriod30DaysPeriodicAccrualProduct(double interestRatePerPeriod) {
return new
PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_",
6))//
.shortName(Utils.uniqueRandomStringGenerator("", 4))//
.description("Loan Product Description")//
@@ -224,7 +228,7 @@ public abstract class BaseLoanIntegrationTest {
.maxNumberOfRepayments(30)//
.isLinkedToFloatingInterestRates(false)//
.minInterestRatePerPeriod((double) 0)//
- .interestRatePerPeriod((double) 0)//
+ .interestRatePerPeriod(interestRatePerPeriod)//
.maxInterestRatePerPeriod((double) 100)//
.interestRateFrequencyType(2)//
.repaymentEvery(30)//
@@ -302,6 +306,21 @@ public abstract class BaseLoanIntegrationTest {
.addPaymentAllocationItem(defaultAllocation);
}
+ protected PostLoanProductsRequest
createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocationAndInterestRecalculation(
+ final double interestRatePerPeriod, final Integer
rescheduleStrategyMethod) {
+ String futureInstallmentAllocationRule = "NEXT_INSTALLMENT";
+ AdvancedPaymentData defaultAllocation =
createDefaultPaymentAllocation(futureInstallmentAllocationRule);
+
+ return
createOnePeriod30DaysPeriodicAccrualProduct(interestRatePerPeriod) //
+
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy")//
+ .loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
+
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()) //
+
.addPaymentAllocationItem(defaultAllocation).enableDownPayment(false) //
+
.isInterestRecalculationEnabled(true).interestRecalculationCompoundingMethod(0)
//
+
.preClosureInterestCalculationStrategy(1).recalculationRestFrequencyType(1).allowPartialPeriodInterestCalcualtion(true)
//
+ .rescheduleStrategyMethod(rescheduleStrategyMethod);
+ }
+
private List<PaymentAllocationOrder>
getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
AtomicInteger integer = new AtomicInteger(1);
return Arrays.stream(paymentAllocationTypes).map(pat -> {