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 33a584bbc FINERACT-1971: Override enableInstallmentLevelDelinquency 
loan application
33a584bbc is described below

commit 33a584bbcde245a0ba6e3ec722360fa71ac4cd0f
Author: Ruchi Dhamankar <[email protected]>
AuthorDate: Tue May 7 15:00:37 2024 +0530

    FINERACT-1971: Override enableInstallmentLevelDelinquency loan application
---
 .../loanaccount/api/LoansApiResourceSwagger.java   |   2 +
 .../LoanApplicationCommandFromApiJsonHelper.java   |  19 ++-
 .../loanaccount/service/LoanAssembler.java         |   8 +-
 ...allmentLevelDelinquencyAPIIntegrationTests.java | 168 +++++++++++++++++++++
 4 files changed, 195 insertions(+), 2 deletions(-)

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 67002b0d9..ab434a921 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
@@ -1223,6 +1223,8 @@ final class LoansApiResourceSwagger {
         public Integer graceOnArrearsAgeing;
         @Schema(example = "HORIZONTAL")
         public String loanScheduleProcessingType;
+        @Schema(example = "false")
+        public Boolean enableInstallmentLevelDelinquency;
     }
 
     @Schema(description = "PostLoansResponse")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
index 2c1251f49..941dc8d1a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java
@@ -104,7 +104,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
             LoanApiConstants.lastApplication, // glim specific
             LoanApiConstants.daysInYearTypeParameterName, 
LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
             LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, 
LoanApiConstants.FRAUD_ATTRIBUTE_NAME,
-            LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, 
LoanProductConstants.FIXED_LENGTH));
+            LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, 
LoanProductConstants.FIXED_LENGTH,
+            LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY));
     public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
 
     private final FromJsonHelper fromApiJsonHelper;
@@ -581,6 +582,22 @@ public final class LoanApplicationCommandFromApiJsonHelper 
{
         }
 
         validatePartialPeriodSupport(interestCalculationPeriodType, 
baseDataValidator, element, loanProduct);
+
+        // validate enable installment level delinquency
+        if 
(this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
 element)) {
+            final Boolean isEnableInstallmentLevelDelinquency = 
this.fromApiJsonHelper
+                    
.extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, 
element);
+            
baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY)
+                    
.value(isEnableInstallmentLevelDelinquency).validateForBooleanValue();
+            if (loanProduct.getDelinquencyBucket() == null) {
+                if (isEnableInstallmentLevelDelinquency) {
+                    
baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY).failWithCode(
+                            
"can.be.enabled.for.loan.with.loan.product.having.valid.delinquency.bucket",
+                            "Installment level delinquency cannot be enabled 
for a loan if Delinquency bucket is not configured for loan product");
+                }
+            }
+        }
+
         if (!dataValidationErrors.isEmpty()) {
             throw new PlatformApiDataValidationException(dataValidationErrors);
         }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
index 36b9e86de..708155759 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java
@@ -327,7 +327,13 @@ public class LoanAssembler {
             }
         }
 
-        
loanApplication.updateEnableInstallmentLevelDelinquency(loanProduct.isEnableInstallmentLevelDelinquency());
+        final Boolean isEnableInstallmentLevelDelinquency = 
this.fromApiJsonHelper
+                
.extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, 
element);
+        if (isEnableInstallmentLevelDelinquency != null) {
+            
loanApplication.updateEnableInstallmentLevelDelinquency(isEnableInstallmentLevelDelinquency);
+        } else {
+            
loanApplication.updateEnableInstallmentLevelDelinquency(loanProduct.isEnableInstallmentLevelDelinquency());
+        }
 
         final LoanApplicationTerms loanApplicationTerms = 
this.loanScheduleAssembler.assembleLoanTerms(element);
         final boolean isHolidayEnabled = 
this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java
index fb9183169..d6ed9adc7 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java
@@ -31,10 +31,12 @@ import 
org.apache.fineract.client.models.GetLoansLoanIdLoanInstallmentLevelDelin
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.PostLoanProductsRequest;
 import org.apache.fineract.client.models.PostLoanProductsResponse;
+import org.apache.fineract.client.util.CallFailedRuntimeException;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
 import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
 import 
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
@@ -242,6 +244,172 @@ public class 
InstallmentLevelDelinquencyAPIIntegrationTests extends BaseLoanInte
         });
     }
 
+    @Test
+    public void 
testInstallmentLevelDelinquencyTurnedOnForProductAndOffForLoan() {
+        runAt("31 May 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            // Create DelinquencyBuckets
+            Integer delinquencyBucketId = 
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, 
List.of(//
+                    Pair.of(1, 10), //
+                    Pair.of(11, 30), //
+                    Pair.of(31, 60), //
+                    Pair.of(61, null)//
+            ));
+
+            // Create Loan Product
+            PostLoanProductsRequest loanProductsRequest = 
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+                    InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+            // set installment level delinquency as true
+            loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
+            
loanProductsRequest.setDelinquencyBucketId(delinquencyBucketId.longValue());
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            // Apply and Approve Loan, turn loan level installment delinquency 
as false
+            Long loanId = applyAndApproveLoan(clientId, 
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4,
+                    req -> req.setEnableInstallmentLevelDelinquency(false));
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+            // Verify Repayment Schedule and Due Dates
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250.0, null, "01 January 2023"), //
+                    installment(312.0, false, "31 January 2023"), // 120 days 
delinquent -> range4
+                    installment(312.0, false, "02 March 2023"), // 90 days 
delinquent -> range4
+                    installment(312.0, false, "01 April 2023"), // 60 days 
delinquent -> range3
+                    installment(314.0, false, "01 May 2023") // 30 days 
delinquent -> range2
+            );
+
+            // since the installment level delinquency is overridden and set 
as false for loan application, therefore it
+            // is not calculated
+            verifyDelinquency(loanId, 120, "1250.0");
+        });
+
+    }
+
+    @Test
+    public void 
testInstallmentLevelDelinquencyTurnedOffForProductAndOnForLoan() {
+        runAt("31 May 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            // Create DelinquencyBuckets
+            Integer delinquencyBucketId = 
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, 
List.of(//
+                    Pair.of(1, 10), //
+                    Pair.of(11, 30), //
+                    Pair.of(31, 60), //
+                    Pair.of(61, null)//
+            ));
+
+            // Create Loan Product
+            PostLoanProductsRequest loanProductsRequest = 
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+                    InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+            // set installment level delinquency as false
+            loanProductsRequest.setEnableInstallmentLevelDelinquency(false);
+            
loanProductsRequest.setDelinquencyBucketId(delinquencyBucketId.longValue());
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            // Apply and Approve Loan, turn loan level installment delinquency 
as true
+            Long loanId = applyAndApproveLoan(clientId, 
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4,
+                    req -> req.setEnableInstallmentLevelDelinquency(true));
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+            // Verify Repayment Schedule and Due Dates
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250.0, null, "01 January 2023"), //
+                    installment(312.0, false, "31 January 2023"), // 120 days 
delinquent -> range4
+                    installment(312.0, false, "02 March 2023"), // 90 days 
delinquent -> range4
+                    installment(312.0, false, "01 April 2023"), // 60 days 
delinquent -> range3
+                    installment(314.0, false, "01 May 2023") // 30 days 
delinquent -> range2
+            );
+
+            // since the installment level delinquency is overridden and set 
as true for loan application, therefore it
+            // is calculated
+            verifyDelinquency(loanId, 120, "1250.0", //
+                    delinquency(11, 30, "314.0"), // 4th installment
+                    delinquency(31, 60, "312.0"), // 3rd installment
+                    delinquency(61, null, "624.0") // 1st installment + 2nd 
installment
+            );
+        });
+
+    }
+
+    @Test
+    public void 
testLoanInheritsInstallmentLevelSettingFromLoanProductIfNotSet() {
+        runAt("31 May 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            // Create DelinquencyBuckets
+            Integer delinquencyBucketId = 
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, 
List.of(//
+                    Pair.of(1, 10), //
+                    Pair.of(11, 30), //
+                    Pair.of(31, 60), //
+                    Pair.of(61, null)//
+            ));
+
+            // Create Loan Product
+            PostLoanProductsRequest loanProductsRequest = 
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+                    InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+            // set installment level delinquency as true
+            loanProductsRequest.setEnableInstallmentLevelDelinquency(true);
+            
loanProductsRequest.setDelinquencyBucketId(delinquencyBucketId.longValue());
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            // Apply and Approve Loan, do not set installment level delinquency
+            Long loanId = applyAndApproveLoan(clientId, 
loanProductResponse.getResourceId(), "01 January 2023", 1250.0, 4);
+
+            // Disburse Loan
+            disburseLoan(loanId, BigDecimal.valueOf(1250), "01 January 2023");
+
+            // Verify Repayment Schedule and Due Dates
+            verifyRepaymentSchedule(loanId, //
+                    installment(1250.0, null, "01 January 2023"), //
+                    installment(312.0, false, "31 January 2023"), // 120 days 
delinquent -> range4
+                    installment(312.0, false, "02 March 2023"), // 90 days 
delinquent -> range4
+                    installment(312.0, false, "01 April 2023"), // 60 days 
delinquent -> range3
+                    installment(314.0, false, "01 May 2023") // 30 days 
delinquent -> range2
+            );
+
+            // since the installment level delinquency is inherited from loan 
product, therefore it
+            // is calculated
+            verifyDelinquency(loanId, 120, "1250.0", //
+                    delinquency(11, 30, "314.0"), // 4th installment
+                    delinquency(31, 60, "312.0"), // 3rd installment
+                    delinquency(61, null, "624.0") // 1st installment + 2nd 
installment
+            );
+        });
+
+    }
+
+    @Test
+    public void 
tesInstallmentLevelSettingForLoanWithLoanProductWithoutDelinquencyBucketValidation()
 {
+
+        runAt("31 May 2023", () -> {
+            // Create Client
+            Long clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+            // Create Loan Product
+            PostLoanProductsRequest loanProductsRequest = 
create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
+                    InterestType.FLAT, AmortizationType.EQUAL_INSTALLMENTS);
+            PostLoanProductsResponse loanProductResponse = 
loanProductHelper.createLoanProduct(loanProductsRequest);
+
+            // Apply For Loan with installment level delinquency setting
+            CallFailedRuntimeException callFailedRuntimeException = 
Assertions.assertThrows(CallFailedRuntimeException.class,
+                    () -> 
loanTransactionHelper.applyLoan(applyLoanRequest(clientId, 
loanProductResponse.getResourceId(), "01 January 2023",
+                            1250.0, 4, req -> 
req.setEnableInstallmentLevelDelinquency(true))));
+
+            
Assertions.assertTrue(callFailedRuntimeException.getMessage().contains(
+                    "Installment level delinquency cannot be enabled for a 
loan if Delinquency bucket is not configured for loan product"));
+
+        });
+
+    }
+
     private void updateBusinessDateAndExecuteCOBJob(String date) {
         businessDateHelper.updateBusinessDate(
                 new 
BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));

Reply via email to