[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();
|
this.repaymentScheduleInstallments.clear();
|
||||||
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
|
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
|
||||||
|
|
||||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||||
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this,
|
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this,
|
||||||
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
||||||
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
|
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
|
||||||
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
|
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
|
||||||
scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(),
|
scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(),
|
||||||
scheduledLoanInstallment.getLoanCompoundingDetails());
|
scheduledLoanInstallment.getLoanCompoundingDetails(), scheduledLoanInstallment.rescheduleInterestPortion(),
|
||||||
|
scheduledLoanInstallment.isDownPaymentPeriod());
|
||||||
addLoanRepaymentScheduleInstallment(installment);
|
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,
|
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);
|
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE);
|
||||||
|
|
||||||
@@ -2495,7 +2496,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
|
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
|
||||||
|
|
||||||
regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(),
|
regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(),
|
||||||
isDisbursementMissed(), scheduleGeneratorDTO);
|
isDisbursementMissed(), scheduleGeneratorDTO, downPaymentEnabled);
|
||||||
|
|
||||||
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||||
@@ -2527,14 +2528,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam,
|
private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam,
|
||||||
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean downPaymentEnabled) {
|
||||||
|
|
||||||
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||||
if (interestRecalculationEnabledParam
|
if ((interestRecalculationEnabledParam
|
||||||
&& (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam)) {
|
&& (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam))) {
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||||
|
} else if (downPaymentEnabled) {
|
||||||
|
LoanScheduleDTO loanSchedule = getRecalculatedSchedule(scheduleGeneratorDTO);
|
||||||
|
updateLoanSchedule(loanSchedule.getInstallments());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LoanDisbursementDetails> getDisbursedLoanDisbursementDetails() {
|
private List<LoanDisbursementDetails> getDisbursedLoanDisbursementDetails() {
|
||||||
@@ -2965,7 +2968,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
externalId = ExternalId.generate();
|
externalId = ExternalId.generate();
|
||||||
}
|
}
|
||||||
Money downPaymentMoney = Money.of(getCurrency(),
|
Money downPaymentMoney = Money.of(getCurrency(),
|
||||||
MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 9));
|
MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 19));
|
||||||
LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn,
|
LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn,
|
||||||
externalId);
|
externalId);
|
||||||
/*
|
/*
|
||||||
@@ -5553,7 +5556,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
||||||
|
|
||||||
LocalDate lastTransactionDate = getLastUserTransactionDate();
|
LocalDate lastTransactionDate = getLastUserTransactionDate();
|
||||||
final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO);
|
final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO);
|
||||||
if (loanSchedule == null) {
|
if (loanSchedule == null) {
|
||||||
@@ -5788,15 +5790,15 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
|
private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
|
||||||
|
if (!this.repaymentScheduleDetail().isEnableDownPayment()
|
||||||
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || isChargedOff()) {
|
&& (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || isChargedOff())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||||
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
|
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
|
||||||
|
|
||||||
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||||
final MathContext mc = new MathContext(8, roundingMode);
|
final MathContext mc = new MathContext(19, roundingMode);
|
||||||
|
|
||||||
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO);
|
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)
|
@Column(name = "credits_amount", scale = 6, precision = 19, nullable = true)
|
||||||
private BigDecimal credits;
|
private BigDecimal credits;
|
||||||
|
|
||||||
|
@Column(name = "is_down_payment", nullable = false)
|
||||||
|
private boolean isDownPayment;
|
||||||
|
|
||||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
|
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
|
||||||
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
|
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 LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||||
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
|
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
|
||||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails, final BigDecimal rescheduleInterestPortion) {
|
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.loan = loan;
|
||||||
this.installmentNumber = installmentNumber;
|
this.installmentNumber = installmentNumber;
|
||||||
this.fromDate = fromDate;
|
this.fromDate = fromDate;
|
||||||
@@ -169,6 +181,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
}
|
}
|
||||||
this.loanCompoundingDetails = compoundingDetails;
|
this.loanCompoundingDetails = compoundingDetails;
|
||||||
this.rescheduleInterestPortion = rescheduleInterestPortion;
|
this.rescheduleInterestPortion = rescheduleInterestPortion;
|
||||||
|
this.isDownPayment = isDownPayment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
|
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
|
||||||
|
|||||||
+60
@@ -91,6 +91,11 @@ public final class LoanSchedulePeriodData {
|
|||||||
totalInstallmentAmountForPeriod);
|
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,
|
public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnings("unused") final Long loanId,
|
||||||
final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate,
|
final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate,
|
||||||
final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid,
|
final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid,
|
||||||
@@ -265,6 +270,61 @@ public final class LoanSchedulePeriodData {
|
|||||||
this.totalCredits = BigDecimal.ZERO;
|
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
|
* Used for creating loan schedule periods with full information on expected principal, interest & charges along
|
||||||
* with what portion of each is paid.
|
* with what portion of each is paid.
|
||||||
|
|||||||
+8
@@ -1737,4 +1737,12 @@ public final class LoanApplicationTerms {
|
|||||||
public void setNewScheduledDueDateStart(LocalDate newScheduledDueDateStart) {
|
public void setNewScheduledDueDateStart(LocalDate newScheduledDueDateStart) {
|
||||||
this.newScheduledDueDateStart = 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 isRepaymentPeriod();
|
||||||
|
|
||||||
|
boolean isDownPaymentPeriod();
|
||||||
|
|
||||||
Integer periodNumber();
|
Integer periodNumber();
|
||||||
|
|
||||||
LocalDate periodFromDate();
|
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 Collection<RecalculationDetail> recalculationDetails = null;
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
|
||||||
final LocalDate scheduleTillDate = null;
|
final LocalDate scheduleTillDate = null;
|
||||||
final boolean applyInterestRecalculation = false;
|
|
||||||
final Money unCompoundedAmount = Money.zero(currency);
|
final Money unCompoundedAmount = Money.zero(currency);
|
||||||
|
final boolean applyInterestRecalculation = false;
|
||||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||||
|
|||||||
+95
-26
@@ -29,9 +29,11 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
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.ApplicationCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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.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.data.LoanScheduleParams;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
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 MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||||
final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions();
|
final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions();
|
||||||
LoanScheduleParams scheduleParams = null;
|
LoanScheduleParams scheduleParams;
|
||||||
if (loanScheduleParams == null) {
|
if (loanScheduleParams == null) {
|
||||||
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
|
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
|
||||||
loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms));
|
loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms));
|
||||||
@@ -107,7 +110,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
Collection<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
Collection<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||||
if (!scheduleParams.isPartialUpdate()) {
|
if (!scheduleParams.isPartialUpdate()) {
|
||||||
periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, loanApplicationTerms,
|
periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, loanApplicationTerms,
|
||||||
chargesDueAtTimeOfDisbursement);
|
chargesDueAtTimeOfDisbursement, scheduleParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the total interest owed over the full loan for FLAT
|
// 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);
|
loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
|
||||||
|
|
||||||
if (!scheduleParams.isPartialUpdate()) {
|
if (!scheduleParams.isPartialUpdate()) {
|
||||||
|
Long notDownPaymentPeriodNumber = periods.stream().filter(period -> !period.isDownPaymentPeriod()).count();
|
||||||
// Set Fixed Principal Amount
|
// Set Fixed Principal Amount
|
||||||
updateAmortization(mc, loanApplicationTerms, scheduleParams.getPeriodNumber(), scheduleParams.getOutstandingBalance());
|
updateAmortization(mc, loanApplicationTerms, notDownPaymentPeriodNumber.intValue(), scheduleParams.getOutstandingBalance());
|
||||||
|
|
||||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||||
/* fetches the first tranche amount and also updates other tranche details to map */
|
/* fetches the first tranche amount and also updates other tranche details to map */
|
||||||
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), periods,
|
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), periods,
|
||||||
chargesDueAtTimeOfDisbursement, scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation());
|
chargesDueAtTimeOfDisbursement, scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation(),
|
||||||
|
scheduleParams);
|
||||||
scheduleParams.setPrincipalToBeScheduled(Money.of(currency, disburseAmt));
|
scheduleParams.setPrincipalToBeScheduled(Money.of(currency, disburseAmt));
|
||||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
|
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
|
||||||
scheduleParams.setOutstandingBalance(Money.of(currency, disburseAmt));
|
|
||||||
scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, disburseAmt));
|
scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, disburseAmt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,6 +182,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
isFirstRepayment = false;
|
isFirstRepayment = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
|
while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
|
||||||
LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
|
LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
|
||||||
scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator
|
scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator
|
||||||
@@ -976,6 +981,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||||
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||||
periods.add(disbursementPeriod);
|
periods.add(disbursementPeriod);
|
||||||
|
|
||||||
|
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||||
|
createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, disburseDetail.getKey(),
|
||||||
|
disburseDetail.getValue().getAmount());
|
||||||
|
}
|
||||||
|
|
||||||
// updates actual outstanding balance with new
|
// updates actual outstanding balance with new
|
||||||
// disbursement detail
|
// disbursement detail
|
||||||
scheduleParams.addOutstandingBalance(disburseDetail.getValue());
|
scheduleParams.addOutstandingBalance(disburseDetail.getValue());
|
||||||
@@ -1461,7 +1472,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation,
|
private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation,
|
||||||
LocalDate repaymentDate, final Collection<RecalculationDetail> transactions) {
|
LocalDate repaymentDate, final Collection<RecalculationDetail> transactions) {
|
||||||
Collection<RecalculationDetail> applicableTransactions = new ArrayList<>();
|
Collection<RecalculationDetail> applicableTransactions = new ArrayList<>();
|
||||||
if (applyInterestRecalculation) {
|
if (applyInterestRecalculation && !Objects.isNull(transactions)) {
|
||||||
for (RecalculationDetail detail : transactions) {
|
for (RecalculationDetail detail : transactions) {
|
||||||
if (!detail.getTransactionDate().isAfter(repaymentDate)) {
|
if (!detail.getTransactionDate().isAfter(repaymentDate)) {
|
||||||
applicableTransactions.add(detail);
|
applicableTransactions.add(detail);
|
||||||
@@ -1912,26 +1923,38 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
|
|
||||||
private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate,
|
private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate,
|
||||||
final Collection<LoanScheduleModelPeriod> periods, final BigDecimal chargesDueAtTimeOfDisbursement,
|
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
|
// 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;
|
BigDecimal principal = BigDecimal.ZERO;
|
||||||
|
scheduleParams.setOutstandingBalance(Money.zero(currency));
|
||||||
if (loanApplicationTerms.getDisbursementDatas().size() == 0) {
|
if (loanApplicationTerms.getDisbursementDatas().size() == 0) {
|
||||||
// non tranche loans have no disbursement data entries in submitted and approved status
|
// 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
|
// the appropriate approved amount or applied for amount is used to show a proposed schedule
|
||||||
if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) {
|
if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
principal = loanApplicationTerms.getApprovedPrincipal().getAmount();
|
principal = loanApplicationTerms.getApprovedPrincipal().getAmount();
|
||||||
|
scheduleParams.addOutstandingBalance(Money.of(currency, principal));
|
||||||
} else {
|
} else {
|
||||||
principal = loanApplicationTerms.getPrincipal().getAmount();
|
principal = loanApplicationTerms.getPrincipal().getAmount();
|
||||||
|
scheduleParams.addOutstandingBalance(Money.of(currency, principal));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
|
|
||||||
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
|
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
|
||||||
if (disbursementData.disbursementDate().equals(disbursementDate)) {
|
if (disbursementData.disbursementDate().equals(disbursementDate)) {
|
||||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||||
disbursementData.disbursementDate(), Money.of(currency, disbursementData.getPrincipal()),
|
disbursementData.disbursementDate(), Money.of(currency, disbursementData.getPrincipal()),
|
||||||
chargesDueAtTimeOfDisbursement);
|
chargesDueAtTimeOfDisbursement);
|
||||||
periods.add(disbursementPeriod);
|
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());
|
principal = principal.add(disbursementData.getPrincipal());
|
||||||
} else if (!excludePastUndisbursed || disbursementData.isDisbursed()
|
} else if (!excludePastUndisbursed || disbursementData.isDisbursed()
|
||||||
|| !disbursementData.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) {
|
|| !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
|
* 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
|
* 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;
|
BigDecimal sumToNow = BigDecimal.ZERO;
|
||||||
if (prevsum != null) {
|
if (prevsum != null) {
|
||||||
sumToNow = prevsum.getAmount();
|
sumToNow = prevsum.getAmount();
|
||||||
}
|
}
|
||||||
sumToNow = sumToNow.add(disbursementData.getPrincipal());
|
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,
|
private Collection<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final int numberOfRepayments,
|
||||||
final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement,
|
||||||
|
LoanScheduleParams scheduleParams) {
|
||||||
Collection<LoanScheduleModelPeriod> periods = null;
|
Collection<LoanScheduleModelPeriod> periods;
|
||||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||||
int multiDisbursalTrancheEntries = loanApplicationTerms.getDisbursementDatas().size();
|
int multiDisbursalTrancheEntries = loanApplicationTerms.getDisbursementDatas().size();
|
||||||
if (multiDisbursalTrancheEntries == 0) {
|
if (multiDisbursalTrancheEntries == 0) {
|
||||||
@@ -1968,11 +1991,35 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||||
.disbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
.disbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
||||||
periods.add(disbursementPeriod);
|
periods.add(disbursementPeriod);
|
||||||
|
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||||
|
createDownPaymentPeriod(loanApplicationTerms, scheduleParams, periods, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||||
|
loanApplicationTerms.getPrincipal().getAmount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return periods;
|
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) {
|
private Set<LoanCharge> seperateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
|
||||||
Set<LoanCharge> interestCharges = new HashSet<>();
|
Set<LoanCharge> interestCharges = new HashSet<>();
|
||||||
for (final LoanCharge loanCharge : loanCharges) {
|
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,
|
private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, Loan loan,
|
||||||
final HolidayDetailDTO holidayDetailDTO,
|
final HolidayDetailDTO holidayDetailDTO,
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate rescheduleFrom,
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate rescheduleFrom,
|
||||||
final LocalDate scheduleTillDate) {
|
final LocalDate scheduleTillDate) {
|
||||||
// Loan transactions to process and find the variation on payments
|
// Loan transactions to process and find the variation on payments
|
||||||
Collection<RecalculationDetail> recalculationDetails = new ArrayList<>();
|
Collection<RecalculationDetail> recalculationDetails = new ArrayList<>();
|
||||||
List<LoanTransaction> transactions = loan.getLoanTransactions();
|
List<LoanTransaction> transactions = loan.getLoanTransactions();
|
||||||
|
MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||||
|
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loan.getActiveCharges());
|
||||||
for (LoanTransaction loanTransaction : transactions) {
|
for (LoanTransaction loanTransaction : transactions) {
|
||||||
if (loanTransaction.isPaymentTransaction()) {
|
if (loanTransaction.isPaymentTransaction()) {
|
||||||
recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(),
|
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
|
// this block is to retain the schedule installments prior to the
|
||||||
// provided date and creates late and early payment details for further
|
// provided date and creates late and early payment details for further
|
||||||
// calculations
|
// calculations
|
||||||
|
if (Objects.isNull(rescheduleFrom) && loanApplicationTerms.isDownPaymentEnabled()) {
|
||||||
|
rescheduleFrom = loanApplicationTerms.getExpectedDisbursementDate();
|
||||||
|
}
|
||||||
if (rescheduleFrom != null) {
|
if (rescheduleFrom != null) {
|
||||||
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
|
Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms);
|
||||||
// actual outstanding balance for interest calculation
|
// 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
|
// this is required to update total fee amounts in the
|
||||||
// LoanScheduleModel
|
// 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(),
|
periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(),
|
||||||
loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
loanApplicationTerms, chargesDueAtTimeOfDisbursement, loanScheduleParams);
|
||||||
MonetaryCurrency currency = outstandingBalance.getCurrency();
|
|
||||||
|
|
||||||
// early payments will be added here and as per the selected
|
// early payments will be added here and as per the selected
|
||||||
// strategy
|
// strategy
|
||||||
@@ -2160,7 +2215,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
Money totalCumulativeInterest = principalToBeScheduled.zero();
|
Money totalCumulativeInterest = principalToBeScheduled.zero();
|
||||||
Money totalFeeChargesCharged = principalToBeScheduled.zero().plus(chargesDueAtTimeOfDisbursement);
|
Money totalFeeChargesCharged = principalToBeScheduled.zero().plus(chargesDueAtTimeOfDisbursement);
|
||||||
Money totalPenaltyChargesCharged = principalToBeScheduled.zero();
|
Money totalPenaltyChargesCharged = principalToBeScheduled.zero();
|
||||||
Money totalRepaymentExpected = principalToBeScheduled.zero();
|
Money totalRepaymentExpected;
|
||||||
|
|
||||||
// Actual period Number as per the schedule
|
// Actual period Number as per the schedule
|
||||||
int periodNumber = 1;
|
int periodNumber = 1;
|
||||||
@@ -2180,7 +2235,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
// tranche
|
// tranche
|
||||||
// details to map
|
// details to map
|
||||||
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(),
|
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||||
periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true);
|
periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true, loanScheduleParams);
|
||||||
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
|
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
|
||||||
outstandingBalanceAsPerRest = outstandingBalance;
|
outstandingBalanceAsPerRest = outstandingBalance;
|
||||||
principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt);
|
principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt);
|
||||||
@@ -2273,6 +2328,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||||
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||||
periods.add(disbursementPeriod);
|
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
|
// updates actual outstanding balance with new
|
||||||
// disbursement detail
|
// disbursement detail
|
||||||
outstandingBalance = outstandingBalance.plus(disburseDetail.getValue());
|
outstandingBalance = outstandingBalance.plus(disburseDetail.getValue());
|
||||||
@@ -2388,9 +2450,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
}
|
}
|
||||||
// for complete schedule generation
|
// for complete schedule generation
|
||||||
if (loanScheduleParams == null) {
|
if (loanScheduleParams == null) {
|
||||||
loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails,
|
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation);
|
loanScheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||||
periods.clear();
|
Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(),
|
||||||
|
getPrincipalToBeScheduled(loanApplicationTerms));
|
||||||
|
periods.clear();
|
||||||
|
} else {
|
||||||
|
loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails,
|
||||||
|
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation);
|
||||||
|
periods.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retainedInstallments.size() > 0
|
if (retainedInstallments.size() > 0
|
||||||
@@ -2402,7 +2471,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
loanScheduleParams);
|
loanScheduleParams);
|
||||||
|
|
||||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) {
|
for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) {
|
||||||
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
|
if (loanScheduleModelPeriod.isRepaymentPeriod() || loanScheduleModelPeriod.isDownPaymentPeriod()) {
|
||||||
// adding newly created repayment periods to installments
|
// adding newly created repayment periods to installments
|
||||||
addLoanRepaymentScheduleInstallment(retainedInstallments, loanScheduleModelPeriod);
|
addLoanRepaymentScheduleInstallment(retainedInstallments, loanScheduleModelPeriod);
|
||||||
}
|
}
|
||||||
@@ -2598,13 +2667,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
|||||||
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||||
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
||||||
LoanRepaymentScheduleInstallment installment = null;
|
LoanRepaymentScheduleInstallment installment = null;
|
||||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||||
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
||||||
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
||||||
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
||||||
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
||||||
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
|
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
|
||||||
scheduledLoanInstallment.rescheduleInterestPortion());
|
scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod());
|
||||||
installments.add(installment);
|
installments.add(installment);
|
||||||
}
|
}
|
||||||
return installment;
|
return installment;
|
||||||
|
|||||||
+5
@@ -69,6 +69,11 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDownPaymentPeriod() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer periodNumber() {
|
public Integer periodNumber() {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+5
@@ -80,6 +80,11 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDownPaymentPeriod() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer periodNumber() {
|
public Integer periodNumber() {
|
||||||
return this.periodNumber;
|
return this.periodNumber;
|
||||||
|
|||||||
+11
-7
@@ -445,13 +445,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
|
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
|
||||||
rescheduledRepaymentDate);
|
rescheduledRepaymentDate);
|
||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
boolean downPaymentEnabled = loan.repaymentScheduleDetail().isEnableDownPayment();
|
||||||
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || downPaymentEnabled) {
|
||||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
if (isPaymentTypeApplicableForDisbursementCharge) {
|
if (isPaymentTypeApplicableForDisbursementCharge) {
|
||||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail,
|
||||||
|
downPaymentEnabled);
|
||||||
} else {
|
} else {
|
||||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null, downPaymentEnabled);
|
||||||
}
|
}
|
||||||
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
|
loan.adjustNetDisbursalAmount(amountToDisburse.getAmount());
|
||||||
loan.handleDownPayment(amountToDisburse.getAmount(), command, scheduleGeneratorDTO);
|
loan.handleDownPayment(amountToDisburse.getAmount(), command, scheduleGeneratorDTO);
|
||||||
@@ -730,13 +732,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||||
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
|
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate,
|
||||||
rescheduledRepaymentDate);
|
rescheduledRepaymentDate);
|
||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
boolean downPaymentEnabled = loan.repaymentScheduleDetail().isEnableDownPayment();
|
||||||
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || downPaymentEnabled) {
|
||||||
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
createAndSaveLoanScheduleArchive(loan, scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
if (configurationDomainService.isPaymentTypeApplicableForDisbursementCharge()) {
|
if (configurationDomainService.isPaymentTypeApplicableForDisbursementCharge()) {
|
||||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail);
|
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail,
|
||||||
|
downPaymentEnabled);
|
||||||
} else {
|
} else {
|
||||||
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null);
|
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null, downPaymentEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!changes.isEmpty()) {
|
if (!changes.isEmpty()) {
|
||||||
@@ -2467,7 +2471,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
|
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
|
||||||
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||||
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
|
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
|
||||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||||
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
|
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
|
||||||
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
|
||||||
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
|
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/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/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/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>
|
</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();
|
final Long transactionId = loanIdTransactionsResponse.getResourceId();
|
||||||
reviewLoanTransactionRelations(loanId, transactionId, 0, Double.valueOf("666.67"));
|
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(),
|
final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId, transactionId, amount.toString(),
|
||||||
0, responseSpec);
|
0, responseSpec);
|
||||||
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("666.67"));
|
reviewLoanTransactionRelations(loanId, transactionId, 1, Double.valueOf("666.67"));
|
||||||
|
|||||||
Reference in New Issue
Block a user