Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FINERACT-1971: Override enableInstallmentLevelDelinquency loan application #3886

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,8 @@ private PostLoansRequest() {}
public Integer graceOnArrearsAgeing;
@Schema(example = "HORIZONTAL")
public String loanScheduleProcessingType;
@Schema(example = "false")
public Boolean enableInstallmentLevelDelinquency;
}

@Schema(description = "PostLoansResponse")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -581,6 +582,22 @@ public void validateForCreate(final String json, final boolean isMeetingMandator
}

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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,13 @@ private Loan assembleApplication(final JsonElement element, final Long clientId,
}
}

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
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;

Expand Down Expand Up @@ -242,6 +244,172 @@ public void testInstallmentLevelDelinquencyUpdatedWhenCOBIsExecuted() {
});
}

@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"));
Expand Down