[FINERACT-1958] Repayment schedule for down payment
This commit is contained in:
+15
-13
@@ -1264,13 +1264,14 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
this.repaymentScheduleInstallments.clear();
|
||||
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
|
||||
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this,
|
||||
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
||||
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
|
||||
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
|
||||
scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(),
|
||||
scheduledLoanInstallment.getLoanCompoundingDetails());
|
||||
scheduledLoanInstallment.getLoanCompoundingDetails(), scheduledLoanInstallment.rescheduleInterestPortion(),
|
||||
scheduledLoanInstallment.isDownPaymentPeriod());
|
||||
addLoanRepaymentScheduleInstallment(installment);
|
||||
}
|
||||
}
|
||||
@@ -2477,7 +2478,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges,
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) {
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail, boolean downPaymentEnabled) {
|
||||
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE);
|
||||
|
||||
@@ -2495,7 +2496,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
|
||||
|
||||
regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(),
|
||||
isDisbursementMissed(), scheduleGeneratorDTO);
|
||||
isDisbursementMissed(), scheduleGeneratorDTO, downPaymentEnabled);
|
||||
|
||||
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
@@ -2527,14 +2528,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam,
|
||||
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean downPaymentEnabled) {
|
||||
|
||||
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||
if (interestRecalculationEnabledParam
|
||||
&& (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam)) {
|
||||
if ((interestRecalculationEnabledParam
|
||||
&& (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam))) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
} else if (downPaymentEnabled) {
|
||||
LoanScheduleDTO loanSchedule = getRecalculatedSchedule(scheduleGeneratorDTO);
|
||||
updateLoanSchedule(loanSchedule.getInstallments());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<LoanDisbursementDetails> getDisbursedLoanDisbursementDetails() {
|
||||
@@ -2965,7 +2968,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
externalId = ExternalId.generate();
|
||||
}
|
||||
Money downPaymentMoney = Money.of(getCurrency(),
|
||||
MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 9));
|
||||
MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 19));
|
||||
LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn,
|
||||
externalId);
|
||||
/*
|
||||
@@ -5553,7 +5556,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
||||
|
||||
LocalDate lastTransactionDate = getLastUserTransactionDate();
|
||||
final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO);
|
||||
if (loanSchedule == null) {
|
||||
@@ -5788,15 +5790,15 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
|
||||
|
||||
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || isChargedOff()) {
|
||||
if (!this.repaymentScheduleDetail().isEnableDownPayment()
|
||||
&& (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || isChargedOff())) {
|
||||
return null;
|
||||
}
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
|
||||
|
||||
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
final MathContext mc = new MathContext(19, roundingMode);
|
||||
|
||||
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO);
|
||||
|
||||
|
||||
+13
@@ -131,6 +131,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
@Column(name = "credits_amount", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal credits;
|
||||
|
||||
@Column(name = "is_down_payment", nullable = false)
|
||||
private boolean isDownPayment;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
|
||||
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
|
||||
|
||||
@@ -154,6 +157,15 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
|
||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails, final BigDecimal rescheduleInterestPortion) {
|
||||
this(loan, installmentNumber, fromDate, dueDate, principal, interest, feeCharges, penaltyCharges, recalculatedInterestComponent,
|
||||
compoundingDetails, rescheduleInterestPortion, false);
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
|
||||
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
|
||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails, final BigDecimal rescheduleInterestPortion,
|
||||
final boolean isDownPayment) {
|
||||
this.loan = loan;
|
||||
this.installmentNumber = installmentNumber;
|
||||
this.fromDate = fromDate;
|
||||
@@ -169,6 +181,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
}
|
||||
this.loanCompoundingDetails = compoundingDetails;
|
||||
this.rescheduleInterestPortion = rescheduleInterestPortion;
|
||||
this.isDownPayment = isDownPayment;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
|
||||
|
||||
+60
@@ -91,6 +91,11 @@ public final class LoanSchedulePeriodData {
|
||||
totalInstallmentAmountForPeriod);
|
||||
}
|
||||
|
||||
public static LoanSchedulePeriodData downPaymentOnlyPeriod(final Integer periodNumber, final LocalDate periodDate,
|
||||
final BigDecimal principalDue, final BigDecimal principalOutstanding) {
|
||||
return new LoanSchedulePeriodData(periodNumber, periodDate, periodDate, principalDue, principalOutstanding);
|
||||
}
|
||||
|
||||
public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnings("unused") final Long loanId,
|
||||
final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate,
|
||||
final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid,
|
||||
@@ -265,6 +270,61 @@ public final class LoanSchedulePeriodData {
|
||||
this.totalCredits = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
// TODO refactor the class to builder pattern
|
||||
private LoanSchedulePeriodData(Integer periodNumber, LocalDate fromDate, LocalDate dueDate, BigDecimal principalDue,
|
||||
BigDecimal principalOutstanding) {
|
||||
this.period = periodNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.obligationsMetOnDate = null;
|
||||
this.complete = null;
|
||||
this.daysInPeriod = 1;
|
||||
this.principalDisbursed = null;
|
||||
this.principalOriginalDue = principalDue;
|
||||
this.principalDue = principalOriginalDue;
|
||||
this.principalPaid = null;
|
||||
this.principalWrittenOff = null;
|
||||
this.principalOutstanding = principalOriginalDue;
|
||||
this.principalLoanBalanceOutstanding = principalOutstanding;
|
||||
|
||||
this.interestOriginalDue = null;
|
||||
this.interestDue = null;
|
||||
this.interestPaid = null;
|
||||
this.interestWaived = null;
|
||||
this.interestWrittenOff = null;
|
||||
this.interestOutstanding = null;
|
||||
|
||||
this.feeChargesDue = null;
|
||||
this.feeChargesPaid = null;
|
||||
this.feeChargesWaived = null;
|
||||
this.feeChargesWrittenOff = null;
|
||||
this.feeChargesOutstanding = null;
|
||||
|
||||
this.penaltyChargesDue = null;
|
||||
this.penaltyChargesPaid = null;
|
||||
this.penaltyChargesWaived = null;
|
||||
this.penaltyChargesWrittenOff = null;
|
||||
this.penaltyChargesOutstanding = null;
|
||||
|
||||
this.totalOriginalDueForPeriod = principalDue;
|
||||
this.totalDueForPeriod = principalDue;
|
||||
this.totalPaidForPeriod = BigDecimal.ZERO;
|
||||
this.totalPaidInAdvanceForPeriod = null;
|
||||
this.totalPaidLateForPeriod = null;
|
||||
this.totalWaivedForPeriod = null;
|
||||
this.totalWrittenOffForPeriod = null;
|
||||
this.totalOutstandingForPeriod = totalDueForPeriod;
|
||||
this.totalActualCostOfLoanForPeriod = null;
|
||||
this.totalInstallmentAmountForPeriod = totalDueForPeriod;
|
||||
|
||||
if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) {
|
||||
this.totalOverdue = this.totalOutstandingForPeriod;
|
||||
} else {
|
||||
this.totalOverdue = null;
|
||||
}
|
||||
this.totalCredits = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
/*
|
||||
* Used for creating loan schedule periods with full information on expected principal, interest & charges along
|
||||
* with what portion of each is paid.
|
||||
|
||||
+8
@@ -1737,4 +1737,12 @@ public final class LoanApplicationTerms {
|
||||
public void setNewScheduledDueDateStart(LocalDate newScheduledDueDateStart) {
|
||||
this.newScheduledDueDateStart = newScheduledDueDateStart;
|
||||
}
|
||||
|
||||
public boolean isDownPaymentEnabled() {
|
||||
return isDownPaymentEnabled;
|
||||
}
|
||||
|
||||
public BigDecimal getDisbursedAmountPercentageForDownPayment() {
|
||||
return disbursedAmountPercentageForDownPayment;
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -31,6 +31,8 @@ public interface LoanScheduleModelPeriod {
|
||||
|
||||
boolean isRepaymentPeriod();
|
||||
|
||||
boolean isDownPaymentPeriod();
|
||||
|
||||
Integer periodNumber();
|
||||
|
||||
LocalDate periodFromDate();
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||
|
||||
public final class LoanScheduleModelDownPaymentPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
private final int periodNumber;
|
||||
private final LocalDate periodDate;
|
||||
private Money principalDue;
|
||||
private final Money outstandingLoanBalance;
|
||||
|
||||
public static LoanScheduleModelDownPaymentPeriod downPayment(final int periodNumber, final LocalDate periodDate,
|
||||
final Money principalDue, final Money outstandingLoanBalance) {
|
||||
|
||||
return new LoanScheduleModelDownPaymentPeriod(periodNumber, periodDate, principalDue, outstandingLoanBalance);
|
||||
}
|
||||
|
||||
private LoanScheduleModelDownPaymentPeriod(int periodNumber, LocalDate periodDate, Money principalDue, Money outstandingLoanBalance) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.periodDate = periodDate;
|
||||
this.principalDue = principalDue;
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedulePeriodData toData() {
|
||||
return LoanSchedulePeriodData.downPaymentOnlyPeriod(this.periodNumber, this.periodDate, this.principalDue.getAmount(),
|
||||
this.outstandingLoanBalance.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDownPaymentPeriod() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return this.periodNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodFromDate() {
|
||||
return this.periodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodDueDate() {
|
||||
return this.periodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal principalDue() {
|
||||
BigDecimal value = BigDecimal.ZERO;
|
||||
if (this.principalDue != null) {
|
||||
value = this.principalDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal interestDue() {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal feeChargesDue() {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal penaltyChargesDue() {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecalculatedInterestComponent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrincipalAmount(Money principalDue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterestAmount(Money interestDue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEMIFixedSpecificToInstallmentTrue() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEMIFixedSpecificToInstallment() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal rescheduleInterestPortion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -218,8 +218,8 @@ public final class LoanScheduleParams {
|
||||
final Collection<RecalculationDetail> recalculationDetails = null;
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
|
||||
final LocalDate scheduleTillDate = null;
|
||||
final boolean applyInterestRecalculation = false;
|
||||
final Money unCompoundedAmount = Money.zero(currency);
|
||||
final boolean applyInterestRecalculation = false;
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
|
||||
+95
-26
@@ -29,9 +29,11 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
@@ -50,6 +52,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
||||
@@ -88,7 +91,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions();
|
||||
LoanScheduleParams scheduleParams = null;
|
||||
LoanScheduleParams scheduleParams;
|
||||
if (loanScheduleParams == null) {
|
||||
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
|
||||
loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms));
|
||||
@@ -107,7 +110,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
Collection<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||
if (!scheduleParams.isPartialUpdate()) {
|
||||
periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, loanApplicationTerms,
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
chargesDueAtTimeOfDisbursement, scheduleParams);
|
||||
}
|
||||
|
||||
// Determine the total interest owed over the full loan for FLAT
|
||||
@@ -128,16 +131,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
|
||||
|
||||
if (!scheduleParams.isPartialUpdate()) {
|
||||
Long notDownPaymentPeriodNumber = periods.stream().filter(period -> !period.isDownPaymentPeriod()).count();
|
||||
// Set Fixed Principal Amount
|
||||
updateAmortization(mc, loanApplicationTerms, scheduleParams.getPeriodNumber(), scheduleParams.getOutstandingBalance());
|
||||
updateAmortization(mc, loanApplicationTerms, notDownPaymentPeriodNumber.intValue(), scheduleParams.getOutstandingBalance());
|
||||
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
/* fetches the first tranche amount and also updates other tranche details to map */
|
||||
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), periods,
|
||||
chargesDueAtTimeOfDisbursement, scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation());
|
||||
chargesDueAtTimeOfDisbursement, scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation(),
|
||||
scheduleParams);
|
||||
scheduleParams.setPrincipalToBeScheduled(Money.of(currency, disburseAmt));
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
|
||||
scheduleParams.setOutstandingBalance(Money.of(currency, disburseAmt));
|
||||
scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, disburseAmt));
|
||||
}
|
||||
}
|
||||
@@ -178,6 +182,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
isFirstRepayment = false;
|
||||
}
|
||||
}
|
||||
|
||||
while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
|
||||
LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
|
||||
scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator
|
||||
@@ -976,6 +981,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, disburseDetail.getKey(),
|
||||
disburseDetail.getValue().getAmount());
|
||||
}
|
||||
|
||||
// updates actual outstanding balance with new
|
||||
// disbursement detail
|
||||
scheduleParams.addOutstandingBalance(disburseDetail.getValue());
|
||||
@@ -1461,7 +1472,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation,
|
||||
LocalDate repaymentDate, final Collection<RecalculationDetail> transactions) {
|
||||
Collection<RecalculationDetail> applicableTransactions = new ArrayList<>();
|
||||
if (applyInterestRecalculation) {
|
||||
if (applyInterestRecalculation && !Objects.isNull(transactions)) {
|
||||
for (RecalculationDetail detail : transactions) {
|
||||
if (!detail.getTransactionDate().isAfter(repaymentDate)) {
|
||||
applicableTransactions.add(detail);
|
||||
@@ -1912,26 +1923,38 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate,
|
||||
final Collection<LoanScheduleModelPeriod> periods, final BigDecimal chargesDueAtTimeOfDisbursement,
|
||||
final Map<LocalDate, Money> disurseDetail, final boolean excludePastUndisbursed) {
|
||||
|
||||
final Map<LocalDate, Money> disburseDetails, final boolean excludePastUndisbursed, LoanScheduleParams scheduleParams) {
|
||||
// this method relates to multi-disbursement loans
|
||||
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
|
||||
if (Objects.isNull(scheduleParams)) {
|
||||
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
|
||||
loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms));
|
||||
}
|
||||
BigDecimal principal = BigDecimal.ZERO;
|
||||
scheduleParams.setOutstandingBalance(Money.zero(currency));
|
||||
if (loanApplicationTerms.getDisbursementDatas().size() == 0) {
|
||||
// non tranche loans have no disbursement data entries in submitted and approved status
|
||||
// the appropriate approved amount or applied for amount is used to show a proposed schedule
|
||||
if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
principal = loanApplicationTerms.getApprovedPrincipal().getAmount();
|
||||
scheduleParams.addOutstandingBalance(Money.of(currency, principal));
|
||||
} else {
|
||||
principal = loanApplicationTerms.getPrincipal().getAmount();
|
||||
scheduleParams.addOutstandingBalance(Money.of(currency, principal));
|
||||
}
|
||||
} else {
|
||||
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
|
||||
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
|
||||
if (disbursementData.disbursementDate().equals(disbursementDate)) {
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||
disbursementData.disbursementDate(), Money.of(currency, disbursementData.getPrincipal()),
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
scheduleParams.addOutstandingBalance(Money.of(currency, disbursementData.getPrincipal()));
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, disbursementData.disbursementDate(),
|
||||
disbursementData.getPrincipal());
|
||||
}
|
||||
|
||||
principal = principal.add(disbursementData.getPrincipal());
|
||||
} else if (!excludePastUndisbursed || disbursementData.isDisbursed()
|
||||
|| !disbursementData.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) {
|
||||
@@ -1939,13 +1962,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
* JW: sums up amounts by disbursal date in case of side-effect issues. Original assumed that there
|
||||
* were no duplicate disbursal dates and 'put' each amount into the map keyed by date
|
||||
*/
|
||||
Money prevsum = disurseDetail.get(disbursementData.disbursementDate());
|
||||
Money prevsum = disburseDetails.get(disbursementData.disbursementDate());
|
||||
BigDecimal sumToNow = BigDecimal.ZERO;
|
||||
if (prevsum != null) {
|
||||
sumToNow = prevsum.getAmount();
|
||||
}
|
||||
sumToNow = sumToNow.add(disbursementData.getPrincipal());
|
||||
disurseDetail.put(disbursementData.disbursementDate(), Money.of(currency, sumToNow));
|
||||
disburseDetails.put(disbursementData.disbursementDate(), Money.of(currency, sumToNow));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1953,9 +1976,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
}
|
||||
|
||||
private Collection<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final int numberOfRepayments,
|
||||
final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
|
||||
Collection<LoanScheduleModelPeriod> periods = null;
|
||||
final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement,
|
||||
LoanScheduleParams scheduleParams) {
|
||||
Collection<LoanScheduleModelPeriod> periods;
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
int multiDisbursalTrancheEntries = loanApplicationTerms.getDisbursementDatas().size();
|
||||
if (multiDisbursalTrancheEntries == 0) {
|
||||
@@ -1968,11 +1991,35 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanApplicationTerms.getPrincipal().getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
|
||||
private void createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms, LoanScheduleParams scheduleParams,
|
||||
Collection<LoanScheduleModelPeriod> periods, LocalDate date, BigDecimal periodBaseAmount) {
|
||||
BigDecimal downPaymentAmount = MathUtil.percentageOf(periodBaseAmount,
|
||||
loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19);
|
||||
Money downPayment = Money.of(loanApplicationTerms.getCurrency(), downPaymentAmount);
|
||||
LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod
|
||||
.downPayment(scheduleParams.getInstalmentNumber(), date, downPayment, scheduleParams.getOutstandingBalance());
|
||||
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
|
||||
scheduleParams.incrementPeriodNumber();
|
||||
scheduleParams.incrementInstalmentNumber();
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
scheduleParams.reduceOutstandingBalance(downPayment);
|
||||
scheduleParams.addTotalCumulativePrincipal(downPayment);
|
||||
scheduleParams.addTotalRepaymentExpected(downPayment);
|
||||
}
|
||||
|
||||
private Set<LoanCharge> seperateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
|
||||
Set<LoanCharge> interestCharges = new HashSet<>();
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
@@ -2090,11 +2137,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, Loan loan,
|
||||
final HolidayDetailDTO holidayDetailDTO,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate rescheduleFrom,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate rescheduleFrom,
|
||||
final LocalDate scheduleTillDate) {
|
||||
// Loan transactions to process and find the variation on payments
|
||||
Collection<RecalculationDetail> recalculationDetails = new ArrayList<>();
|
||||
List<LoanTransaction> transactions = loan.getLoanTransactions();
|
||||
MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loan.getActiveCharges());
|
||||
for (LoanTransaction loanTransaction : transactions) {
|
||||
if (loanTransaction.isPaymentTransaction()) {
|
||||
recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(),
|
||||
@@ -2110,6 +2159,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
// this block is to retain the schedule installments prior to the
|
||||
// provided date and creates late and early payment details for further
|
||||
// calculations
|
||||
if (Objects.isNull(rescheduleFrom) && loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
rescheduleFrom = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
}
|
||||
if (rescheduleFrom != null) {
|
||||
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
|
||||
// actual outstanding balance for interest calculation
|
||||
@@ -2119,10 +2171,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
// this is required to update total fee amounts in the
|
||||
// LoanScheduleModel
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loan.getActiveCharges());
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
getPrincipalToBeScheduled(loanApplicationTerms));
|
||||
}
|
||||
periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(),
|
||||
loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
||||
MonetaryCurrency currency = outstandingBalance.getCurrency();
|
||||
loanApplicationTerms, chargesDueAtTimeOfDisbursement, loanScheduleParams);
|
||||
|
||||
// early payments will be added here and as per the selected
|
||||
// strategy
|
||||
@@ -2160,7 +2215,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
Money totalCumulativeInterest = principalToBeScheduled.zero();
|
||||
Money totalFeeChargesCharged = principalToBeScheduled.zero().plus(chargesDueAtTimeOfDisbursement);
|
||||
Money totalPenaltyChargesCharged = principalToBeScheduled.zero();
|
||||
Money totalRepaymentExpected = principalToBeScheduled.zero();
|
||||
Money totalRepaymentExpected;
|
||||
|
||||
// Actual period Number as per the schedule
|
||||
int periodNumber = 1;
|
||||
@@ -2180,7 +2235,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
// tranche
|
||||
// details to map
|
||||
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true);
|
||||
periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true, loanScheduleParams);
|
||||
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
|
||||
outstandingBalanceAsPerRest = outstandingBalance;
|
||||
principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt);
|
||||
@@ -2273,6 +2328,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
getPrincipalToBeScheduled(loanApplicationTerms));
|
||||
createDownPaymentPeriod(loanApplicationTerms, loanScheduleParams, periods, disburseDetail.getKey(),
|
||||
disbursementPeriod.principalDue());
|
||||
}
|
||||
// updates actual outstanding balance with new
|
||||
// disbursement detail
|
||||
outstandingBalance = outstandingBalance.plus(disburseDetail.getValue());
|
||||
@@ -2388,9 +2450,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
}
|
||||
// for complete schedule generation
|
||||
if (loanScheduleParams == null) {
|
||||
loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation);
|
||||
periods.clear();
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
getPrincipalToBeScheduled(loanApplicationTerms));
|
||||
periods.clear();
|
||||
} else {
|
||||
loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation);
|
||||
periods.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (retainedInstallments.size() > 0
|
||||
@@ -2402,7 +2471,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
loanScheduleParams);
|
||||
|
||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) {
|
||||
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
|
||||
if (loanScheduleModelPeriod.isRepaymentPeriod() || loanScheduleModelPeriod.isDownPaymentPeriod()) {
|
||||
// adding newly created repayment periods to installments
|
||||
addLoanRepaymentScheduleInstallment(retainedInstallments, loanScheduleModelPeriod);
|
||||
}
|
||||
@@ -2598,13 +2667,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
||||
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
||||
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
||||
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
||||
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
|
||||
scheduledLoanInstallment.rescheduleInterestPortion());
|
||||
scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod());
|
||||
installments.add(installment);
|
||||
}
|
||||
return installment;
|
||||
|
||||
+5
@@ -69,6 +69,11 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDownPaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return null;
|
||||
|
||||
+5
@@ -80,6 +80,11 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDownPaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return this.periodNumber;
|
||||
|
||||
+11
-7
@@ -445,13 +445,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
|
||||
rescheduledRepaymentDate);
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
boolean downPaymentEnabled = loan.repaymentScheduleDetail().isEnableDownPayment();
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || downPaymentEnabled) {
|
||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
if (isPaymentTypeApplicableForDisbursementCharge) {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail,
|
||||
downPaymentEnabled);
|
||||
} else {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null, downPaymentEnabled);
|
||||
}
|
||||
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
|
||||
loan.handleDownPayment(amountToDisburse.getAmount(), command, scheduleGeneratorDTO);
|
||||
@@ -730,13 +732,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
|
||||
rescheduledRepaymentDate);
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
boolean downPaymentEnabled = loan.repaymentScheduleDetail().isEnableDownPayment();
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || downPaymentEnabled) {
|
||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||
}
|
||||
if (configurationDomainService.isPaymentTypeApplicableForDisbursementCharge()) {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail,
|
||||
downPaymentEnabled);
|
||||
} else {
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null, downPaymentEnabled);
|
||||
}
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
@@ -2467,7 +2471,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
|
||||
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
|
||||
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
||||
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
|
||||
|
||||
@@ -142,4 +142,5 @@
|
||||
<include file="parts/0120_transaction_summary_with_asset_owner_report_typo_fix.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0121_transaction_summary_with_asset_owner_report_typo_fix_2.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0122_add_batch_job_execution_params_index.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0123_add_is_down_payment_to_repayment_schedule.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_loan_repayment_schedule">
|
||||
<column name="is_down_payment" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 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 org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.builder.ResponseSpecBuilder;
|
||||
import io.restassured.http.ContentType;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import io.restassured.specification.ResponseSpecification;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
|
||||
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanRepaymentScheduleWithDownPaymentTest {
|
||||
|
||||
private ResponseSpecification responseSpec;
|
||||
private RequestSpecification requestSpec;
|
||||
private LoanTransactionHelper loanTransactionHelper;
|
||||
private ClientHelper clientHelper;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
Utils.initializeRESTAssured();
|
||||
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loanRepaymentScheduleWithSimpleDisbursementAndDownPayment() {
|
||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
||||
|
||||
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
|
||||
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
|
||||
delinquencyBucketId);
|
||||
|
||||
Boolean enableDownPayment = true;
|
||||
BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25);
|
||||
Boolean enableAutoRepaymentForDownPayment = false;
|
||||
|
||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||
|
||||
Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment,
|
||||
"25", enableAutoRepaymentForDownPayment, false);
|
||||
|
||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
|
||||
assertNotNull(getLoanProductsProductResponse);
|
||||
assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment());
|
||||
assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
|
||||
.compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
final Integer loanId = createApproveAndDisburseLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
||||
|
||||
assertNotNull(loanDetails);
|
||||
assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
|
||||
assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
List<GetLoansLoanIdRepaymentPeriod> periods = loanDetails.getRepaymentSchedule().getPeriods();
|
||||
Double expectedDownPaymentAmount = 250.00;
|
||||
LocalDate expectedDownPaymentDueDate = LocalDate.of(2022, 9, 3);
|
||||
Double expectedRepaymentAmount = 750.00;
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loanRepaymentScheduleWithSimpleDisbursementAndAutoRepaymentDownPayment() {
|
||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
||||
|
||||
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
|
||||
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
|
||||
delinquencyBucketId);
|
||||
|
||||
Boolean enableDownPayment = true;
|
||||
BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25);
|
||||
Boolean enableAutoRepaymentForDownPayment = true;
|
||||
|
||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||
|
||||
Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment,
|
||||
"25", enableAutoRepaymentForDownPayment, false);
|
||||
|
||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
|
||||
assertNotNull(getLoanProductsProductResponse);
|
||||
assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment());
|
||||
assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
|
||||
.compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
final Integer loanId = createApproveAndDisburseLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
||||
|
||||
assertNotNull(loanDetails);
|
||||
assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
|
||||
assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
List<GetLoansLoanIdRepaymentPeriod> periods = loanDetails.getRepaymentSchedule().getPeriods();
|
||||
Double expectedDownPaymentAmount = 250.00;
|
||||
LocalDate expectedDownPaymentDueDate = LocalDate.of(2022, 9, 3);
|
||||
Double expectedRepaymentAmount = 750.00;
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalPaidForPeriod()) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertEquals(expectedRepaymentAmount, loanDetails.getSummary().getTotalOutstanding());
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loanRepaymentScheduleWithMultiDisbursementProductOneDisbursementAndDownPayment() {
|
||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
||||
|
||||
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
|
||||
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
|
||||
delinquencyBucketId);
|
||||
|
||||
Boolean enableDownPayment = true;
|
||||
BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25);
|
||||
Boolean enableAutoRepaymentForDownPayment = false;
|
||||
|
||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||
|
||||
Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment,
|
||||
"25", enableAutoRepaymentForDownPayment, true);
|
||||
|
||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
|
||||
assertNotNull(getLoanProductsProductResponse);
|
||||
assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment());
|
||||
assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
|
||||
.compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
final Integer loanId = createApproveAndDisburseLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
||||
|
||||
assertNotNull(loanDetails);
|
||||
assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
|
||||
assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
List<GetLoansLoanIdRepaymentPeriod> periods = loanDetails.getRepaymentSchedule().getPeriods();
|
||||
Double expectedDownPaymentAmount = 250.00;
|
||||
LocalDate expectedDownPaymentDueDate = LocalDate.of(2022, 9, 3);
|
||||
Double expectedRepaymentAmount = 750.00;
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loanRepaymentScheduleWithMultiDisbursementProductTwoDisbursementAndDownPayment() {
|
||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
||||
|
||||
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
|
||||
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
|
||||
delinquencyBucketId);
|
||||
|
||||
Boolean enableDownPayment = true;
|
||||
BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25);
|
||||
Boolean enableAutoRepaymentForDownPayment = false;
|
||||
|
||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||
|
||||
Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment,
|
||||
"25", enableAutoRepaymentForDownPayment, true);
|
||||
|
||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
|
||||
assertNotNull(getLoanProductsProductResponse);
|
||||
assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment());
|
||||
assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
|
||||
.compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
final Integer loanId = createApproveAndDisburseTwiceLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
||||
|
||||
assertNotNull(loanDetails);
|
||||
assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
|
||||
assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
List<GetLoansLoanIdRepaymentPeriod> periods = loanDetails.getRepaymentSchedule().getPeriods();
|
||||
Double expectedFirstDownPaymentAmount = 175.00;
|
||||
LocalDate expectedFirstDownPaymentDueDate = LocalDate.of(2022, 9, 3);
|
||||
Double expectedSecondDownPaymentAmount = 75.00;
|
||||
LocalDate expectedSecondDownPaymentDueDate = LocalDate.of(2022, 9, 4);
|
||||
Double expectedRepaymentAmount = 750.00;
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedFirstDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
&& expectedFirstDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedSecondDownPaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
&& expectedSecondDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loanRepaymentScheduleWithMultiDisbursementProductTwoDisbursementAndAutoRepaymentDownPayment() {
|
||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
||||
|
||||
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
|
||||
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
|
||||
delinquencyBucketId);
|
||||
|
||||
Boolean enableDownPayment = true;
|
||||
BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25);
|
||||
Boolean enableAutoRepaymentForDownPayment = true;
|
||||
|
||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||
|
||||
Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment,
|
||||
"25", enableAutoRepaymentForDownPayment, true);
|
||||
|
||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
|
||||
assertNotNull(getLoanProductsProductResponse);
|
||||
assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment());
|
||||
assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment()
|
||||
.compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
final Integer loanId = createApproveAndDisburseTwiceLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue());
|
||||
|
||||
assertNotNull(loanDetails);
|
||||
assertEquals(enableDownPayment, loanDetails.getEnableDownPayment());
|
||||
assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment));
|
||||
assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment());
|
||||
|
||||
List<GetLoansLoanIdRepaymentPeriod> periods = loanDetails.getRepaymentSchedule().getPeriods();
|
||||
Double expectedFirstDownPaymentAmount = 175.00;
|
||||
LocalDate expectedFirstDownPaymentDueDate = LocalDate.of(2022, 9, 3);
|
||||
Double expectedSecondDownPaymentAmount = 75.00;
|
||||
LocalDate expectedSecondDownPaymentDueDate = LocalDate.of(2022, 9, 4);
|
||||
Double expectedRepaymentAmount = 750.00;
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedFirstDownPaymentAmount.equals(period.getTotalPaidForPeriod()) //
|
||||
&& expectedFirstDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedSecondDownPaymentAmount.equals(period.getTotalPaidForPeriod())
|
||||
&& expectedSecondDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
assertEquals(expectedRepaymentAmount, loanDetails.getSummary().getTotalOutstanding());
|
||||
}
|
||||
|
||||
private Integer createLoanProductWithDownPaymentConfiguration(final LoanTransactionHelper loanTransactionHelper,
|
||||
final Integer delinquencyBucketId, Boolean enableDownPayment, String disbursedAmountPercentageForDownPayment,
|
||||
Boolean enableAutoRepaymentForDownPayment, boolean multiDisbursement) {
|
||||
HashMap<String, Object> loanProductMap;
|
||||
if (multiDisbursement) {
|
||||
loanProductMap = new LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments() //
|
||||
.withInterestTypeAsDecliningBalance().withMoratorium("", "").withInterestCalculationPeriodTypeAsRepaymentPeriod(true)
|
||||
.withInterestTypeAsDecliningBalance() //
|
||||
.withMultiDisburse() //
|
||||
.withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment) //
|
||||
.withDisallowExpectedDisbursements(true) //
|
||||
.build(null, delinquencyBucketId);
|
||||
} else {
|
||||
loanProductMap = new LoanProductTestBuilder() //
|
||||
.withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment) //
|
||||
.build(null, delinquencyBucketId);
|
||||
}
|
||||
final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
|
||||
return loanProductId;
|
||||
}
|
||||
|
||||
private Integer createApproveAndDisburseLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) {
|
||||
|
||||
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
|
||||
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
|
||||
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
|
||||
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
|
||||
.withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
|
||||
.withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null);
|
||||
|
||||
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null);
|
||||
loanTransactionHelper.disburseLoanWithTransactionAmount("03 September 2022", loanId, "1000");
|
||||
return loanId;
|
||||
}
|
||||
|
||||
private Integer createApproveAndDisburseTwiceLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) {
|
||||
|
||||
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
|
||||
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
|
||||
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
|
||||
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
|
||||
.withExpectedDisbursementDate("04 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
|
||||
.withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null);
|
||||
|
||||
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null);
|
||||
loanTransactionHelper.disburseLoanWithTransactionAmount("03 September 2022", loanId, "700");
|
||||
loanTransactionHelper.disburseLoanWithTransactionAmount("04 September 2022", loanId, "300");
|
||||
return loanId;
|
||||
}
|
||||
}
|
||||
+3
@@ -211,6 +211,9 @@ public class LoanTransactionChargebackTest {
|
||||
final Long transactionId = loanIdTransactionsResponse.getResourceId();
|
||||
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("666.67"));
|
||||
|
||||
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
|
||||
assertNotNull(getLoansLoanIdResponse);
|
||||
|
||||
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, amount.toString(),
|
||||
0, responseSpec);
|
||||
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("666.67"));
|
||||
|
||||
Reference in New Issue
Block a user