FINERACT-1971: Fixing Loan repayment re-schedule with down payment.

This commit is contained in:
Peter Bagrij
2023-10-30 16:00:52 +01:00
committed by Arnold Gálovics
parent 0b1ca2dced
commit 9b2c24289c
4 changed files with 161 additions and 16 deletions
@@ -2186,8 +2186,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
// actual outstanding balance for interest calculation
Money outstandingBalance = principalToBeScheduled;
loanScheduleParams.setOutstandingBalance(outstandingBalance);
// total outstanding balance as per rest for interest calculation.
Money outstandingBalanceAsPerRest = outstandingBalance;
loanScheduleParams.setOutstandingBalanceAsPerRest(outstandingBalanceAsPerRest);
// this is required to update total fee amounts in the
// LoanScheduleModel
@@ -70,13 +70,13 @@ public abstract class BaseLoanIntegrationTest {
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
protected final ResponseSpecification requestSpec = createResponseSpecification(200);
protected final RequestSpecification responseSpec = createRequestSpecification();
protected final ResponseSpecification responseSpec = createResponseSpecification(200);
protected final RequestSpecification requestSpec = createRequestSpecification();
protected final AccountHelper accountHelper = new AccountHelper(responseSpec, requestSpec);
protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(responseSpec, requestSpec);
protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec);
protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
protected final LoanProductHelper loanProductHelper = new LoanProductHelper();
protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(responseSpec, requestSpec);
protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
protected BusinessDateHelper businessDateHelper = new BusinessDateHelper();
// asset
@@ -196,7 +196,7 @@ public abstract class BaseLoanIntegrationTest {
protected void verifyUndoLastDisbursalShallFail(Long loanId, String expectedError) {
ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build();
LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(this.responseSpec, errorResponse);
LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(this.requestSpec, errorResponse);
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> {
validationErrorHelper.undoLastDisbursalLoan(loanId, new PostLoansLoanIdRequest());
});
@@ -208,7 +208,7 @@ public abstract class BaseLoanIntegrationTest {
}
protected void verifyTransactions(Long loanId, Transaction... transactions) {
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(responseSpec, requestSpec, loanId.intValue());
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
if (transactions == null || transactions.length == 0) {
assertNull(loanDetails.getTransactions(), "No transaction is expected");
} else {
@@ -241,7 +241,7 @@ public abstract class BaseLoanIntegrationTest {
}
protected void verifyRepaymentSchedule(Long loanId, Installment... installments) {
GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoan(responseSpec, requestSpec, loanId.intValue());
GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
Assertions.assertNotNull(loanResponse.getRepaymentSchedule());
@@ -264,25 +264,26 @@ public abstract class BaseLoanIntegrationTest {
protected void runAt(String date, Runnable runnable) {
try {
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(responseSpec, requestSpec, 42, true);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(responseSpec, requestSpec, TRUE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 42, true);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, TRUE);
businessDateHelper.updateBusinessDate(
new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));
runnable.run();
} finally {
GlobalConfigurationHelper.updateIsBusinessDateEnabled(responseSpec, requestSpec, FALSE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(responseSpec, requestSpec, 42, false);
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, FALSE);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 42, false);
}
}
protected Long applyAndApproveLoan(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount) {
protected Long applyAndApproveLoan(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount,
int numberOfRepayments) {
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(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(0)
.interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0).repaymentEvery(30).repaymentFrequencyType(0)
.numberOfRepayments(1).loanTermFrequency(30).loanTermFrequencyType(0).maxOutstandingLoanBalance(BigDecimal.valueOf(amount))
.principal(BigDecimal.valueOf(amount)).loanType("individual"));
.numberOfRepayments(numberOfRepayments).loanTermFrequency(numberOfRepayments * 30).loanTermFrequencyType(0)
.maxOutstandingLoanBalance(BigDecimal.valueOf(amount)).principal(BigDecimal.valueOf(amount)).loanType("individual"));
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(amount)).dateFormat(DATETIME_PATTERN)
@@ -291,6 +292,10 @@ public abstract class BaseLoanIntegrationTest {
return approvedLoanResult.getLoanId();
}
protected Long applyAndApproveLoan(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount) {
return applyAndApproveLoan(clientId, loanProductId, loanDisbursementDate, amount, 1);
}
protected void addRepaymentForLoan(Long loanId, Double amount, String date) {
String firstRepaymentUUID = UUID.randomUUID().toString();
loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
@@ -0,0 +1,138 @@
/**
* 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.integrationtests;
import static java.lang.Boolean.TRUE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.math.BigDecimal;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.PostLoanProductsRequest;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
import org.apache.fineract.integrationtests.common.loans.LoanRescheduleRequestTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(LoanTestLifecycleExtension.class)
public class LoanRescheduleTestWithDownpayment extends BaseLoanIntegrationTest {
public static final BigDecimal DOWN_PAYMENT_PERCENTAGE = new BigDecimal(25);
private final ClientHelper clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
private final LoanRescheduleRequestHelper loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec,
this.responseSpec);
@Test
public void testRescheduleWithDownpayment() {
runAt("01 January 2023", () -> {
// Create Client
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
// Create Loan Product
Long loanProductId = createLoanProductWith25PctDownPayment(true, true);
// Apply and Approve Loan
Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1500.0, 2);
// Verify Repayment Schedule
verifyRepaymentSchedule(loanId, //
installment(1500.0, null, "01 January 2023"), //
installment(375.0, false, "01 January 2023"), //
installment(563.0, false, "31 January 2023"), //
installment(562.0, false, "02 March 2023") //
);
// 1st Disburse Loan
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
// verify transactions
verifyTransactions(loanId, //
transaction(250.0, "Down Payment", "01 January 2023"), //
transaction(1000.0, "Disbursement", "01 January 2023") //
);
// verify journal entries
verifyJournalEntries(loanId, journalEntry(250.0, loansReceivableAccount, "CREDIT"), //
journalEntry(250.0, suspenseClearingAccount, "DEBIT"), //
journalEntry(1000.0, loansReceivableAccount, "DEBIT"), //
journalEntry(1000.0, suspenseClearingAccount, "CREDIT") //
);
// Verify Repayment Schedule
verifyRepaymentSchedule(loanId, //
installment(1000.0, null, "01 January 2023"), //
installment(250.0, true, "01 January 2023"), //
installment(375.0, false, "31 January 2023"), //
installment(375.0, false, "02 March 2023") //
);
String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnInterest(null).updateGraceOnPrincipal(null)
.updateExtraTerms(null).updateNewInterestRate(null).updateRescheduleFromDate("31 January 2023")
.updateAdjustedDueDate("15 February 2023").updateSubmittedOnDate("01 January 2023").updateRescheduleReasonId("1")
.build(loanId.toString());
Integer loanRescheduleRequest = loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON);
requestJSON = new LoanRescheduleRequestTestBuilder().updateSubmittedOnDate("01 January 2023")
.getApproveLoanRescheduleRequestJSON();
Integer approveLoanRescheduleRequest = loanRescheduleRequestHelper.approveLoanRescheduleRequest(loanRescheduleRequest,
requestJSON);
verifyRepaymentSchedule(loanId, //
installment(1000.0, null, "01 January 2023"), //
installment(250.0, true, "01 January 2023"), //
installment(375.0, false, "15 February 2023"), //
installment(375.0, false, "17 March 2023") //
);
});
}
private Long createLoanProductWith25PctDownPayment(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled) {
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
product.setMultiDisburseLoan(multiDisburseEnabled);
if (!multiDisburseEnabled) {
product.disallowExpectedDisbursements(null);
product.setAllowApprovedDisbursedAmountsOverApplied(null);
product.overAppliedCalculationType(null);
product.overAppliedNumber(null);
}
product.setEnableDownPayment(true);
product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE);
product.setEnableAutoRepaymentForDownPayment(autoDownPaymentEnabled);
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper
.retrieveLoanProductById(loanProductResponse.getResourceId());
Long loanProductId = loanProductResponse.getResourceId();
assertEquals(TRUE, getLoanProductsProductIdResponse.getEnableDownPayment());
assertNotNull(getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment());
assertEquals(0, getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment().compareTo(DOWN_PAYMENT_PERCENTAGE));
assertEquals(autoDownPaymentEnabled, getLoanProductsProductIdResponse.getEnableAutoRepaymentForDownPayment());
assertEquals(multiDisburseEnabled, getLoanProductsProductIdResponse.getMultiDisburseLoan());
return loanProductId;
}
}
@@ -37,7 +37,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
public class UndoLoanDisbursalWithDownPaymentIntegrationTest extends BaseLoanIntegrationTest {
public static final BigDecimal DOWN_PAYMENT_PERCENTAGE = new BigDecimal(25);
private final ClientHelper clientHelper = new ClientHelper(this.responseSpec, this.requestSpec);
private final ClientHelper clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
@Test
public void testUndoDisbursalForLoanWithSingleDisbursalAutoDownPaymentEnabledAndNoManualTransactions() {