FINERACT-1748: Loan prepayment issue after advance payment

This commit is contained in:
logoutdhaval
2022-10-10 15:56:50 +02:00
committed by Arnold Gálovics
parent 47c9b93d27
commit ba1d0ce66c
3 changed files with 219 additions and 2 deletions
@@ -2225,7 +2225,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
LocalDate modifiedLastInstDate = null;
LoanTermVariationsData variation1 = null;
boolean hasDueDateVariation = false;
while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) {
hasDueDateVariation = true;
LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
if (!variation.isSpecificToInstallment()) {
modifiedLastInstDate = variation.getDateValue();
@@ -2233,7 +2235,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
}
if (!lastInstallmentDate.isEqual(installment.getDueDate())
if (hasDueDateVariation && !lastInstallmentDate.isEqual(installment.getDueDate())
&& !installment.getDueDate().equals(modifiedLastInstDate)) {
lastInstallmentDate = prevLastInstDate;
actualRepaymentDate = lastInstallmentDate;
@@ -45,6 +45,7 @@ import java.util.Locale;
import java.util.Map;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.accounting.Account;
@@ -4712,6 +4713,82 @@ public class ClientLoanIntegrationTest {
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
private Integer createLoanProductWithInterestRecalculationAndCompoundingDetails(final String repaymentStrategy,
final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
final String recalculationRestFrequencyType, final String preCloseInterestCalculationStrategy, final Account[] accounts,
final String installmentMultipleOf) {
final String recalculationCompoundingFrequencyType = null;
final String recalculationCompoundingFrequencyInterval = null;
final Integer recalculationCompoundingFrequencyOnDayType = null;
final Integer recalculationCompoundingFrequencyDayOfWeekType = null;
return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
rescheduleStrategyMethod, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval,
preCloseInterestCalculationStrategy, accounts, null, false, recalculationCompoundingFrequencyOnDayType,
recalculationCompoundingFrequencyDayOfWeekType, installmentMultipleOf);
}
private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
final String recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval,
final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId,
boolean isArrearsBasedOnOriginalSchedule, final Integer recalculationCompoundingFrequencyOnDayType,
final Integer recalculationCompoundingFrequencyDayOfWeekType, final String installmentsMultiplesOf) {
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
LoanProductTestBuilder builder = new LoanProductTestBuilder().withPrincipal("10000.00").withNumberOfRepayments("12")
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("19.9")
.withInterestRateFrequencyTypeAsMonths().withRepaymentStrategy(repaymentStrategy).withAmortizationTypeAsEqualInstallments()
.withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays()
.withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
preCloseInterestCalculationStrategy)
.withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod,
preCloseInterestCalculationStrategy)
.withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType,
recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType,
recalculationCompoundingFrequencyDayOfWeekType)
.withDefineInstallmentAmount(true).withInstallmentAmountInMultiplesOf(installmentsMultiplesOf);
if (accounts != null) {
builder = builder.withAccountingRulePeriodicAccrual(accounts);
}
if (isArrearsBasedOnOriginalSchedule) {
builder = builder.withArrearsConfiguration();
}
final String loanProductJSON = builder.build(chargeId);
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
final String disbursementDate, final String repaymentStrategy, final String firstRepaymentDate) {
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec);
Assertions.assertNotNull(collateralId);
List<HashMap> collaterals = new ArrayList<>();
final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec,
String.valueOf(clientID), collateralId);
Assertions.assertNotNull(clientCollateralId);
addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1));
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
.withPrincipal("10000.00") //
.withLoanTermFrequency("12") //
.withLoanTermFrequencyAsMonths() //
.withNumberOfRepayments("12") //
.withRepaymentEveryAfter("1") //
.withLoanTermFrequencyAsMonths() //
.withInterestRatePerPeriod("19.9") //
.withAmortizationTypeAsEqualInstallments() //
.withInterestTypeAsDecliningBalance() //
.withInterestCalculationPeriodTypeAsDays() //
.withExpectedDisbursementDate(disbursementDate) //
.withSubmittedOnDate(disbursementDate) //
.withwithRepaymentStrategy(repaymentStrategy).withRepaymentFrequencyTypeAsMonths()//
.withFirstRepaymentDate(firstRepaymentDate).withCollaterals(collaterals)
.build(clientID.toString(), loanProductID.toString(), null);
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
}
private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID,
final String disbursementDate, final String repaymentStrategy, final List<HashMap> charges) {
return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, repaymentStrategy, charges, null,
@@ -5721,6 +5798,136 @@ public class ClientLoanIntegrationTest {
}
@Test
public void testLoanScheduleWithInterestRecalculationMakePrepaymentAfterRepayment() {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
Calendar currentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
startDate.add(Calendar.MONTH, -8);
Calendar firstRepaymentDate = (Calendar) startDate.clone();
firstRepaymentDate.add(Calendar.MONTH, 1);
firstRepaymentDate.add(Calendar.DAY_OF_MONTH, firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) - Calendar.DAY_OF_MONTH);
String firstRepayment = dateFormat.format(firstRepaymentDate.getTime());
final String loanDisbursementDate = dateFormat.format(startDate.getTime());
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
Assertions.assertNotNull(loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
Assertions.assertNotNull(loanSchedule);
startDate.add(Calendar.DAY_OF_MONTH, 2);
String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
//
Float earlyPayment = Float.parseFloat("3000");
this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate, earlyPayment, loanID);
HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
String prepayAmount = String.valueOf(prepayDetail.get("amount"));
String loanPrepaymentDate = dateFormat.format(currentDate.getTime());
this.loanTransactionHelper.makeRepayment(loanPrepaymentDate, Float.parseFloat(prepayAmount), loanID);
loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap);
}
@Test
public void testLoanScheduleWithInterestRecalculationMakeAdvancePaymentTillSettlement() {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build();
final LoanTransactionHelper validationErrorHelper = new LoanTransactionHelper(this.requestSpec, errorResponse);
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US);
dateFormat.setTimeZone(Utils.getTimeZoneOfTenant());
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "42", true);
Calendar startDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
Calendar currentDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
startDate.add(Calendar.MONTH, -8);
Calendar firstRepaymentDate = (Calendar) startDate.clone();
firstRepaymentDate.add(Calendar.MONTH, 1);
firstRepaymentDate.add(Calendar.DAY_OF_MONTH, firstRepaymentDate.getActualMaximum(Calendar.DAY_OF_MONTH) - Calendar.DAY_OF_MONTH);
String firstRepayment = dateFormat.format(firstRepaymentDate.getTime());
final String loanDisbursementDate = dateFormat.format(startDate.getTime());
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails(
LoanProductTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD,
LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, "12");
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, loanDisbursementDate,
LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY, firstRepayment);
Assertions.assertNotNull(loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.approveLoan(loanDisbursementDate, loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
LOG.info("-------------------------------DISBURSE LOAN-------------------------------------------");
String loanDetails = this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, loanID);
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(loanDisbursementDate, loanID,
JsonPath.from(loanDetails).get("netDisbursalAmount").toString());
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
Assertions.assertNotNull(loanSchedule);
Calendar repaymentDate = (Calendar) firstRepaymentDate.clone();
startDate.add(Calendar.DAY_OF_MONTH, 2);
String loanFirstRepaymentDate = dateFormat.format(startDate.getTime());
//
Float earlyPayment = Float.parseFloat("3000");
String retrieveDueDate = null;
Float amount = null;
this.loanTransactionHelper.makeRepayment(loanFirstRepaymentDate, earlyPayment, loanID);
for (int i = 1; i < loanSchedule.size(); i++) {
retrieveDueDate = dateFormat.format(repaymentDate.getTime());
amount = (Float) loanSchedule.get(i).get("principalOriginalDue") + (Float) loanSchedule.get(i).get("interestOriginalDue");
if (currentDate.after(repaymentDate)) {
this.loanTransactionHelper.makeRepayment(retrieveDueDate, amount, loanID);
} else {
break;
}
repaymentDate.add(Calendar.MONTH, 1);
}
HashMap savingsAccountErrorData = validationErrorHelper.makeRepayment(retrieveDueDate, amount, loanID);
ArrayList<HashMap> error = (ArrayList<HashMap>) savingsAccountErrorData.get("errors");
assertEquals("error.msg.loan.transaction.cannot.be.a.future.date", error.get(0).get("userMessageGlobalisationCode"));
}
@Test
public void testCollateralDataIsAvailableWhenRequested() {
// given
@@ -131,6 +131,7 @@ public class LoanProductTestBuilder {
private boolean syncExpectedWithDisbursementDate = false;
private String fixedPrincipalPercentagePerInstallment;
private String installmentAmountInMultiplesOf;
private boolean canDefineInstallmentAmount;
public String build(final String chargeId) {
final HashMap<String, Object> map = build(chargeId, null);
@@ -185,7 +186,9 @@ public class LoanProductTestBuilder {
map.put("overAppliedNumber", this.overAppliedNumber);
}
}
if (this.canDefineInstallmentAmount) {
map.put("canDefineInstallmentAmount", this.canDefineInstallmentAmount);
}
if (multiDisburseLoan) {
map.put("multiDisburseLoan", this.multiDisburseLoan);
map.put("maxTrancheCount", this.maxTrancheCount);
@@ -473,6 +476,11 @@ public class LoanProductTestBuilder {
return this;
}
public LoanProductTestBuilder withDefineInstallmentAmount(final boolean canDefineInstallmentAmount) {
this.canDefineInstallmentAmount = canDefineInstallmentAmount;
return this;
}
public LoanProductTestBuilder currencyDetails(final String digitsAfterDecimal, final String inMultiplesOf) {
this.digitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf;