This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch release/1.13.1
in repository https://gitbox.apache.org/repos/asf/fineract.git

commit 1e8fa1099c5269fd4ca6ccf36d74921f82cce49c
Author: Oleksii Novikov <[email protected]>
AuthorDate: Tue Oct 21 15:41:36 2025 +0300

    FINERACT-2389: Fix the handling of nullable field overrides
---
 .../test/data/loanproduct/DefaultLoanProduct.java  |   2 +
 .../global/LoanProductGlobalInitializerStep.java   |  49 +++++++++
 .../stepdef/loan/LoanOverrideFieldsStepDef.java    | 120 +++++++++++++++++++++
 .../fineract/test/support/TestContextKey.java      |   2 +
 .../resources/features/LoanOverrideFileds.feature  |  58 ++++++++++
 .../loanaccount/api/LoansApiResourceSwagger.java   |  10 ++
 .../service/LoanScheduleAssembler.java             |  30 +++---
 .../integrationtests/BaseLoanIntegrationTest.java  |  30 ++++--
 .../DelinquencyActionIntegrationTests.java         |  10 +-
 .../common/loans/LoanProductTestBuilder.java       |   4 +-
 .../InitiateExternalAssetOwnerTransferTest.java    |   4 +-
 11 files changed, 290 insertions(+), 29 deletions(-)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
index 1fe4813d90..851365f5a5 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java
@@ -170,6 +170,8 @@ public enum DefaultLoanProduct implements LoanProduct {
     LP1_INTEREST_FLAT_DAILY_RECALCULATION_DAILY_360_30_MULTIDISB, //
     
LP1_INTEREST_FLAT_SAR_RECALCULATION_SAME_AS_REPAYMENT_MULTIDISB_AUTO_DOWNPAYMENT,
 //
     
LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY,
 //
+    LP1_WITH_OVERRIDES, //
+    LP1_NO_OVERRIDES //
     ;
 
     @Override
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
index 72f6100135..f9251b541b 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java
@@ -36,6 +36,7 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import lombok.RequiredArgsConstructor;
 import org.apache.fineract.client.models.AdvancedPaymentData;
+import org.apache.fineract.client.models.AllowAttributeOverrides;
 import org.apache.fineract.client.models.CreditAllocationData;
 import org.apache.fineract.client.models.CreditAllocationOrder;
 import org.apache.fineract.client.models.LoanProductChargeData;
@@ -4056,6 +4057,54 @@ public class LoanProductGlobalInitializerStep implements 
FineractGlobalInitializ
         TestContext.INSTANCE.set(
                 
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY,
                 
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmi36030InterestRecalculationDaily);
+
+        // (LP1_WITH_OVERRIDES) - Loan product with all attribute overrides 
ENABLED
+        final String nameWithOverrides = 
DefaultLoanProduct.LP1_WITH_OVERRIDES.getName();
+        final PostLoanProductsRequest loanProductsRequestWithOverrides = 
loanProductsRequestFactory.defaultLoanProductsRequestLP1() //
+                .name(nameWithOverrides) //
+                .interestRatePerPeriod(1.0) //
+                .maxInterestRatePerPeriod(30.0) //
+                .inArrearsTolerance(10) //
+                .graceOnPrincipalPayment(1) //
+                .graceOnInterestPayment(1) //
+                .graceOnArrearsAgeing(3) //
+                .numberOfRepayments(6) //
+                .allowAttributeOverrides(new AllowAttributeOverrides() //
+                        .amortizationType(true) //
+                        .interestType(true) //
+                        .transactionProcessingStrategyCode(true) //
+                        .interestCalculationPeriodType(true) //
+                        .inArrearsTolerance(true) //
+                        .repaymentEvery(true) //
+                        .graceOnPrincipalAndInterestPayment(true) //
+                        .graceOnArrearsAgeing(true));
+        final Response<PostLoanProductsResponse> responseWithOverrides = 
loanProductsApi.createLoanProduct(loanProductsRequestWithOverrides)
+                .execute();
+        
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_WITH_OVERRIDES,
 responseWithOverrides);
+
+        // (LP1_NO_OVERRIDES) - Loan product with all attribute overrides 
DISABLED
+        final String nameNoOverrides = 
DefaultLoanProduct.LP1_NO_OVERRIDES.getName();
+        final PostLoanProductsRequest loanProductsRequestNoOverrides = 
loanProductsRequestFactory.defaultLoanProductsRequestLP1() //
+                .name(nameNoOverrides) //
+                .interestRatePerPeriod(1.0) //
+                .maxInterestRatePerPeriod(30.0) //
+                .inArrearsTolerance(10) //
+                .graceOnPrincipalPayment(1) //
+                .graceOnInterestPayment(1) //
+                .graceOnArrearsAgeing(3) //
+                .numberOfRepayments(6) //
+                .allowAttributeOverrides(new AllowAttributeOverrides() //
+                        .amortizationType(false) //
+                        .interestType(false) //
+                        .transactionProcessingStrategyCode(false) //
+                        .interestCalculationPeriodType(false) //
+                        .inArrearsTolerance(false) //
+                        .repaymentEvery(false) //
+                        .graceOnPrincipalAndInterestPayment(false) //
+                        .graceOnArrearsAgeing(false));
+        final Response<PostLoanProductsResponse> responseNoOverrides = 
loanProductsApi.createLoanProduct(loanProductsRequestNoOverrides)
+                .execute();
+        
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_NO_OVERRIDES,
 responseNoOverrides);
     }
 
     public static AdvancedPaymentData createPaymentAllocation(String 
transactionType, String futureInstallmentAllocationRule,
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOverrideFieldsStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOverrideFieldsStepDef.java
new file mode 100644
index 0000000000..4f8185c9bb
--- /dev/null
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanOverrideFieldsStepDef.java
@@ -0,0 +1,120 @@
+/**
+ * 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.test.stepdef.loan;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.cucumber.datatable.DataTable;
+import io.cucumber.java.en.Then;
+import io.cucumber.java.en.When;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.client.models.GetLoansLoanIdResponse;
+import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostLoansRequest;
+import org.apache.fineract.client.models.PostLoansResponse;
+import org.apache.fineract.client.services.LoansApi;
+import org.apache.fineract.test.data.loanproduct.DefaultLoanProduct;
+import org.apache.fineract.test.data.loanproduct.LoanProductResolver;
+import org.apache.fineract.test.factory.LoanRequestFactory;
+import org.apache.fineract.test.helper.ErrorHelper;
+import org.apache.fineract.test.stepdef.AbstractStepDef;
+import org.apache.fineract.test.support.TestContextKey;
+import retrofit2.Response;
+
+@RequiredArgsConstructor
+public class LoanOverrideFieldsStepDef extends AbstractStepDef {
+
+    private final LoanRequestFactory loanRequestFactory;
+    private final LoanProductResolver loanProductResolver;
+    private final LoansApi loansApi;
+
+    @Then("LoanDetails has {string} field with value: {string}")
+    public void checkLoanDetailsFieldWithValue(final String fieldName, final 
String expectedValue) throws IOException {
+        final Response<PostLoansResponse> loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        assertNotNull(loanResponse.body());
+        final Long loanId = loanResponse.body().getLoanId();
+
+        final Response<GetLoansLoanIdResponse> loanDetails = 
loansApi.retrieveLoan(loanId, false, "", "", "").execute();
+        ErrorHelper.checkSuccessfulApiCall(loanDetails);
+        assertNotNull(loanDetails.body());
+
+        verifyFieldValue(loanDetails.body(), fieldName, expectedValue);
+    }
+
+    private void verifyFieldValue(final GetLoansLoanIdResponse loanDetails, 
final String fieldName, final String expectedValue) {
+        final Integer actualValue = getIntFieldValue(loanDetails, fieldName);
+        final Integer expected = Integer.valueOf(expectedValue);
+        assertThat(actualValue).as("Expected %s to be %d but was %s", 
fieldName, expected, actualValue).isEqualTo(expected);
+    }
+
+    private Integer getIntFieldValue(final GetLoansLoanIdResponse loanDetails, 
final String fieldName) {
+        return switch (fieldName) {
+            case "inArrearsTolerance" -> loanDetails.getInArrearsTolerance();
+            case "graceOnPrincipalPayment" -> 
loanDetails.getGraceOnPrincipalPayment();
+            case "graceOnInterestPayment" -> 
loanDetails.getGraceOnInterestPayment();
+            case "graceOnArrearsAgeing" -> 
loanDetails.getGraceOnArrearsAgeing();
+            default -> throw new IllegalArgumentException("Unknown override 
field: " + fieldName);
+        };
+    }
+
+    @When("Admin creates a new Loan with the following override data:")
+    public void createLoanWithOverrideData(final DataTable dataTable) throws 
IOException {
+        final Response<PostClientsResponse> clientResponse = 
testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE);
+        assertNotNull(clientResponse.body());
+        final Long clientId = clientResponse.body().getClientId();
+
+        final Map<String, String> overrideData = dataTable.asMap(String.class, 
String.class);
+
+        final String loanProductName = overrideData.get("loanProduct");
+        if (loanProductName == null) {
+            throw new IllegalArgumentException("loanProduct is required in 
override data");
+        }
+
+        final PostLoansRequest loansRequest = 
loanRequestFactory.defaultLoansRequest(clientId)
+                
.productId(loanProductResolver.resolve(DefaultLoanProduct.valueOf(loanProductName))).numberOfRepayments(6)
+                .loanTermFrequency(180).interestRatePerPeriod(new 
BigDecimal(1));
+
+        overrideData.forEach((fieldName, value) -> {
+            if (!"loanProduct".equals(fieldName)) {
+                applyOverrideField(loansRequest, fieldName, value);
+            }
+        });
+
+        final Response<PostLoansResponse> response = 
loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, 
"").execute();
+        testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response);
+        ErrorHelper.checkSuccessfulApiCall(response);
+    }
+
+    private void applyOverrideField(final PostLoansRequest request, final 
String fieldName, final String value) {
+        final boolean isNull = "null".equals(value);
+
+        switch (fieldName) {
+            case "inArrearsTolerance" -> request.inArrearsTolerance(isNull ? 
null : new BigDecimal(value));
+            case "graceOnInterestPayment" -> 
request.graceOnInterestPayment(isNull ? null : Integer.valueOf(value));
+            case "graceOnPrincipalPayment" -> 
request.graceOnPrincipalPayment(isNull ? null : Integer.valueOf(value));
+            case "graceOnArrearsAgeing" -> request.graceOnArrearsAgeing(isNull 
? null : Integer.valueOf(value));
+            default -> throw new IllegalArgumentException("Unknown override 
field: " + fieldName);
+        }
+    }
+
+}
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
index 37c72d2d34..0f56c55464 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java
@@ -275,4 +275,6 @@ public abstract class TestContextKey {
     public static final String 
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_INTEREST_RECALC_360_30_MULTIDISB_OVER_APPLIED_EXPECTED_TRANCHES
 = 
"loanProductCreateResponseLP2ProgressiveAdvancedPaymentInterestRecalculationMultidisbursalApprovedOverAppliedAmountExpectedTransches";
     public static final String 
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_MIN_INT_3_MAX_INT_20
 = 
"loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationDailyTillPreCloseMinInt3MaxInt20";
     public static final String 
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_PROGRESSIVE_ADV_PYMNT_WRITE_OFF_REASON_MAP
 = "loanProductCreateResponseLP2ProgressiveAdvancedPaymentWriteOffReasonMap";
+    public static final String 
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_WITH_OVERRIDES = 
"loanProductCreateResponseLP1WithOverrides";
+    public static final String 
DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_NO_OVERRIDES = 
"loanProductCreateResponseLP1NoOverrides";
 }
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanOverrideFileds.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanOverrideFileds.feature
new file mode 100644
index 0000000000..b955d0f272
--- /dev/null
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanOverrideFileds.feature
@@ -0,0 +1,58 @@
+@LoanOverrideFields
+Feature: LoanOverrideFields
+
+  Scenario: Verify that all nullable fields default to product when overrides 
not allowed and not provided
+    When Admin sets the business date to the actual date
+    When Admin creates a client with random data
+    When Admin creates a new Loan with the following override data:
+      | loanProduct             | LP1_NO_OVERRIDES |
+      | inArrearsTolerance      | null             |
+      | graceOnPrincipalPayment | null             |
+      | graceOnInterestPayment  | null             |
+      | graceOnArrearsAgeing    | null             |
+    Then LoanDetails has "inArrearsTolerance" field with value: "10"
+    Then LoanDetails has "graceOnPrincipalPayment" field with value: "1"
+    Then LoanDetails has "graceOnInterestPayment" field with value: "1"
+    Then LoanDetails has "graceOnArrearsAgeing" field with value: "3"
+
+  Scenario: Verify that all nullable fields ignore overrides when overrides 
not allowed
+    When Admin sets the business date to the actual date
+    When Admin creates a client with random data
+    When Admin creates a new Loan with the following override data:
+      | loanProduct             | LP1_NO_OVERRIDES |
+      | inArrearsTolerance      | 11               |
+      | graceOnPrincipalPayment | 2                |
+      | graceOnInterestPayment  | 2                |
+      | graceOnArrearsAgeing    | 4                |
+    Then LoanDetails has "inArrearsTolerance" field with value: "10"
+    Then LoanDetails has "graceOnPrincipalPayment" field with value: "1"
+    Then LoanDetails has "graceOnInterestPayment" field with value: "1"
+    Then LoanDetails has "graceOnArrearsAgeing" field with value: "3"
+
+  Scenario: Verify that nullable fields default to product when override is 
allowed but not provided
+    When Admin sets the business date to the actual date
+    When Admin creates a client with random data
+    When Admin creates a new Loan with the following override data:
+      | loanProduct             | LP1_WITH_OVERRIDES |
+      | inArrearsTolerance      | null               |
+      | graceOnPrincipalPayment | null               |
+      | graceOnInterestPayment  | null               |
+      | graceOnArrearsAgeing    | null               |
+    Then LoanDetails has "inArrearsTolerance" field with value: "10"
+    Then LoanDetails has "graceOnPrincipalPayment" field with value: "1"
+    Then LoanDetails has "graceOnInterestPayment" field with value: "1"
+    Then LoanDetails has "graceOnArrearsAgeing" field with value: "3"
+
+  Scenario: Verify that nullable fields default to product when override is 
allowed and provided
+    When Admin sets the business date to the actual date
+    When Admin creates a client with random data
+    When Admin creates a new Loan with the following override data:
+      | loanProduct             | LP1_WITH_OVERRIDES |
+      | inArrearsTolerance      | 11                 |
+      | graceOnPrincipalPayment | 2                  |
+      | graceOnInterestPayment  | 2                  |
+      | graceOnArrearsAgeing    | 4                  |
+    Then LoanDetails has "inArrearsTolerance" field with value: "11"
+    Then LoanDetails has "graceOnPrincipalPayment" field with value: "2"
+    Then LoanDetails has "graceOnInterestPayment" field with value: "2"
+    Then LoanDetails has "graceOnArrearsAgeing" field with value: "4"
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index 3ca327b710..40d97e3fc4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -1216,6 +1216,14 @@ final class LoansApiResourceSwagger {
         public Boolean chargedOff;
         @Schema(example = "3")
         public Integer inArrearsTolerance;
+        @Schema(example = "0")
+        public Integer graceOnPrincipalPayment;
+        @Schema(example = "0")
+        public Integer graceOnInterestPayment;
+        @Schema(example = "0")
+        public Integer graceOnInterestCharged;
+        @Schema(example = "3")
+        public Integer graceOnArrearsAgeing;
         @Schema(example = "false")
         public Boolean enableDownPayment;
         @Schema(example = "0.000000")
@@ -1348,6 +1356,8 @@ final class LoansApiResourceSwagger {
         public Integer graceOnInterestPayment;
         @Schema(example = "1")
         public Integer graceOnArrearsAgeing;
+        @Schema(example = "10")
+        public BigDecimal inArrearsTolerance;
         @Schema(example = "HORIZONTAL")
         public String loanScheduleProcessingType;
         @Schema(example = "false")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
index c0742533d9..bb303f52b6 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java
@@ -350,27 +350,33 @@ public class LoanScheduleAssembler {
         }
 
         // grace details
-        final Integer graceOnPrincipalPayment = 
allowOverridingGraceOnPrincipalAndInterestPayment
-                ? 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", 
element)
-                : 
loanProduct.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
+        Integer graceOnPrincipalPayment = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", 
element);
+        if (!allowOverridingGraceOnPrincipalAndInterestPayment || 
graceOnPrincipalPayment == null) {
+            graceOnPrincipalPayment = 
loanProduct.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
+        }
         final Integer recurringMoratoriumOnPrincipalPeriods = 
this.fromApiJsonHelper
                 
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", 
element);
-        final Integer graceOnInterestPayment = 
allowOverridingGraceOnPrincipalAndInterestPayment
-                ? 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", 
element)
-                : 
loanProduct.getLoanProductRelatedDetail().getGraceOnInterestPayment();
+        Integer graceOnInterestPayment = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", 
element);
+        if (!allowOverridingGraceOnPrincipalAndInterestPayment || 
graceOnInterestPayment == null) {
+            graceOnInterestPayment = 
loanProduct.getLoanProductRelatedDetail().getGraceOnInterestPayment();
+        }
         final Integer graceOnInterestCharged = 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", 
element);
         final LocalDate interestChargedFromDate = 
this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", 
element);
         final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = 
this.configurationDomainService
                 .isInterestChargedFromDateSameAsDisbursementDate();
 
-        final Integer graceOnArrearsAgeing = allowOverridingGraceOnArrearsAging
-                ? 
this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
 element)
-                : 
loanProduct.getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
+        Integer graceOnArrearsAgeing = this.fromApiJsonHelper
+                
.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
 element);
+        if (!allowOverridingGraceOnArrearsAging || graceOnArrearsAgeing == 
null) {
+            graceOnArrearsAgeing = 
loanProduct.getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
+        }
 
         // other
-        final BigDecimal inArrearsTolerance = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", 
element);
-        final Money inArrearsToleranceMoney = allowOverridingArrearsTolerance 
? Money.of(currency, inArrearsTolerance)
-                : 
loanProduct.getLoanProductRelatedDetail().getInArrearsTolerance();
+        BigDecimal inArrearsTolerance = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", 
element);
+        if (!allowOverridingArrearsTolerance || inArrearsTolerance == null) {
+            inArrearsTolerance = 
loanProduct.getLoanProductRelatedDetail().getInArrearsTolerance().getAmount();
+        }
+        final Money inArrearsToleranceMoney = Money.of(currency, 
inArrearsTolerance);
 
         final BigDecimal emiAmount = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
                 element);
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 ba07af2d60..08bacb5170 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
@@ -886,7 +886,8 @@ public abstract class BaseLoanIntegrationTest extends 
IntegrationTest {
                 .repaymentEvery(1)//
                 
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
                 .interestType(interestType)//
-                .amortizationType(amortizationType);
+                .amortizationType(amortizationType)//
+                .graceOnArrearsAgeing(0);
     }
 
     private RequestSpecification createRequestSpecification(String authKey) {
@@ -1350,13 +1351,26 @@ public abstract class BaseLoanIntegrationTest extends 
IntegrationTest {
     protected PostLoansRequest applyLoanRequest(Long clientId, Long 
loanProductId, String loanDisbursementDate, Double amount,
             int numberOfRepayments, Consumer<PostLoansRequest> customizer) {
 
-        PostLoansRequest postLoansRequest = new 
PostLoansRequest().clientId(clientId).productId(loanProductId)
-                
.expectedDisbursementDate(loanDisbursementDate).dateFormat(DATETIME_PATTERN)
-                
.transactionProcessingStrategyCode(DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY)
-                
.locale("en").submittedOnDate(loanDisbursementDate).amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO)
-                
.interestCalculationPeriodType(1).interestType(0).repaymentEvery(30).repaymentFrequencyType(0)
-                
.numberOfRepayments(numberOfRepayments).loanTermFrequency(numberOfRepayments * 
30).loanTermFrequencyType(0)
-                
.maxOutstandingLoanBalance(BigDecimal.valueOf(amount)).principal(BigDecimal.valueOf(amount)).loanType("individual");
+        PostLoansRequest postLoansRequest = new 
PostLoansRequest().clientId(clientId) //
+                .productId(loanProductId) //
+                .expectedDisbursementDate(loanDisbursementDate) //
+                .dateFormat(DATETIME_PATTERN) //
+                
.transactionProcessingStrategyCode(DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY)
 //
+                .locale("en") //
+                .submittedOnDate(loanDisbursementDate) //
+                .amortizationType(1) //
+                .interestRatePerPeriod(BigDecimal.ZERO) //
+                .interestCalculationPeriodType(1) //
+                .interestType(0) //
+                .repaymentEvery(30) //
+                .repaymentFrequencyType(0) //
+                .numberOfRepayments(numberOfRepayments) //
+                .loanTermFrequency(numberOfRepayments * 30) //
+                .loanTermFrequencyType(0) //
+                .maxOutstandingLoanBalance(BigDecimal.valueOf(amount)) //
+                .principal(BigDecimal.valueOf(amount)) //
+                .loanType("individual") //
+                .graceOnArrearsAgeing(0);
         if (customizer != null) {
             customizer.accept(postLoansRequest);
         }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
index 168a029c8b..12be1252a1 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyActionIntegrationTests.java
@@ -285,15 +285,15 @@ public class DelinquencyActionIntegrationTests extends 
BaseLoanIntegrationTest {
             verifyLoanDelinquencyData(loanId, 6, new 
InstallmentDelinquencyData(4, 10, BigDecimal.valueOf(250.0)));
 
             // Create Delinquency Pause for the Loan in the past
-            PostLoansDelinquencyActionResponse response = 
loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE,
-                    "27 January 2023", "15 February 2023");
+            loanTransactionHelper.createLoanDelinquencyAction(loanId, PAUSE, 
"27 January 2023", "15 February 2023");
 
             List<GetDelinquencyActionsResponse> loanDelinquencyActions = 
loanTransactionHelper.getLoanDelinquencyActions(loanId);
             Assertions.assertNotNull(loanDelinquencyActions);
             Assertions.assertEquals(1, loanDelinquencyActions.size());
-            Assertions.assertEquals("PAUSE", 
loanDelinquencyActions.get(0).getAction());
-            Assertions.assertEquals(LocalDate.parse("27 January 2023", 
dateTimeFormatter), loanDelinquencyActions.get(0).getStartDate());
-            Assertions.assertEquals(LocalDate.parse("15 February 2023", 
dateTimeFormatter), loanDelinquencyActions.get(0).getEndDate());
+            Assertions.assertEquals("PAUSE", 
loanDelinquencyActions.getFirst().getAction());
+            Assertions.assertEquals(LocalDate.parse("27 January 2023", 
dateTimeFormatter),
+                    loanDelinquencyActions.getFirst().getStartDate());
+            Assertions.assertEquals(LocalDate.parse("15 February 2023", 
dateTimeFormatter), loanDelinquencyActions.getFirst().getEndDate());
 
             // Loan delinquency data calculation after backdated pause
             verifyLoanDelinquencyData(loanId, 3, new 
InstallmentDelinquencyData(1, 3, BigDecimal.valueOf(250.0)));
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
index 0ae33fa693..8ae4bcc1df 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java
@@ -137,8 +137,8 @@ public class LoanProductTestBuilder {
     private String minimumGuaranteeFromOwnFunds = null;
     private String minimumGuaranteeFromGuarantor = null;
     private String isArrearsBasedOnOriginalSchedule = null;
-    private String graceOnPrincipalPayment = "1";
-    private String graceOnInterestPayment = "1";
+    private String graceOnPrincipalPayment = null;
+    private String graceOnInterestPayment = null;
     private JsonObject allowAttributeOverrides = null;
     private Boolean allowPartialPeriodInterestCalcualtion = false;
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
index 7848cf6adf..4b7f3f92c3 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/investor/externalassetowner/InitiateExternalAssetOwnerTransferTest.java
@@ -1368,8 +1368,8 @@ public class InitiateExternalAssetOwnerTransferTest 
extends BaseLoanIntegrationT
                 
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("4").withRepaymentEveryAfter("1")
                 
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments()
                 
.withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
-                
.withExpectedDisbursementDate(date).withSubmittedOnDate(date).withCollaterals(collaterals)
-                .build(clientID, loanProductID, null);
+                
.withExpectedDisbursementDate(date).withSubmittedOnDate(date).withCollaterals(collaterals).withInArrearsTolerance("0")
+                
.withPrincipalGrace("0").withInterestGrace("0").build(clientID, loanProductID, 
null);
         return LOAN_TRANSACTION_HELPER.getLoanId(loanApplicationJSON);
     }
 

Reply via email to