FINERACT-1971: Fixing Loan repayment re-schedule with down payment.
This commit is contained in:
committed by
Arnold Gálovics
parent
0b1ca2dced
commit
9b2c24289c
+2
@@ -2186,8 +2186,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
|
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
|
||||||
// actual outstanding balance for interest calculation
|
// actual outstanding balance for interest calculation
|
||||||
Money outstandingBalance = principalToBeScheduled;
|
Money outstandingBalance = principalToBeScheduled;
|
||||||
|
loanScheduleParams.setOutstandingBalance(outstandingBalance);
|
||||||
// total outstanding balance as per rest for interest calculation.
|
// total outstanding balance as per rest for interest calculation.
|
||||||
Money outstandingBalanceAsPerRest = outstandingBalance;
|
Money outstandingBalanceAsPerRest = outstandingBalance;
|
||||||
|
loanScheduleParams.setOutstandingBalanceAsPerRest(outstandingBalanceAsPerRest);
|
||||||
|
|
||||||
// this is required to update total fee amounts in the
|
// this is required to update total fee amounts in the
|
||||||
// LoanScheduleModel
|
// LoanScheduleModel
|
||||||
|
|||||||
+20
-15
@@ -70,13 +70,13 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
||||||
|
|
||||||
protected final ResponseSpecification requestSpec = createResponseSpecification(200);
|
protected final ResponseSpecification responseSpec = createResponseSpecification(200);
|
||||||
protected final RequestSpecification responseSpec = createRequestSpecification();
|
protected final RequestSpecification requestSpec = createRequestSpecification();
|
||||||
|
|
||||||
protected final AccountHelper accountHelper = new AccountHelper(responseSpec, requestSpec);
|
protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec);
|
||||||
protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(responseSpec, requestSpec);
|
protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||||
protected final LoanProductHelper loanProductHelper = new LoanProductHelper();
|
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();
|
protected BusinessDateHelper businessDateHelper = new BusinessDateHelper();
|
||||||
|
|
||||||
// asset
|
// asset
|
||||||
@@ -196,7 +196,7 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
protected void verifyUndoLastDisbursalShallFail(Long loanId, String expectedError) {
|
protected void verifyUndoLastDisbursalShallFail(Long loanId, String expectedError) {
|
||||||
ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build();
|
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, () -> {
|
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, () -> {
|
||||||
validationErrorHelper.undoLastDisbursalLoan(loanId, new PostLoansLoanIdRequest());
|
validationErrorHelper.undoLastDisbursalLoan(loanId, new PostLoansLoanIdRequest());
|
||||||
});
|
});
|
||||||
@@ -208,7 +208,7 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void verifyTransactions(Long loanId, Transaction... transactions) {
|
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) {
|
if (transactions == null || transactions.length == 0) {
|
||||||
assertNull(loanDetails.getTransactions(), "No transaction is expected");
|
assertNull(loanDetails.getTransactions(), "No transaction is expected");
|
||||||
} else {
|
} else {
|
||||||
@@ -241,7 +241,7 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void verifyRepaymentSchedule(Long loanId, Installment... installments) {
|
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);
|
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
||||||
|
|
||||||
Assertions.assertNotNull(loanResponse.getRepaymentSchedule());
|
Assertions.assertNotNull(loanResponse.getRepaymentSchedule());
|
||||||
@@ -264,25 +264,26 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
protected void runAt(String date, Runnable runnable) {
|
protected void runAt(String date, Runnable runnable) {
|
||||||
try {
|
try {
|
||||||
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(responseSpec, requestSpec, 42, true);
|
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 42, true);
|
||||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(responseSpec, requestSpec, TRUE);
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, TRUE);
|
||||||
businessDateHelper.updateBusinessDate(
|
businessDateHelper.updateBusinessDate(
|
||||||
new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));
|
new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en"));
|
||||||
runnable.run();
|
runnable.run();
|
||||||
} finally {
|
} finally {
|
||||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(responseSpec, requestSpec, FALSE);
|
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, FALSE);
|
||||||
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(responseSpec, requestSpec, 42, 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)
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(clientId)
|
||||||
.productId(loanProductId).expectedDisbursementDate(loanDisbursementDate).dateFormat(DATETIME_PATTERN)
|
.productId(loanProductId).expectedDisbursementDate(loanDisbursementDate).dateFormat(DATETIME_PATTERN)
|
||||||
.transactionProcessingStrategyCode(DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY)
|
.transactionProcessingStrategyCode(DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY)
|
||||||
.locale("en").submittedOnDate(loanDisbursementDate).amortizationType(1).interestRatePerPeriod(0)
|
.locale("en").submittedOnDate(loanDisbursementDate).amortizationType(1).interestRatePerPeriod(0)
|
||||||
.interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0).repaymentEvery(30).repaymentFrequencyType(0)
|
.interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0).repaymentEvery(30).repaymentFrequencyType(0)
|
||||||
.numberOfRepayments(1).loanTermFrequency(30).loanTermFrequencyType(0).maxOutstandingLoanBalance(BigDecimal.valueOf(amount))
|
.numberOfRepayments(numberOfRepayments).loanTermFrequency(numberOfRepayments * 30).loanTermFrequencyType(0)
|
||||||
.principal(BigDecimal.valueOf(amount)).loanType("individual"));
|
.maxOutstandingLoanBalance(BigDecimal.valueOf(amount)).principal(BigDecimal.valueOf(amount)).loanType("individual"));
|
||||||
|
|
||||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||||
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(amount)).dateFormat(DATETIME_PATTERN)
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(amount)).dateFormat(DATETIME_PATTERN)
|
||||||
@@ -291,6 +292,10 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
return approvedLoanResult.getLoanId();
|
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) {
|
protected void addRepaymentForLoan(Long loanId, Double amount, String date) {
|
||||||
String firstRepaymentUUID = UUID.randomUUID().toString();
|
String firstRepaymentUUID = UUID.randomUUID().toString();
|
||||||
loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
|
loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
|
||||||
|
|||||||
+138
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -37,7 +37,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||||||
public class UndoLoanDisbursalWithDownPaymentIntegrationTest extends BaseLoanIntegrationTest {
|
public class UndoLoanDisbursalWithDownPaymentIntegrationTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
public static final BigDecimal DOWN_PAYMENT_PERCENTAGE = new BigDecimal(25);
|
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
|
@Test
|
||||||
public void testUndoDisbursalForLoanWithSingleDisbursalAutoDownPaymentEnabledAndNoManualTransactions() {
|
public void testUndoDisbursalForLoanWithSingleDisbursalAutoDownPaymentEnabledAndNoManualTransactions() {
|
||||||
|
|||||||
Reference in New Issue
Block a user