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 -> {

Reply via email to