diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index d8432f0cb..e89880f11 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -41,7 +41,6 @@ import jakarta.persistence.Version; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.math.MathContext; -import java.math.RoundingMode; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; @@ -2823,8 +2822,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO scheduleGeneratorDTO) { - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mc = new MathContext(8, roundingMode); + final MathContext mc = MoneyHelper.getMathContext(); final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO); LoanScheduleGenerator loanScheduleGenerator; @@ -5762,8 +5760,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory() .create(this.loanRepaymentScheduleDetail.getLoanScheduleType(), interestMethod); - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mc = new MathContext(19, roundingMode); + final MathContext mc = MoneyHelper.getMathContext(); final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO); @@ -5778,8 +5775,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { LoanRepaymentScheduleInstallment installment = null; if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mc = new MathContext(8, roundingMode); + + final MathContext mc = MoneyHelper.getMathContext(); final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 252886de6..42ec041a6 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -565,7 +565,7 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom { BigDecimal percentageOf = BigDecimal.ZERO; if (isGreaterThanZero(value)) { - final MathContext mc = new MathContext(8, MoneyHelper.getRoundingMode()); + final MathContext mc = MoneyHelper.getMathContext(); final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100L), mc); percentageOf = value.multiply(multiplicand, mc); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 52e43ac1e..19bee527a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Set; import lombok.extern.slf4j.Slf4j; 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; @@ -212,6 +213,7 @@ public final class LoanApplicationTerms { private LocalDate newScheduledDueDateStart; private boolean isDownPaymentEnabled; private BigDecimal disbursedAmountPercentageForDownPayment; + private Money downPaymentAmount; private boolean isAutoRepaymentForDownPaymentEnabled; private RepaymentStartDateType repaymentStartDateType; @@ -450,6 +452,15 @@ public final class LoanApplicationTerms { this.isPrincipalCompoundingDisabledForOverdueLoans = isPrincipalCompoundingDisabledForOverdueLoans; this.isDownPaymentEnabled = isDownPaymentEnabled; this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment; + this.downPaymentAmount = Money.zero(getCurrency()); + if (isDownPaymentEnabled) { + this.downPaymentAmount = Money.of(getCurrency(), + MathUtil.percentageOf(getPrincipal().getAmount(), getDisbursedAmountPercentageForDownPayment(), 19)); + if (getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, getInstallmentAmountInMultiplesOf()); + } + } + this.isAutoRepaymentForDownPaymentEnabled = isAutoRepaymentForDownPaymentEnabled; this.repaymentStartDateType = repaymentStartDateType; this.submittedOnDate = submittedOnDate; @@ -1817,4 +1828,8 @@ public final class LoanApplicationTerms { public LoanScheduleType getLoanScheduleType() { return loanScheduleType; } + + public Money getDownPaymentAmount() { + return downPaymentAmount; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index a06bcb780..b90d184d3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -1082,7 +1082,7 @@ final class LoansApiResourceSwagger { public Integer repaymentEvery; public GetLoansLoanIdRepaymentFrequencyType repaymentFrequencyType; @Schema(example = "24") - public Integer interestRatePerPeriod; + public BigDecimal interestRatePerPeriod; public GetLoansLoanIdInterestRateFrequencyType interestRateFrequencyType; @Schema(example = "24") public Integer annualInterestRate; @@ -1180,7 +1180,7 @@ final class LoansApiResourceSwagger { @Schema(example = "2") public Integer repaymentFrequencyType; @Schema(example = "2") - public Integer interestRatePerPeriod; + public BigDecimal interestRatePerPeriod; @Schema(example = "1") public Integer amortizationType; @Schema(example = "5.5") @@ -1318,7 +1318,7 @@ final class LoansApiResourceSwagger { @Schema(example = "0") public Integer repaymentFrequencyType; @Schema(example = "2") - public Integer interestRatePerPeriod; + public BigDecimal interestRatePerPeriod; @Schema(example = "0") public Integer interestType; @Schema(example = "0") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java index 282915b45..f73f67b19 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java @@ -180,7 +180,7 @@ public final class LoanScheduleParams { final Money principalToBeScheduled = null; final Money outstandingBalance = null; final Money outstandingBalanceAsPerRest = null; - final List installments = null; + final List installments = new ArrayList<>(); final boolean partialUpdate = false; final int loanTermInDays = 0; final Money totalOutstandingInterestPaymentDueToGrace = null; @@ -266,6 +266,10 @@ public final class LoanScheduleParams { return this.periodNumber; } + public void setPeriodNumber(int periodNumber) { + this.periodNumber = periodNumber; + } + public int getInstalmentNumber() { return this.instalmentNumber; } @@ -274,12 +278,20 @@ public final class LoanScheduleParams { return this.actualRepaymentDate; } + public void setActualRepaymentDate(LocalDate actualRepaymentDate) { + this.actualRepaymentDate = actualRepaymentDate; + } + public Money getTotalCumulativePrincipal() { return this.totalCumulativePrincipal; } public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) { - this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal); + if (this.totalCumulativePrincipal != null) { + this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal); + } else { + this.totalCumulativePrincipal = totalCumulativePrincipal; + } } public Money getTotalCumulativeInterest() { @@ -287,7 +299,11 @@ public final class LoanScheduleParams { } public void addTotalCumulativeInterest(final Money totalCumulativeInterest) { - this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest); + if (this.totalCumulativeInterest != null) { + this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest); + } else { + this.totalCumulativeInterest = totalCumulativeInterest; + } } public Money getTotalFeeChargesCharged() { @@ -295,7 +311,11 @@ public final class LoanScheduleParams { } public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) { - this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged); + if (this.totalFeeChargesCharged != null) { + this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged); + } else { + this.totalFeeChargesCharged = totalFeeChargesCharged; + } } public Money getTotalPenaltyChargesCharged() { @@ -303,7 +323,11 @@ public final class LoanScheduleParams { } public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) { - this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged); + if (this.totalPenaltyChargesCharged != null) { + this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged); + } else { + this.totalPenaltyChargesCharged = totalPenaltyChargesCharged; + } } public Money getTotalRepaymentExpected() { @@ -311,13 +335,21 @@ public final class LoanScheduleParams { } public void addTotalRepaymentExpected(final Money totalRepaymentExpected) { - this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected); + if (this.totalRepaymentExpected != null) { + this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected); + } else { + this.totalRepaymentExpected = totalRepaymentExpected; + } } public Money getReducePrincipal() { return this.reducePrincipal; } + public void setReducePrincipal(Money reducePrincipal) { + this.reducePrincipal = reducePrincipal; + } + public Map getPrincipalPortionMap() { return this.principalPortionMap; } @@ -338,10 +370,18 @@ public final class LoanScheduleParams { return this.outstandingBalance; } + public void setOutstandingBalance(Money outstandingBalance) { + this.outstandingBalance = outstandingBalance; + } + public Money getOutstandingBalanceAsPerRest() { return this.outstandingBalanceAsPerRest; } + public void setOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { + this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; + } + public List getInstallments() { return this.installments; } @@ -366,12 +406,16 @@ public final class LoanScheduleParams { return this.periodStartDate; } + public void setPeriodStartDate(LocalDate periodStartDate) { + this.periodStartDate = periodStartDate; + } + public Money getPrincipalToBeScheduled() { return this.principalToBeScheduled; } - public void setPeriodNumber(int periodNumber) { - this.periodNumber = periodNumber; + public void setPrincipalToBeScheduled(Money principalToBeScheduled) { + this.principalToBeScheduled = principalToBeScheduled; } public void incrementPeriodNumber() { @@ -382,56 +426,54 @@ public final class LoanScheduleParams { this.instalmentNumber++; } - public void setPeriodStartDate(LocalDate periodStartDate) { - this.periodStartDate = periodStartDate; - } - - public void setActualRepaymentDate(LocalDate actualRepaymentDate) { - this.actualRepaymentDate = actualRepaymentDate; - } - - public void setReducePrincipal(Money reducePrincipal) { - this.reducePrincipal = reducePrincipal; - } - public void addReducePrincipal(Money reducePrincipal) { - this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal); + if (this.reducePrincipal != null) { + this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal); + } else { + this.reducePrincipal = reducePrincipal; + } } public void reduceReducePrincipal(Money reducePrincipal) { - this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal); - } - - public void setPrincipalToBeScheduled(Money principalToBeScheduled) { - this.principalToBeScheduled = principalToBeScheduled; + if (this.reducePrincipal != null) { + this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal); + } } public void addPrincipalToBeScheduled(Money principalToBeScheduled) { - this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled); - } - - public void setOutstandingBalance(Money outstandingBalance) { - this.outstandingBalance = outstandingBalance; + if (this.principalToBeScheduled != null) { + this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled); + } else { + this.principalToBeScheduled = principalToBeScheduled; + } } public void addOutstandingBalance(Money outstandingBalance) { - this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance); + if (this.outstandingBalance != null) { + this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance); + } else { + this.outstandingBalance = outstandingBalance; + } } public void reduceOutstandingBalance(Money outstandingBalance) { - this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance); - } - - public void setOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { - this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; + if (this.outstandingBalance != null) { + this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance); + } } public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { - this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest); + if (this.outstandingBalanceAsPerRest != null) { + this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest); + } else { + this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; + } } public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { - this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest); + if (this.outstandingBalanceAsPerRest != null) { + this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest); + } } public int getLoanTermInDays() { @@ -466,16 +508,22 @@ public final class LoanScheduleParams { return this.unCompoundedAmount; } + public void setUnCompoundedAmount(Money unCompoundedAmount) { + this.unCompoundedAmount = unCompoundedAmount; + } + public void addUnCompoundedAmount(Money unCompoundedAmount) { - this.unCompoundedAmount = this.unCompoundedAmount.plus(unCompoundedAmount); + if (this.unCompoundedAmount != null) { + this.unCompoundedAmount = this.unCompoundedAmount.plus(unCompoundedAmount); + } else { + this.unCompoundedAmount = unCompoundedAmount; + } } public void minusUnCompoundedAmount(Money unCompoundedAmount) { - this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount); - } - - public void setUnCompoundedAmount(Money unCompoundedAmount) { - this.unCompoundedAmount = unCompoundedAmount; + if (this.unCompoundedAmount != null) { + this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount); + } } public boolean isFirstPeriod() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java index 957ede516..d2acafeee 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java @@ -132,18 +132,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms); if (!scheduleParams.isPartialUpdate()) { - Money downPaymentAmount = Money.zero(currency); - if (loanApplicationTerms.isDownPaymentEnabled()) { - downPaymentAmount = Money.of(currency, MathUtil.percentageOf(scheduleParams.getOutstandingBalance().getAmount(), - loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); - if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { - downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, - loanApplicationTerms.getInstallmentAmountInMultiplesOf()); - } - - } - Money calculatedAmortizableAmount = loanApplicationTerms.getPrincipal().minus(downPaymentAmount); - scheduleParams.reduceOutstandingBalance(downPaymentAmount); + Money calculatedAmortizableAmount = loanApplicationTerms.getPrincipal().minus(loanApplicationTerms.getDownPaymentAmount()); // Set Fixed Principal Amount updateAmortization(mc, loanApplicationTerms, scheduleParams.getPeriodNumber(), calculatedAmortizableAmount); @@ -161,9 +150,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch } Money remainingPrincipalAmt = disburseAmt.minus(downPaymentAmt); scheduleParams.setPrincipalToBeScheduled(remainingPrincipalAmt); - loanApplicationTerms.setPrincipal(remainingPrincipalAmt); scheduleParams.setOutstandingBalance(remainingPrincipalAmt); scheduleParams.setOutstandingBalanceAsPerRest(remainingPrincipalAmt); + loanApplicationTerms.setPrincipal(remainingPrincipalAmt); } } @@ -305,7 +294,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(), scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), - loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), + loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(loanApplicationTerms, scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, scheduledDueDate, interestRates); // will check for EMI amount greater than interest calculated @@ -427,10 +416,11 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms); return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), - scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid, - scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), - scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalRepaymentExpected().getAmount(), - totalOutstanding); + scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()), + scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(), + totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), + scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(), + scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding); } private void updateCompoundingDetails(final Collection periods, final LoanScheduleParams params, @@ -510,7 +500,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); // update outstanding balance for interest calculation - updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate); + updateOutstandingBalanceAsPerRest(loanApplicationTerms, scheduleParams, scheduledDueDate); } private LoanScheduleModelPeriod handleRecalculationForTransactions(final MathContext mc, @@ -630,8 +620,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch interestCalculationGraceOnRepaymentPeriodFraction, scheduleParams.getTotalCumulativePrincipal(), scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, lastTotalOutstandingInterestPaymentDueToGrace, scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, - scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(), - periodStartDateApplicableForInterest, calculateTill, interestRates); + scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(loanApplicationTerms, scheduleParams), + scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, calculateTill, interestRates); loanApplicationTerms.updateAnnualNominalInterestRate(currentInterestRate); // applies charges for the period @@ -767,7 +757,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, - scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), + scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(loanApplicationTerms, scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, transactionDate, interestRates); interestForCurrentInstallment = principalInterestForThisPeriod.interest(); @@ -805,7 +795,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch // update outstanding balance for interest // calculation as per the rest - updateOutstandingBalanceAsPerRest(scheduleParams, transactionDate); + updateOutstandingBalanceAsPerRest(loanApplicationTerms, scheduleParams, transactionDate); // handle cumulative fields scheduleParams.addLoanTermInDays(periodDays); @@ -898,9 +888,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, - scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), - scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, transactionDate, - interestRates); + scheduleParams.getPeriodNumber(), mc, + mergeVariationsToMap(loanApplicationTerms, scheduleParams), scheduleParams.getCompoundingMap(), + periodStartDateApplicableForInterest, transactionDate, interestRates); if (!principalInterestForThisPeriod.interest() .plus(principalInterestForThisPeriod.interestPaymentDueToGrace()) .plus(scheduleParams.getOutstandingBalance()).isGreaterThan(unprocessed)) { @@ -971,11 +961,37 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch scheduleParams.addTotalCumulativePrincipal(unprocessed.plus(addToPrincipal)); } - private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) { + private void updateOutstandingBalanceAsPerRest(LoanApplicationTerms loanApplicationTerms, final LoanScheduleParams scheduleParams, + final LocalDate scheduledDueDate) { scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getPrincipalPortionMap(), - scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), false)); - scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getDisburseDetailMap(), - scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), true)); + scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest())); + scheduleParams.setOutstandingBalanceAsPerRest(calculateOutstandingBalanceAsPerRest(loanApplicationTerms, + scheduleParams.getDisburseDetailMap(), scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest())); + } + + private Money calculateOutstandingBalanceAsPerRest(LoanApplicationTerms loanApplicationTerms, Map disburseDetailMap, + LocalDate scheduledDueDate, Money outstandingBalance) { + List removeFromMap = new ArrayList<>(); + for (Map.Entry entry : disburseDetailMap.entrySet()) { + if (!DateUtils.isAfter(entry.getKey(), scheduledDueDate)) { + Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency()); + if (loanApplicationTerms.isDownPaymentEnabled()) { + downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), MathUtil.percentageOf(entry.getValue().getAmount(), + loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, + loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + } + outstandingBalance = outstandingBalance.plus(entry.getValue()).minus(downPaymentAmount); + + removeFromMap.add(entry.getKey()); + } + } + for (LocalDate date : removeFromMap) { + disburseDetailMap.remove(date); + } + return outstandingBalance; } /** @@ -1305,7 +1321,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch } else { principalToBeScheduled = loanApplicationTerms.getPrincipal(); } - return principalToBeScheduled; + return principalToBeScheduled.minus(loanApplicationTerms.getDownPaymentAmount()); } private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber, @@ -1389,8 +1405,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( getPaymentPeriodsInOneYearCalculator(), interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, - periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), params.getPeriodStartDate(), - transactionDate, applicableVariations); + periodNumberTemp, mc, mergeVariationsToMap(loanApplicationTerms, params), params.getCompoundingMap(), + params.getPeriodStartDate(), transactionDate, applicableVariations); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); @@ -1421,7 +1437,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch outstanding = outstanding.zero(); } else { outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(), - outstanding, false); + outstanding); } if (params.getLatePaymentMap().isEmpty() && outstanding.isZero()) { break; @@ -1432,8 +1448,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( getPaymentPeriodsInOneYearCalculator(), interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, - periodNumberTemp, mc, mergeVariationsToMap(params), params.getCompoundingMap(), params.getPeriodStartDate(), - params.getActualRepaymentDate(), applicableVariations); + periodNumberTemp, mc, mergeVariationsToMap(loanApplicationTerms, params), params.getCompoundingMap(), + params.getPeriodStartDate(), params.getActualRepaymentDate(), applicableVariations); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); @@ -1562,16 +1578,12 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch * Identifies all the past date principal changes and apply them on outstanding balance for future calculations */ private Money updateBalanceForInterestCalculation(final Map principalPortionMap, final LocalDate scheduledDueDate, - final Money outstandingBalanceAsPerRest, boolean addMapDetails) { + final Money outstandingBalanceAsPerRest) { List removeFromPrincipalPortionMap = new ArrayList<>(); Money outstandingBalance = outstandingBalanceAsPerRest; for (Map.Entry principal : principalPortionMap.entrySet()) { if (!DateUtils.isAfter(principal.getKey(), scheduledDueDate)) { - if (addMapDetails) { - outstandingBalance = outstandingBalance.plus(principal.getValue()); - } else { - outstandingBalance = outstandingBalance.minus(principal.getValue()); - } + outstandingBalance = outstandingBalance.minus(principal.getValue()); removeFromPrincipalPortionMap.add(principal.getKey()); } } @@ -1852,13 +1864,22 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch * as per rest) changes to single map for interest calculation * */ - private TreeMap mergeVariationsToMap(final LoanScheduleParams params) { + private TreeMap mergeVariationsToMap(LoanApplicationTerms loanApplicationTerms, final LoanScheduleParams params) { TreeMap map = new TreeMap<>(params.getLatePaymentMap()); for (Map.Entry mapEntry : params.getDisburseDetailMap().entrySet()) { Money value = mapEntry.getValue(); if (map.containsKey(mapEntry.getKey())) { value = value.plus(map.get(mapEntry.getKey())); } + if (loanApplicationTerms.isDownPaymentEnabled()) { + Money downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), + MathUtil.percentageOf(value.getAmount(), loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, + loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + value = value.minus(downPaymentAmount); + } map.put(mapEntry.getKey(), value); } @@ -2235,33 +2256,30 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) ? loanApplicationTerms.getExpectedDisbursementDate() : loanApplicationTerms.getSubmittedOnDate(); - Money downPaymentAmount = Money.zero(currency); - if (loanApplicationTerms.isDownPaymentEnabled()) { - downPaymentAmount = Money.of(currency, MathUtil.percentageOf(loanScheduleParams.getOutstandingBalance().getAmount(), - loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); - if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { - downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, - loanApplicationTerms.getInstallmentAmountInMultiplesOf()); - } - } - Money calculatedAmortizableAmount = principalToBeScheduled.minus(downPaymentAmount); - loanScheduleParams.setOutstandingBalance(calculatedAmortizableAmount); // Set fixed Amortization Amounts(either EMI or Principal ) - updateAmortization(mc, loanApplicationTerms, periodNumber, calculatedAmortizableAmount); + updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance); // count periods without interest grace to exclude for flat loan // calculations final Map disburseDetailMap = new HashMap<>(); if (loanApplicationTerms.isMultiDisburseLoan()) { - // fetches the first tranche amount and also updates other - // tranche - // details to map - BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), - disburseDetailMap, true); - outstandingBalance = outstandingBalance.zero().plus(disburseAmt); - outstandingBalanceAsPerRest = outstandingBalance; - principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt); + /* fetches the first tranche amount and also updates other tranche details to map */ + Money disburseAmt = Money.of(currency, + getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), disburseDetailMap, + loanScheduleParams.applyInterestRecalculation())); + Money downPaymentAmt = Money.zero(currency); + if (loanApplicationTerms.isDownPaymentEnabled()) { + downPaymentAmt = Money.of(currency, MathUtil.percentageOf(disburseAmt.getAmount(), + loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); + if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { + downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + } + } + Money remainingPrincipalAmt = disburseAmt.minus(downPaymentAmt); + outstandingBalance = remainingPrincipalAmt; + outstandingBalanceAsPerRest = remainingPrincipalAmt; + principalToBeScheduled = remainingPrincipalAmt; } int loanTermInDays = 0; @@ -2279,6 +2297,12 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch // This will create the recalculation details by applying the // transactions for (LoanRepaymentScheduleInstallment installment : processInstallmentsInstallments) { + if (installment.isDownPayment()) { + instalmentNumber++; + periods.add(createLoanScheduleModelDownPaymentPeriod(installment, outstandingBalance)); + newRepaymentScheduleInstallments.add(installment); + continue; + } // this will generate the next schedule due date and allows to // process the installment only if recalculate from date is // greater than due date @@ -2442,9 +2466,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch // update outstanding balance for interest calculation outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), - outstandingBalanceAsPerRest, false); - outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), - outstandingBalanceAsPerRest, true); + outstandingBalanceAsPerRest); + outstandingBalanceAsPerRest = calculateOutstandingBalanceAsPerRest(loanApplicationTerms, disburseDetailMap, + installment.getDueDate(), outstandingBalanceAsPerRest); // updates the map with over due amounts updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, newRepaymentScheduleInstallments, true, lastRestDate); @@ -2692,6 +2716,13 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch return installment; } + private LoanScheduleModelPeriod createLoanScheduleModelDownPaymentPeriod(final LoanRepaymentScheduleInstallment installment, + final Money outstandingPrincipal) { + final MonetaryCurrency currency = outstandingPrincipal.getCurrency(); + return LoanScheduleModelDownPaymentPeriod.downPayment(installment.getInstallmentNumber(), installment.getDueDate(), + installment.getPrincipal(currency), outstandingPrincipal); + } + private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment, final Money outstandingPrincipal) { final MonetaryCurrency currency = outstandingPrincipal.getCurrency(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java index 2399093a6..a5738aec0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java @@ -188,10 +188,11 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc final BigDecimal totalOutstanding = BigDecimal.ZERO; return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), - scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid, - scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), - scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalRepaymentExpected().getAmount(), - totalOutstanding); + scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()), + scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(), + totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), + scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(), + scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index f34269cf7..b6c15d85f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -23,7 +23,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.math.BigDecimal; import java.math.MathContext; -import java.math.RoundingMode; import java.time.LocalDate; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; @@ -675,8 +674,7 @@ public class LoanScheduleAssembler { final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails); - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mc = new MathContext(8, roundingMode); + final MathContext mc = MoneyHelper.getMathContext(); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), @@ -703,8 +701,8 @@ public class LoanScheduleAssembler { public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, Loan loan, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate rescheduleFrom) { - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mc = new MathContext(8, roundingMode); + + final MathContext mc = MoneyHelper.getMathContext(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, @@ -723,8 +721,8 @@ public class LoanScheduleAssembler { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), loanApplicationTerms.getInterestMethod()); - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mc = new MathContext(8, roundingMode); + + final MathContext mc = MoneyHelper.getMathContext(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index d326f5a7c..e8df96f89 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -19,7 +19,6 @@ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service; import java.math.MathContext; -import java.math.RoundingMode; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -127,8 +126,8 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP } loanApplicationTerms.getLoanTermVariations().updateLoanTermVariationsData(loanTermVariationsData); - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mathContext = new MathContext(8, roundingMode); + + final MathContext mathContext = MoneyHelper.getMathContext(); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory .determineProcessor(loan.transactionProcessingStrategy()); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 3c0d41236..05a98e07e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.service; import java.math.BigDecimal; import java.math.MathContext; -import java.math.RoundingMode; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -432,8 +431,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche * loanTermVariation.setApplicableFromDate(adjustedDate); } } } */ - final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); - final MathContext mathContext = new MathContext(8, roundingMode); + final MathContext mathContext = MoneyHelper.getMathContext(); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory .determineProcessor(loan.transactionProcessingStrategy()); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index e587e126d..e47ed1dcc 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -124,7 +124,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -192,7 +192,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -259,7 +259,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -327,7 +327,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -414,7 +414,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -498,7 +498,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -580,7 +580,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -666,7 +666,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -735,7 +735,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -818,7 +818,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -878,7 +878,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -947,7 +947,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1030,7 +1030,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1115,7 +1115,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1197,7 +1197,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1253,7 +1253,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1309,7 +1309,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1365,7 +1365,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("20 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1458,7 +1458,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("20 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1572,7 +1572,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan runAt("20 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1645,7 +1645,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("500", "15", "4", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1724,7 +1724,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("500", "15", "4", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1815,7 +1815,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("500", "15", "4", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1881,7 +1881,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("500", "15", "4", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -1960,7 +1960,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("500", "15", "4", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(500.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(500)).dateFormat(DATETIME_PATTERN) @@ -2065,15 +2065,16 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan assertNotNull(localLoanProductId); CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, - () -> applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, - "01 January 2023", "01 January 2023", FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE, - LoanScheduleType.PROGRESSIVE.name(), LoanScheduleProcessingType.VERTICAL.name())); + () -> applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, + BigDecimal.ZERO, "01 January 2023", "01 January 2023", + FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE, LoanScheduleType.PROGRESSIVE.name(), + LoanScheduleProcessingType.VERTICAL.name())); assertEquals(400, exception.getResponse().code()); assertTrue(exception.getMessage().contains("supported.only.with.advanced.payment.allocation.strategy")); exception = assertThrows(CallFailedRuntimeException.class, - () -> applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, - "01 January 2023", "01 January 2023", + () -> applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, + BigDecimal.ZERO, "01 January 2023", "01 January 2023", AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY, LoanScheduleType.PROGRESSIVE.name(), LoanScheduleProcessingType.HORIZONTAL.name())); assertEquals(400, exception.getResponse().code()); @@ -2099,7 +2100,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan assertNotNull(localLoanProductId); PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), 45, - 15, 3, 0, "01 January 2023", "01 January 2023"); + 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 16), 333.33, 0.0, 333.33, 0.0, 0.0); @@ -2128,8 +2129,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 2, 15), 333.34, 0.0, 333.34, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), 90, 15, 6, 0, - "01 January 2023", "01 January 2023"); + loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), 90, 15, 6, + BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 16), 166.67, 0.0, 166.67, 0.0, 0.0); @@ -2187,7 +2188,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan assertNotNull(localLoanProductId); PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), 45, - 15, 3, 0, "01 January 2023", "01 January 2023"); + 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 16), 333.33, 0.0, 333.33, 0.0, 0.0); @@ -2216,8 +2217,8 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 2, 15), 333.34, 0.0, 333.34, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), 90, 15, 6, 0, - "01 January 2023", "01 January 2023"); + loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000), 90, 15, 6, + BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 16), 166.67, 0.0, 166.67, 0.0, 0.0); @@ -2275,7 +2276,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan assertNotNull(localLoanProductId); PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40.50), - 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 10.12, 0.0, 10.12, 0.0, 0.0); @@ -2326,7 +2327,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan assertNotNull(localLoanProductId); PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40.50), - 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 10.12, 0.0, 10.12, 0.0, 0.0); @@ -2380,7 +2381,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 September 2023", "01 September 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -2486,7 +2487,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 September 2023", "01 September 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -2591,7 +2592,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 September 2023", "01 September 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -2696,7 +2697,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 September 2023", "01 September 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 September 2023", "01 September 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -2795,7 +2796,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -2881,7 +2882,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -2970,7 +2971,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, - BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + BigDecimal.valueOf(1000.0), 45, 15, 3, BigDecimal.ZERO, "01 January 2023", "01 January 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -3205,7 +3206,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan } private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal, - final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final int interestRate, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, final String expectedDisbursementDate, final String submittedOnDate, String transactionProcessorCode, String loanScheduleType, String loanScheduleProcessingType) { LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); @@ -3220,7 +3221,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan } private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal, - final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final int interestRate, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, final String expectedDisbursementDate, final String submittedOnDate) { LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments, diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index a510eaf23..12a7a81de 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -128,14 +128,14 @@ public abstract class BaseLoanIntegrationTest { .useBorrowerCycle(false)// .minPrincipal(100.0)// .principal(1000.0)// - .maxPrincipal(10000.0)// + .maxPrincipal(100000.0)// .minNumberOfRepayments(1)// .numberOfRepayments(1)// .maxNumberOfRepayments(30)// .isLinkedToFloatingInterestRates(false)// .minInterestRatePerPeriod((double) 0)// .interestRatePerPeriod((double) 0)// - .maxInterestRatePerPeriod((double) 0)// + .maxInterestRatePerPeriod((double) 100)// .interestRateFrequencyType(2)// .repaymentEvery(30)// .repaymentFrequencyType(0L)// @@ -412,8 +412,8 @@ public abstract class BaseLoanIntegrationTest { PostLoansRequest postLoansRequest = new PostLoansRequest().clientId(clientId).productId(loanProductId) .expectedDisbursementDate(loanDisbursementDate).dateFormat(DATETIME_PATTERN) .transactionProcessingStrategyCode(DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY) - .locale("en").submittedOnDate(loanDisbursementDate).amortizationType(1).interestRatePerPeriod(0) - .interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0).repaymentEvery(30).repaymentFrequencyType(0) + .locale("en").submittedOnDate(loanDisbursementDate).amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO) + .interestCalculationPeriodType(1).interestType(0).repaymentEvery(30).repaymentFrequencyType(0) .numberOfRepayments(numberOfRepayments).loanTermFrequency(numberOfRepayments * 30).loanTermFrequencyType(0) .maxOutstandingLoanBalance(BigDecimal.valueOf(amount)).principal(BigDecimal.valueOf(amount)).loanType("individual"); if (customizer != null) { @@ -422,9 +422,9 @@ public abstract class BaseLoanIntegrationTest { return postLoansRequest; } - protected PostLoansLoanIdRequest approveLoanRequest(Double amount) { + protected PostLoansLoanIdRequest approveLoanRequest(Double amount, String approvalDate) { return new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(amount)).dateFormat(DATETIME_PATTERN) - .approvedOnDate("01 January 2023").locale("en"); + .approvedOnDate(approvalDate).locale("en"); } protected Long applyAndApproveLoan(Long clientId, Long loanProductId, String loanDisbursementDate, Double amount, @@ -438,7 +438,7 @@ public abstract class BaseLoanIntegrationTest { .applyLoan(applyLoanRequest(clientId, loanProductId, loanDisbursementDate, amount, numberOfRepayments, customizer)); PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount)); + approveLoanRequest(amount, loanDisbursementDate)); return approvedLoanResult.getLoanId(); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index 4ca9fedfa..b0287e94a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -8041,9 +8041,9 @@ public class ClientLoanIntegrationTest { return LOAN_TRANSACTION_HELPER.applyLoan(new PostLoansRequest().clientId(clientId.longValue()).productId(loanProductId) .expectedDisbursementDate(loanDisbursementDate).dateFormat(DATETIME_PATTERN) .transactionProcessingStrategyCode(repaymentStrategyCode).locale("en").submittedOnDate(loanDisbursementDate) - .amortizationType(1).interestRatePerPeriod(0).interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0) - .repaymentEvery(30).repaymentFrequencyType(0).numberOfRepayments(1).loanTermFrequency(30).loanTermFrequencyType(0) - .principal(BigDecimal.valueOf(1000.0)).loanType("individual")); + .amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0) + .repaymentFrequencyType(0).repaymentEvery(30).repaymentFrequencyType(0).numberOfRepayments(1).loanTermFrequency(30) + .loanTermFrequencyType(0).principal(BigDecimal.valueOf(1000.0)).loanType("individual")); } private PostLoanProductsRequest createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java index d5824bdc6..2ca0a6611 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalIdSupportIntegrationTest.java @@ -935,10 +935,11 @@ public class ExternalIdSupportIntegrationTest extends IntegrationTest { "modify", new PutLoansLoanIdRequest().submittedOnDate("31 August 2022").dateFormat("dd MMMM yyyy").locale("en") .loanType("individual").productId(loanProductID.longValue()).clientId(client.getClientId()).interestType(0) - .interestCalculationPeriodType(1).interestRatePerPeriod(0).isEqualAmortization(false).loanTermFrequency(30) - .loanTermFrequencyType(0).maxOutstandingLoanBalance(10000L).numberOfRepayments(1).principal(10000L) - .repaymentEvery(30).repaymentFrequencyType(0).transactionProcessingStrategyCode("mifos-standard-strategy") - .expectedDisbursementDate("2 September 2022").amortizationType(1)); + .interestCalculationPeriodType(1).interestRatePerPeriod(BigDecimal.ZERO).isEqualAmortization(false) + .loanTermFrequency(30).loanTermFrequencyType(0).maxOutstandingLoanBalance(10000L).numberOfRepayments(1) + .principal(10000L).repaymentEvery(30).repaymentFrequencyType(0) + .transactionProcessingStrategyCode("mifos-standard-strategy").expectedDisbursementDate("2 September 2022") + .amortizationType(1)); assertEquals(loanExternalIdStr2, modifyLoanApplicationResult.getResourceExternalId()); DeleteLoansLoanIdResponse deleteLoanApplicationResult = this.loanTransactionHelper.deleteLoanApplication(loanExternalIdStr2); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java index 33601c650..737d6c55c 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargePaymentWithAdvancedPaymentAllocationTest.java @@ -163,8 +163,8 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest { savingsAccountHelper.depositToSavingsAccount(savingsId, "10000", startDate, CommonConstants.RESPONSE_RESOURCE_ID); - final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, 1000L, 45, 15, 3, 0, - "01 January 2023", "01 January 2023"); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, 1000L, 45, 15, 3, + BigDecimal.ZERO, "01 January 2023", "01 January 2023"); int loanId = loanResponse.getLoanId().intValue(); @@ -266,7 +266,7 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest { } private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final Long principal, - final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final int interestRate, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, final String expectedDisbursementDate, final String submittedOnDate) { log.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); return loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(clientId).productId(loanProductId.longValue()) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInstallmentMultiplesOfTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInstallmentMultiplesOfTest.java index b7cd5c5d1..9d72e8a61 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInstallmentMultiplesOfTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInstallmentMultiplesOfTest.java @@ -71,7 +71,7 @@ public class LoanInstallmentMultiplesOfTest extends BaseLoanIntegrationTest { PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount)); + approveLoanRequest(amount, "01 January 2023")); Long loanId = approvedLoanResult.getLoanId(); @@ -130,12 +130,12 @@ public class LoanInstallmentMultiplesOfTest extends BaseLoanIntegrationTest { .interestType(interestType)// .amortizationType(amortizationType)// .interestCalculationPeriodType(SAME_AS_REPAYMENT_PERIOD)// - .interestRatePerPeriod(12); + .interestRatePerPeriod(BigDecimal.valueOf(12)); PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount)); + approveLoanRequest(amount, "01 January 2023")); Long loanId = approvedLoanResult.getLoanId(); @@ -193,12 +193,12 @@ public class LoanInstallmentMultiplesOfTest extends BaseLoanIntegrationTest { .interestType(interestType)// .amortizationType(amortizationType)// .interestCalculationPeriodType(SAME_AS_REPAYMENT_PERIOD)// - .interestRatePerPeriod(12); + .interestRatePerPeriod(BigDecimal.valueOf(12)); PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount)); + approveLoanRequest(amount, "01 January 2023")); Long loanId = approvedLoanResult.getLoanId(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleTestWithDownpayment.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleTestWithDownpayment.java index a17929c17..d54a60d20 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleTestWithDownpayment.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleTestWithDownpayment.java @@ -36,13 +36,15 @@ import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(LoanTestLifecycleExtension.class) public class LoanRescheduleTestWithDownpayment extends BaseLoanIntegrationTest { - public static final BigDecimal DOWN_PAYMENT_PERCENTAGE = new BigDecimal(25); + public static final BigDecimal DOWN_PAYMENT_PERCENTAGE_20 = new BigDecimal(20); + public static final BigDecimal DOWN_PAYMENT_PERCENTAGE_25 = new BigDecimal(25); + public static final BigDecimal DOWN_PAYMENT_PERCENTAGE_33 = new BigDecimal(33); private final LoanRescheduleRequestHelper loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec); @Test - public void testRescheduleWithDownpayment() { + public void testRescheduleWithDownPayment() { runAt("01 January 2023", () -> { // Create Client Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); @@ -105,6 +107,342 @@ public class LoanRescheduleTestWithDownpayment extends BaseLoanIntegrationTest { }); } + @Test + public void testRescheduleAddExtraInstallmentsWithDownPayment() { + runAt("01 November 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + Long loanProductId = createLoanProductWith33PctDownPayment(true, true); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "02 October 2023", 1000.0, 3); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(1000.0, null, "02 October 2023"), // + installment(330.0, false, "02 October 2023"), // + installment(223.33, false, "01 November 2023"), // + installment(223.33, false, "01 December 2023"), // + installment(223.34, false, "31 December 2023") // + ); + + // 1st Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "02 October 2023"); + + // verify transactions + verifyTransactions(loanId, // + transaction(330.0, "Down Payment", "02 October 2023"), // + transaction(1000.0, "Disbursement", "02 October 2023") // + ); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(1000.0, null, "02 October 2023"), // + installment(330.0, true, "02 October 2023"), // + installment(223.33, false, "01 November 2023"), // + installment(223.33, false, "01 December 2023"), // + installment(223.34, false, "31 December 2023") // + ); + + String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnInterest(null).updateGraceOnPrincipal(null) + .updateExtraTerms("2").updateNewInterestRate(null).updateRescheduleFromDate("01 November 2023") + .updateAdjustedDueDate(null).updateSubmittedOnDate("01 November 2023").updateRescheduleReasonId("1") + .build(loanId.toString()); + + Integer loanRescheduleRequest = loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON); + requestJSON = new LoanRescheduleRequestTestBuilder().updateSubmittedOnDate("01 November 2023") + .getApproveLoanRescheduleRequestJSON(); + Integer approveLoanRescheduleRequest = loanRescheduleRequestHelper.approveLoanRescheduleRequest(loanRescheduleRequest, + requestJSON); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(1000.0, null, "02 October 2023"), // + installment(330.0, true, "02 October 2023"), // + installment(134.0, false, "01 November 2023"), // + installment(134.0, false, "01 December 2023"), // + installment(134.0, false, "31 December 2023"), // + installment(134.0, false, "30 January 2024"), // + installment(134.0, false, "29 February 2024") // + ); + }); + } + + @Test + public void testRescheduleAddExtraInstallmentsMultipleDisbursementWithDownPayment() { + runAt("31 December 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + Long loanProductId = createLoanProductWith33PctDownPayment(true, true); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "02 October 2023", 1000.0, 3); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(1000.0, null, "02 October 2023"), // + installment(330.0, false, "02 October 2023"), // + installment(223.33, false, "01 November 2023"), // + installment(223.33, false, "01 December 2023"), // + installment(223.34, false, "31 December 2023") // + ); + + // 1st Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "02 October 2023"); + + // verify transactions + verifyTransactions(loanId, // + transaction(330.0, "Down Payment", "02 October 2023"), // + transaction(1000.0, "Disbursement", "02 October 2023") // + ); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(1000.0, null, "02 October 2023"), // + installment(330.0, true, "02 October 2023"), // + installment(223.33, false, "01 November 2023"), // + installment(223.33, false, "01 December 2023"), // + installment(223.34, false, "31 December 2023") // + ); + + // 2nd Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(200.00), "02 December 2023"); + + // verify transactions + verifyTransactions(loanId, // + transaction(330.0, "Down Payment", "02 October 2023"), // + transaction(1000.0, "Disbursement", "02 October 2023"), // + transaction(200.0, "Disbursement", "02 December 2023"), // + transaction(66.0, "Down Payment", "02 December 2023") // + ); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(1000.0, null, "02 October 2023"), // + installment(330.0, true, "02 October 2023"), // + installment(268.0, false, "01 November 2023"), // + installment(268.0, false, "01 December 2023"), // + installment(200.0, null, "02 December 2023"), // + installment(66.0, false, "02 December 2023"), // + installment(268.0, false, "31 December 2023") // + ); + + String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnInterest(null).updateGraceOnPrincipal(null) + .updateExtraTerms("2").updateNewInterestRate(null).updateRescheduleFromDate("31 December 2023") + .updateAdjustedDueDate(null).updateSubmittedOnDate("31 December 2023").updateRescheduleReasonId("1") + .build(loanId.toString()); + + Integer loanRescheduleRequest = loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON); + requestJSON = new LoanRescheduleRequestTestBuilder().updateSubmittedOnDate("31 December 2023") + .getApproveLoanRescheduleRequestJSON(); + Integer approveLoanRescheduleRequest = loanRescheduleRequestHelper.approveLoanRescheduleRequest(loanRescheduleRequest, + requestJSON); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(1000.0, null, "02 October 2023"), // + installment(330.0, true, "02 October 2023"), // + installment(268, false, "01 November 2023"), // + installment(268, false, "01 December 2023"), // + installment(200.0, null, "02 December 2023"), // + installment(66.0, false, "02 December 2023"), // + installment(89.33, false, "31 December 2023"), // + installment(89.33, false, "30 January 2024"), // + installment(89.34, false, "29 February 2024") // + ); + }); + } + + @Test + public void testRescheduleAddExtraInstallmentsDisbursementWithDownPaymentWithInterest() { + runAt("01 January 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + Long loanProductId = createLoanProductWith20PctDownPaymentWithDecliningBalanceInterest(true, true, 5.0); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 15000.0, 6, postLoansRequest -> { + postLoansRequest.interestRatePerPeriod(BigDecimal.valueOf(5)); + postLoansRequest.interestRatePerPeriod(BigDecimal.valueOf(5)); + postLoansRequest.repaymentEvery(1); + postLoansRequest.loanTermFrequencyType(RepaymentFrequencyType.MONTHS); + postLoansRequest.repaymentFrequencyType(RepaymentFrequencyType.MONTHS); + postLoansRequest.loanTermFrequency(6); + }); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(12000.0, null, "01 January 2023"), // + installment(3000.00, false, "01 January 2023"), // + installment(1764.21, false, "01 February 2023"), // + installment(1852.42, false, "01 March 2023"), // + installment(1945.04, false, "01 April 2023"), // + installment(2042.29, false, "01 May 2023"), // + installment(2144.41, false, "01 June 2023"), // + installment(2251.63, false, "01 July 2023") // + ); + + // 1st Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(12000.00), "01 January 2023"); + + // verify transactions + verifyTransactions(loanId, // + transaction(2400.0, "Down Payment", "01 January 2023"), // + transaction(12000.0, "Disbursement", "01 January 2023") // + ); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(12000.0, null, "01 January 2023"), // + installment(2400.00, true, "01 January 2023"), // + installment(1411.37, false, "01 February 2023"), // + installment(1481.94, false, "01 March 2023"), // + installment(1556.04, false, "01 April 2023"), // + installment(1633.84, false, "01 May 2023"), // + installment(1715.53, false, "01 June 2023"), // + installment(1801.28, false, "01 July 2023") // + ); + + updateBusinessDate("01 June 2023"); + String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnInterest(null).updateGraceOnPrincipal(null) + .updateExtraTerms("2").updateNewInterestRate(null).updateRescheduleFromDate("01 June 2023").updateAdjustedDueDate(null) + .updateSubmittedOnDate("01 June 2023").updateRescheduleReasonId("1").build(loanId.toString()); + + Integer loanRescheduleRequest = loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON); + requestJSON = new LoanRescheduleRequestTestBuilder().updateSubmittedOnDate("01 June 2023") + .getApproveLoanRescheduleRequestJSON(); + Integer approveLoanRescheduleRequest = loanRescheduleRequestHelper.approveLoanRescheduleRequest(loanRescheduleRequest, + requestJSON); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(12000.0, null, "01 January 2023"), // + installment(2400.00, true, "01 January 2023"), // + installment(1411.37, false, "01 February 2023"), // + installment(1481.94, false, "01 March 2023"), // + installment(1556.04, false, "01 April 2023"), // + installment(1633.84, false, "01 May 2023"), // + installment(815.94, false, "01 June 2023"), // + installment(856.74, false, "01 July 2023"), // + installment(899.57, false, "01 August 2023"), // + installment(944.56, false, "01 September 2023") // + ); + }); + } + + @Test + public void testRescheduleAddExtraInstallmentsMultipleDisbursementWithDownPaymentWithInterest() { + runAt("01 January 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + + // Create Loan Product + Long loanProductId = createLoanProductWith20PctDownPaymentWithDecliningBalanceInterest(true, true, 5.0); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 15000.0, 6, postLoansRequest -> { + postLoansRequest.interestRatePerPeriod(BigDecimal.valueOf(5)); + postLoansRequest.interestRatePerPeriod(BigDecimal.valueOf(5)); + postLoansRequest.repaymentEvery(1); + postLoansRequest.loanTermFrequencyType(RepaymentFrequencyType.MONTHS); + postLoansRequest.repaymentFrequencyType(RepaymentFrequencyType.MONTHS); + postLoansRequest.loanTermFrequency(6); + }); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(12000.0, null, "01 January 2023"), // + installment(3000.00, false, "01 January 2023"), // + installment(1764.21, false, "01 February 2023"), // + installment(1852.42, false, "01 March 2023"), // + installment(1945.04, false, "01 April 2023"), // + installment(2042.29, false, "01 May 2023"), // + installment(2144.41, false, "01 June 2023"), // + installment(2251.63, false, "01 July 2023") // + ); + + // 1st Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(12000.00), "01 January 2023"); + + // verify transactions + verifyTransactions(loanId, // + transaction(2400.0, "Down Payment", "01 January 2023"), // + transaction(12000.0, "Disbursement", "01 January 2023") // + ); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(12000.0, null, "01 January 2023"), // + installment(2400.00, true, "01 January 2023"), // + installment(1411.37, false, "01 February 2023"), // + installment(1481.94, false, "01 March 2023"), // + installment(1556.04, false, "01 April 2023"), // + installment(1633.84, false, "01 May 2023"), // + installment(1715.53, false, "01 June 2023"), // + installment(1801.28, false, "01 July 2023") // + ); + + updateBusinessDate("02 May 2023"); + // 2nd Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(3000.00), "02 May 2023"); + + // verify transactions + verifyTransactions(loanId, // + transaction(2400.0, "Down Payment", "01 January 2023"), // + transaction(12000.0, "Disbursement", "01 January 2023"), // + transaction(3000.00, "Disbursement", "02 May 2023"), // + transaction(600.0, "Down Payment", "02 May 2023") // + ); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(12000.0, null, "01 January 2023"), // + installment(2400.00, true, "01 January 2023"), // + installment(1884.21, false, "01 February 2023"), // + installment(1978.42, false, "01 March 2023"), // + installment(2077.34, false, "01 April 2023"), // + installment(2181.21, false, "01 May 2023"), // + installment(3000.0, null, "02 May 2023"), // + installment(600.0, false, "02 May 2023"), // + installment(2174.14, false, "01 June 2023"), // + installment(1704.68, false, "01 July 2023") // + ); + + updateBusinessDate("01 June 2023"); + String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnInterest(null).updateGraceOnPrincipal(null) + .updateExtraTerms("2").updateNewInterestRate(null).updateRescheduleFromDate("01 June 2023").updateAdjustedDueDate(null) + .updateSubmittedOnDate("01 June 2023").updateRescheduleReasonId("1").build(loanId.toString()); + + Integer loanRescheduleRequest = loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON); + requestJSON = new LoanRescheduleRequestTestBuilder().updateSubmittedOnDate("01 June 2023") + .getApproveLoanRescheduleRequestJSON(); + Integer approveLoanRescheduleRequest = loanRescheduleRequestHelper.approveLoanRescheduleRequest(loanRescheduleRequest, + requestJSON); + + // Verify Repayment Schedule + verifyRepaymentSchedule(loanId, // + installment(12000.0, null, "01 January 2023"), // + installment(2400.00, true, "01 January 2023"), // + installment(1884.21, false, "01 February 2023"), // + installment(1978.42, false, "01 March 2023"), // + installment(2077.34, false, "01 April 2023"), // + installment(2181.21, false, "01 May 2023"), // + installment(3000.0, null, "02 May 2023"), // + installment(600.0, false, "02 May 2023"), // + installment(903.80, false, "01 June 2023"), // + installment(945.12, false, "01 July 2023"), // + installment(992.37, false, "01 August 2023"), // + installment(1037.53, false, "01 September 2023")// + ); + }); + } + private Long createLoanProductWith25PctDownPayment(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled) { PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); product.setMultiDisburseLoan(multiDisburseEnabled); @@ -117,7 +455,7 @@ public class LoanRescheduleTestWithDownpayment extends BaseLoanIntegrationTest { } product.setEnableDownPayment(true); - product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE); + product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE_25); product.setEnableAutoRepaymentForDownPayment(autoDownPaymentEnabled); PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); @@ -128,7 +466,76 @@ public class LoanRescheduleTestWithDownpayment extends BaseLoanIntegrationTest { assertEquals(TRUE, getLoanProductsProductIdResponse.getEnableDownPayment()); assertNotNull(getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment()); - assertEquals(0, getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment().compareTo(DOWN_PAYMENT_PERCENTAGE)); + assertEquals(0, + getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment().compareTo(DOWN_PAYMENT_PERCENTAGE_25)); + assertEquals(autoDownPaymentEnabled, getLoanProductsProductIdResponse.getEnableAutoRepaymentForDownPayment()); + assertEquals(multiDisburseEnabled, getLoanProductsProductIdResponse.getMultiDisburseLoan()); + return loanProductId; + } + + private Long createLoanProductWith33PctDownPayment(boolean autoDownPaymentEnabled, boolean multiDisburseEnabled) { + PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); + product.setMultiDisburseLoan(multiDisburseEnabled); + product.repaymentEvery(1); + product.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); + product.installmentAmountInMultiplesOf(null); + + if (!multiDisburseEnabled) { + product.disallowExpectedDisbursements(null); + product.setAllowApprovedDisbursedAmountsOverApplied(null); + product.overAppliedCalculationType(null); + product.overAppliedNumber(null); + } + + product.setEnableDownPayment(true); + product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE_33); + product.setEnableAutoRepaymentForDownPayment(autoDownPaymentEnabled); + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper + .retrieveLoanProductById(loanProductResponse.getResourceId()); + + Long loanProductId = loanProductResponse.getResourceId(); + + assertEquals(TRUE, getLoanProductsProductIdResponse.getEnableDownPayment()); + assertNotNull(getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment()); + assertEquals(0, + getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment().compareTo(DOWN_PAYMENT_PERCENTAGE_33)); + assertEquals(autoDownPaymentEnabled, getLoanProductsProductIdResponse.getEnableAutoRepaymentForDownPayment()); + assertEquals(multiDisburseEnabled, getLoanProductsProductIdResponse.getMultiDisburseLoan()); + return loanProductId; + } + + private Long createLoanProductWith20PctDownPaymentWithDecliningBalanceInterest(boolean autoDownPaymentEnabled, + boolean multiDisburseEnabled, double interestRate) { + PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); + product.setMultiDisburseLoan(multiDisburseEnabled); + product.repaymentEvery(1); + product.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); + product.installmentAmountInMultiplesOf(null); + product.interestRatePerPeriod(interestRate); + + if (!multiDisburseEnabled) { + product.disallowExpectedDisbursements(null); + product.setAllowApprovedDisbursedAmountsOverApplied(null); + product.overAppliedCalculationType(null); + product.overAppliedNumber(null); + } + + product.setEnableDownPayment(true); + product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE_20); + product.setEnableAutoRepaymentForDownPayment(autoDownPaymentEnabled); + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper + .retrieveLoanProductById(loanProductResponse.getResourceId()); + + Long loanProductId = loanProductResponse.getResourceId(); + + assertEquals(TRUE, getLoanProductsProductIdResponse.getEnableDownPayment()); + assertNotNull(getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment()); + assertEquals(0, + getLoanProductsProductIdResponse.getDisbursedAmountPercentageForDownPayment().compareTo(DOWN_PAYMENT_PERCENTAGE_20)); assertEquals(autoDownPaymentEnabled, getLoanProductsProductIdResponse.getEnableAutoRepaymentForDownPayment()); assertEquals(multiDisburseEnabled, getLoanProductsProductIdResponse.getMultiDisburseLoan()); return loanProductId; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSpecificDueDateChargeAfterMaturityTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSpecificDueDateChargeAfterMaturityTest.java index 7efe0be71..660f7e19b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSpecificDueDateChargeAfterMaturityTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSpecificDueDateChargeAfterMaturityTest.java @@ -446,8 +446,8 @@ public class LoanSpecificDueDateChargeAfterMaturityTest { .currencyCode("USD").name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)) .chargeAppliesTo(1).locale("en").active(true)); - final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, 1000L, 30, 30, 1, 0, - "01 September 2023", "01 September 2023"); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, 1000L, 30, 30, 1, + BigDecimal.ZERO, "01 September 2023", "01 September 2023"); loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) @@ -548,7 +548,7 @@ public class LoanSpecificDueDateChargeAfterMaturityTest { } private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final Long principal, - final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final int interestRate, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, final String expectedDisbursementDate, final String submittedOnDate) { LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); return loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(clientId).productId(loanProductId.longValue()) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java index 12377c465..9d631d87f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanWaiveChargeTest.java @@ -80,7 +80,7 @@ public class LoanWaiveChargeTest extends BaseLoanIntegrationTest { PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount)); + approveLoanRequest(amount, "01 January 2023")); Long loanId = approvedLoanResult.getLoanId(); appliedLoanId.set(loanId); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/RefundForActiveLoansWithAdvancedPaymentAllocationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/RefundForActiveLoansWithAdvancedPaymentAllocationTest.java index 58c884679..29c9817fd 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/RefundForActiveLoansWithAdvancedPaymentAllocationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/RefundForActiveLoansWithAdvancedPaymentAllocationTest.java @@ -104,8 +104,8 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest { Integer loanProductId = createLoanProduct("1000", "30", "4", LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); - final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), loanProductId, 1000L, 90, 30, 3, 0, - "01 January 2023", "01 January 2023"); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), loanProductId, 1000L, 90, 30, 3, + BigDecimal.ZERO, "01 January 2023", "01 January 2023"); int loanId = loanResponse.getLoanId().intValue(); @@ -303,8 +303,8 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest { Integer loanProductId = createLoanProduct("1000", "30", "4", LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); - final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), loanProductId, 1000L, 90, 30, 3, 0, - "01 January 2023", "01 January 2023"); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), loanProductId, 1000L, 90, 30, 3, + BigDecimal.ZERO, "01 January 2023", "01 January 2023"); int loanId = loanResponse.getLoanId().intValue(); @@ -529,7 +529,7 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest { } private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final Long principal, - final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final int interestRate, + final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate, final String expectedDisbursementDate, final String submittedOnDate) { log.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); return loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())