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 3e923bf48 FINERACT-2114: Interest rate modification, adjust reschedule 
validation
3e923bf48 is described below

commit 3e923bf483205f8bb1f6415d0247a87ce481baf7
Author: Janos Meszaros <[email protected]>
AuthorDate: Sat Aug 10 15:15:29 2024 +0200

    FINERACT-2114: Interest rate modification, adjust reschedule validation
---
 .../infrastructure/core/api/JsonCommand.java       |   4 +
 .../core/serialization/FromJsonHelper.java         |   7 ++
 .../core/serialization/JsonParserHelper.java       |  21 ++++
 .../core/serialization/JsonParserHelperTest.java   |  95 +++++++++++++++++
 .../portfolio/loanaccount/domain/Loan.java         |  15 +++
 .../RescheduleLoansApiConstants.java               |   5 +-
 .../data/LoanRescheduleRequestDataValidator.java   | 113 ++++++++++++++++-----
 ...nRescheduleRequestWritePlatformServiceImpl.java |  21 +---
 8 files changed, 236 insertions(+), 45 deletions(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
index 6cfeb4790..46f9f2ddb 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java
@@ -262,6 +262,10 @@ public final class JsonCommand {
         return parameterExists(parameterName);
     }
 
+    public boolean hasParameterValue(final String parameterName) {
+        return this.fromApiJsonHelper.parameterHasValue(parameterName, 
this.parsedCommand);
+    }
+
     public String dateFormat() {
         return stringValueOfParameterNamed("dateFormat");
     }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
index 239276d0b..5bd35d56d 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/FromJsonHelper.java
@@ -157,6 +157,13 @@ public class FromJsonHelper {
         return this.helperDelegator.parameterExists(parameterName, element);
     }
 
+    /**
+     * Check Parameter has a non-blank value
+     */
+    public boolean parameterHasValue(final String parameterName, final 
JsonElement element) {
+        return this.helperDelegator.parameterHasValue(parameterName, element);
+    }
+
     public String extractStringNamed(final String parameterName, final 
JsonElement element) {
         return this.helperDelegator.extractStringNamed(parameterName, element, 
new HashSet<String>());
     }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
index a3cd2bd41..e0573cd7c 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelper.java
@@ -61,6 +61,27 @@ public class JsonParserHelper {
         return element.getAsJsonObject().has(parameterName);
     }
 
+    /**
+     * Check Parameter has a non-blank value
+     */
+    public boolean parameterHasValue(final String parameterName, final 
JsonElement element) {
+        if (element == null || !element.isJsonObject()) {
+            return false;
+        }
+
+        var valueObject = element.getAsJsonObject().get(parameterName);
+        if (valueObject == null || valueObject.isJsonNull()) {
+            return false;
+        }
+        if (valueObject instanceof JsonArray) {
+            return !valueObject.getAsJsonArray().isEmpty();
+        }
+        if (valueObject instanceof JsonObject) {
+            return !valueObject.getAsJsonObject().isEmpty();
+        }
+        return valueObject.isJsonPrimitive() && 
!valueObject.getAsJsonPrimitive().getAsString().isBlank();
+    }
+
     public Boolean extractBooleanNamed(final String parameterName, final 
JsonElement element, final Set<String> requestParamatersDetected) {
         Boolean value = null;
         if (element.isJsonObject()) {
diff --git 
a/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelperTest.java
 
b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelperTest.java
new file mode 100644
index 000000000..3ed490d7e
--- /dev/null
+++ 
b/fineract-core/src/test/java/org/apache/fineract/infrastructure/core/serialization/JsonParserHelperTest.java
@@ -0,0 +1,95 @@
+/**
+ * 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.infrastructure.core.serialization;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class JsonParserHelperTest {
+
+    static final JsonParserHelper onTestUnit = new JsonParserHelper();
+
+    static final String testDataHasValues = "{\n" + "  \"integerNumber\": 
10,\n" + "  \"doubleNumber\": 3.14,\n"
+            + "  \"string\": \"example\",\n" + "  \"arrayList\": [1, \"two\", 
3.0],\n" + "  \"localDate\": \"2024-08-10\",\n"
+            + "  \"keyValue\": {\n" + "    \"key\": \"sampleKey\",\n" + "    
\"value\": \"sampleValue\"\n" + "  }\n" + "}";
+
+    static final String testDataValuesEmpty = "{\n" + "  \"integerNumber\": 
null,\n" + "  \"doubleNumber\": 0.0,\n"
+            + "  \"string\": \"\",\n" + "  \"arrayList\": [],\n" + "  
\"localDate\": null,\n" + "  \"keyValue\": {\n"
+            + "    \"key\": \"\",\n" + "    \"value\": null\n" + "  }\n" + "}";
+
+    static final String testDataValuesMissing = "{}";
+
+    @Test
+    void parameterExists() {
+        JsonElement jsonHasValues = JsonParser.parseString(testDataHasValues);
+        JsonElement jsonValuesEmpty = 
JsonParser.parseString(testDataValuesEmpty);
+        JsonElement jsonValuesMissing = 
JsonParser.parseString(testDataValuesMissing);
+
+        Assertions.assertTrue(onTestUnit.parameterExists("integerNumber", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterExists("doubleNumber", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterExists("string", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterExists("arrayList", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterExists("localDate", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterExists("keyValue", 
jsonHasValues));
+
+        Assertions.assertTrue(onTestUnit.parameterExists("integerNumber", 
jsonValuesEmpty));
+        Assertions.assertTrue(onTestUnit.parameterExists("doubleNumber", 
jsonValuesEmpty));
+        Assertions.assertTrue(onTestUnit.parameterExists("string", 
jsonValuesEmpty));
+        Assertions.assertTrue(onTestUnit.parameterExists("arrayList", 
jsonValuesEmpty));
+        Assertions.assertTrue(onTestUnit.parameterExists("localDate", 
jsonValuesEmpty));
+        Assertions.assertTrue(onTestUnit.parameterExists("keyValue", 
jsonValuesEmpty));
+
+        Assertions.assertFalse(onTestUnit.parameterExists("integerNumber", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterExists("doubleNumber", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterExists("string", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterExists("arrayList", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterExists("localDate", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterExists("keyValue", 
jsonValuesMissing));
+    }
+
+    @Test
+    void parameterHasValue() {
+        JsonElement jsonHasValues = JsonParser.parseString(testDataHasValues);
+        JsonElement jsonValuesEmpty = 
JsonParser.parseString(testDataValuesEmpty);
+        JsonElement jsonValuesMissing = 
JsonParser.parseString(testDataValuesMissing);
+
+        Assertions.assertTrue(onTestUnit.parameterHasValue("integerNumber", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterHasValue("doubleNumber", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterHasValue("string", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterHasValue("arrayList", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterHasValue("localDate", 
jsonHasValues));
+        Assertions.assertTrue(onTestUnit.parameterHasValue("keyValue", 
jsonHasValues));
+
+        Assertions.assertFalse(onTestUnit.parameterHasValue("integerNumber", 
jsonValuesEmpty));
+        Assertions.assertTrue(onTestUnit.parameterHasValue("doubleNumber", 
jsonValuesEmpty));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("string", 
jsonValuesEmpty));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("arrayList", 
jsonValuesEmpty));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("localDate", 
jsonValuesEmpty));
+        Assertions.assertTrue(onTestUnit.parameterHasValue("keyValue", 
jsonValuesEmpty));
+
+        Assertions.assertFalse(onTestUnit.parameterHasValue("integerNumber", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("doubleNumber", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("string", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("arrayList", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("localDate", 
jsonValuesMissing));
+        Assertions.assertFalse(onTestUnit.parameterHasValue("keyValue", 
jsonValuesMissing));
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 80fb57c04..008f9537e 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -4566,6 +4566,21 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom<Long> {
         return installment;
     }
 
+    /**
+     * @param date
+     * @return a schedule installment is related to the provided date
+     **/
+    public LoanRepaymentScheduleInstallment 
getRelatedRepaymentScheduleInstallment(LocalDate date) {
+        if (date == null) {
+            return null;
+        }
+        return getRepaymentScheduleInstallments()//
+                .stream()//
+                .filter(installment -> date.isAfter(installment.getFromDate()) 
&& !date.isAfter(installment.getDueDate()))//
+                .findAny()//
+                .orElse(null);//
+    }
+
     /**
      * @return loan disbursement data
      **/
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
index 87f4ebbb0..eb0e41045 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java
@@ -50,8 +50,9 @@ public final class RescheduleLoansApiConstants {
     public static final String rescheduleReasonCommentParamName = 
"rescheduleReasonComment";
     public static final String submittedOnDateParamName = "submittedOnDate";
     public static final String adjustedDueDateParamName = "adjustedDueDate";
-    public static final String 
resheduleForMultiDisbursementNotSupportedErrorCode = 
"loan.reschedule.tranche.multidisbursement.error.code";
-    public static final String 
resheduleWithInterestRecalculationNotSupportedErrorCode = 
"loan.reschedule.interestrecalculation.error.code";
+    public static final String 
rescheduleForMultiDisbursementNotSupportedErrorCode = 
"loan.reschedule.tranche.multidisbursement.error.code";
+    public static final String 
rescheduleMultipleOperationsNotSupportedErrorCode = 
"loan.reschedule.multioperations.error.code";
+    public static final String 
rescheduleSelectedOperationNotSupportedErrorCode = 
"loan.reschedule.selectedoperationnotsupported.error.code";
     public static final String allCommandParamName = "all";
     public static final String approveCommandParamName = "approve";
     public static final String pendingCommandParamName = "pending";
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 7f6f57abf..531225711 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
@@ -42,6 +42,7 @@ 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;
@@ -140,11 +141,77 @@ public class LoanRescheduleRequestDataValidator {
         
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment)
                 .ignoreIfNull().notExceedingLengthOf(500);
 
-        if 
(this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName, 
jsonElement)
-                || 
this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.endDateParamName,
 jsonElement)) {
+        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();
 
@@ -156,15 +223,6 @@ public class LoanRescheduleRequestDataValidator {
                             
.failWithCode("repayment.schedule.installment.does.not.exist", "Repayment 
schedule installment does not exist");
                 }
             }
-
-        }
-
-        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");
         }
 
         // at least one of the following must be provided => graceOnPrincipal,
@@ -177,37 +235,38 @@ public class LoanRescheduleRequestDataValidator {
                 && 
!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.emiParamName, 
jsonElement)) {
             
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull();
         }
-        LoanRepaymentScheduleInstallment installment = null;
-        if (rescheduleFromDate != null) {
-            installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
 
-            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");
-            }
+        final LoanRepaymentScheduleInstallment installment = 
loan.getRepaymentScheduleInstallment(rescheduleFromDate);
 
+        if (rescheduleFromDate != null) {
+            validateReschedulingInstallment(dataValidatorBuilder, installment);
         }
 
         if (loan.isMultiDisburmentLoan()) {
             if (!loan.loanProduct().isDisallowExpectedDisbursements()) {
                 
dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(
-                        
RescheduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCode,
+                        
RescheduleLoansApiConstants.rescheduleForMultiDisbursementNotSupportedErrorCode,
                         "Loan rescheduling is not supported for 
multidisbursement tranche loans");
             }
         }
 
         validateForOverdueCharges(dataValidatorBuilder, loan, installment);
-        if (!dataValidationErrors.isEmpty()) {
-            throw new PlatformApiDataValidationException(dataValidationErrors);
+    }
+
+    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(DataValidatorBuilder 
dataValidatorBuilder, final Loan loan,
+    private void validateForOverdueCharges(final DataValidatorBuilder 
dataValidatorBuilder, final Loan loan,
             final LoanRepaymentScheduleInstallment installment) {
         if (installment != null) {
             LocalDate rescheduleFromDate = installment.getFromDate();
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 266514dac..751c3d41f 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
@@ -195,29 +195,18 @@ public class 
LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
                 submittedOnDate = 
jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.submittedOnDateParamName);
             }
 
-            // initially set the value to null
-            LocalDate rescheduleFromDate = null;
-
             // start point of the rescheduling exercise
             Integer rescheduleFromInstallment = null;
 
             // initially set the value to null
             LocalDate adjustedDueDate = null;
 
+            LocalDate rescheduleFromDate = jsonCommand
+                    
.localDateValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName);
             // check if the parameter is in the JsonCommand object
-            if 
(jsonCommand.hasParameter(RescheduleLoansApiConstants.rescheduleFromDateParamName))
 {
-                // create a LocalDate object from the "rescheduleFromDate" Date
-                // string
-                LocalDate localDate = 
jsonCommand.localDateValueOfParameterNamed(RescheduleLoansApiConstants.rescheduleFromDateParamName);
-
-                if (localDate != null) {
-                    // get installment by due date
-                    LoanRepaymentScheduleInstallment installment = 
loan.getRepaymentScheduleInstallment(localDate);
-                    rescheduleFromInstallment = 
installment.getInstallmentNumber();
-
-                    // update the value of the "rescheduleFromDate" variable
-                    rescheduleFromDate = localDate;
-                }
+            if (rescheduleFromDate != null) {
+                // get installment by due date
+                rescheduleFromInstallment = 
loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate).getInstallmentNumber();
             }
 
             if 
(jsonCommand.hasParameter(RescheduleLoansApiConstants.adjustedDueDateParamName))
 {

Reply via email to