FINERACT-1981: Remove hardcoded MoneyHelper dependency during progressive loan
This commit is contained in:
committed by
Adam Saghy
parent
806ee69798
commit
a9ecdf97a4
+38
-34
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -98,6 +99,7 @@ public final class LoanScheduleParams {
|
||||
private int loanTermInDays;
|
||||
private final MonetaryCurrency currency;
|
||||
private final boolean applyInterestRecalculation;
|
||||
private final MathContext mc;
|
||||
|
||||
private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
|
||||
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
|
||||
@@ -108,7 +110,7 @@ public final class LoanScheduleParams {
|
||||
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
|
||||
final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
|
||||
final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.instalmentNumber = instalmentNumber;
|
||||
this.loanTermInDays = loanTermInDays;
|
||||
@@ -136,8 +138,9 @@ public final class LoanScheduleParams {
|
||||
this.partialUpdate = partialUpdate;
|
||||
this.currency = currency;
|
||||
this.applyInterestRecalculation = applyInterestRecalculation;
|
||||
this.mc = mc;
|
||||
if (this.currency != null) {
|
||||
this.compoundedInLastInstallment = Money.zero(this.currency);
|
||||
this.compoundedInLastInstallment = Money.zero(this.currency, mc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,19 +153,19 @@ public final class LoanScheduleParams {
|
||||
final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
|
||||
final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
|
||||
final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) {
|
||||
final boolean partialUpdate = true;
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
|
||||
}
|
||||
|
||||
public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
|
||||
final boolean applyInterestRecalculation) {
|
||||
final boolean applyInterestRecalculation, MathContext mc) {
|
||||
final int periodNumber = 1;
|
||||
final int instalmentNumber = 1;
|
||||
final LocalDate periodStartDate = null;
|
||||
@@ -191,22 +194,22 @@ public final class LoanScheduleParams {
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
|
||||
}
|
||||
|
||||
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
|
||||
final LocalDate periodStartDate, final Money principalToBeScheduled) {
|
||||
final LocalDate periodStartDate, final Money principalToBeScheduled, final MathContext mc) {
|
||||
final int loanTermInDays = 0;
|
||||
final int periodNumber = 1;
|
||||
final int instalmentNumber = 1;
|
||||
final Money totalCumulativePrincipal = Money.zero(currency);
|
||||
final Money totalCumulativeInterest = Money.zero(currency);
|
||||
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
|
||||
final Money totalCumulativePrincipal = Money.zero(currency, mc);
|
||||
final Money totalCumulativeInterest = Money.zero(currency, mc);
|
||||
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency, mc);
|
||||
final LocalDate actualRepaymentDate = periodStartDate;
|
||||
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
final Money totalPenaltyChargesCharged = Money.zero(currency);
|
||||
final Money totalPenaltyChargesCharged = Money.zero(currency, mc);
|
||||
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
final Money reducePrincipal = Money.zero(currency);
|
||||
final Money reducePrincipal = Money.zero(currency, mc);
|
||||
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
|
||||
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
|
||||
final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
|
||||
@@ -218,27 +221,28 @@ public final class LoanScheduleParams {
|
||||
final Collection<RecalculationDetail> recalculationDetails = null;
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
|
||||
final LocalDate scheduleTillDate = null;
|
||||
final Money unCompoundedAmount = Money.zero(currency);
|
||||
final Money unCompoundedAmount = Money.zero(currency, mc);
|
||||
final boolean applyInterestRecalculation = false;
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
|
||||
}
|
||||
|
||||
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
|
||||
final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams) {
|
||||
final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams,
|
||||
MathContext mc) {
|
||||
final int loanTermInDays = 0;
|
||||
final int periodNumber = 1;
|
||||
final int instalmentNumber = 1;
|
||||
final Money totalCumulativePrincipal = Money.zero(currency);
|
||||
final Money totalCumulativeInterest = Money.zero(currency);
|
||||
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
|
||||
final Money totalCumulativePrincipal = Money.zero(currency, mc);
|
||||
final Money totalCumulativeInterest = Money.zero(currency, mc);
|
||||
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency, mc);
|
||||
final LocalDate actualRepaymentDate = periodStartDate;
|
||||
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
final Money totalPenaltyChargesCharged = Money.zero(currency);
|
||||
final Money totalPenaltyChargesCharged = Money.zero(currency, mc);
|
||||
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
final Money reducePrincipal = Money.zero(currency);
|
||||
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
|
||||
@@ -253,13 +257,13 @@ public final class LoanScheduleParams {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor;
|
||||
final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate;
|
||||
final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation;
|
||||
final Money unCompoundedAmount = Money.zero(currency);
|
||||
final Money unCompoundedAmount = Money.zero(currency, mc);
|
||||
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
|
||||
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
|
||||
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
|
||||
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
|
||||
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
|
||||
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
|
||||
}
|
||||
|
||||
public int getPeriodNumber() {
|
||||
@@ -288,7 +292,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) {
|
||||
if (this.totalCumulativePrincipal != null) {
|
||||
this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal);
|
||||
this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal, mc);
|
||||
} else {
|
||||
this.totalCumulativePrincipal = totalCumulativePrincipal;
|
||||
}
|
||||
@@ -300,7 +304,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void addTotalCumulativeInterest(final Money totalCumulativeInterest) {
|
||||
if (this.totalCumulativeInterest != null) {
|
||||
this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest);
|
||||
this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest, mc);
|
||||
} else {
|
||||
this.totalCumulativeInterest = totalCumulativeInterest;
|
||||
}
|
||||
@@ -312,7 +316,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) {
|
||||
if (this.totalFeeChargesCharged != null) {
|
||||
this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged);
|
||||
this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged, mc);
|
||||
} else {
|
||||
this.totalFeeChargesCharged = totalFeeChargesCharged;
|
||||
}
|
||||
@@ -324,7 +328,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) {
|
||||
if (this.totalPenaltyChargesCharged != null) {
|
||||
this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged);
|
||||
this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged, mc);
|
||||
} else {
|
||||
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
|
||||
}
|
||||
@@ -336,7 +340,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void addTotalRepaymentExpected(final Money totalRepaymentExpected) {
|
||||
if (this.totalRepaymentExpected != null) {
|
||||
this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected);
|
||||
this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected, mc);
|
||||
} else {
|
||||
this.totalRepaymentExpected = totalRepaymentExpected;
|
||||
}
|
||||
@@ -428,7 +432,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void addReducePrincipal(Money reducePrincipal) {
|
||||
if (this.reducePrincipal != null) {
|
||||
this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal);
|
||||
this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal, mc);
|
||||
} else {
|
||||
this.reducePrincipal = reducePrincipal;
|
||||
}
|
||||
@@ -436,13 +440,13 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void reduceReducePrincipal(Money reducePrincipal) {
|
||||
if (this.reducePrincipal != null) {
|
||||
this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal);
|
||||
this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal, mc);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPrincipalToBeScheduled(Money principalToBeScheduled) {
|
||||
if (this.principalToBeScheduled != null) {
|
||||
this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled);
|
||||
this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled, mc);
|
||||
} else {
|
||||
this.principalToBeScheduled = principalToBeScheduled;
|
||||
}
|
||||
@@ -450,7 +454,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void addOutstandingBalance(Money outstandingBalance) {
|
||||
if (this.outstandingBalance != null) {
|
||||
this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance);
|
||||
this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance, mc);
|
||||
} else {
|
||||
this.outstandingBalance = outstandingBalance;
|
||||
}
|
||||
@@ -458,13 +462,13 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void reduceOutstandingBalance(Money outstandingBalance) {
|
||||
if (this.outstandingBalance != null) {
|
||||
this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance);
|
||||
this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance, mc);
|
||||
}
|
||||
}
|
||||
|
||||
public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
|
||||
if (this.outstandingBalanceAsPerRest != null) {
|
||||
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest);
|
||||
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest, mc);
|
||||
} else {
|
||||
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
|
||||
}
|
||||
@@ -472,7 +476,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
|
||||
if (this.outstandingBalanceAsPerRest != null) {
|
||||
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest);
|
||||
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest, mc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +526,7 @@ public final class LoanScheduleParams {
|
||||
|
||||
public void minusUnCompoundedAmount(Money unCompoundedAmount) {
|
||||
if (this.unCompoundedAmount != null) {
|
||||
this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount);
|
||||
this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount, mc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -94,10 +94,10 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
: loanApplicationTerms.getSubmittedOnDate();
|
||||
if (loanScheduleParams == null) {
|
||||
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
|
||||
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms));
|
||||
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), mc);
|
||||
} else if (!loanScheduleParams.isPartialUpdate()) {
|
||||
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
|
||||
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams);
|
||||
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams, mc);
|
||||
} else {
|
||||
scheduleParams = loanScheduleParams;
|
||||
}
|
||||
@@ -345,7 +345,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
|
||||
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
|
||||
totalInstallmentDue, !isCompletePeriod);
|
||||
totalInstallmentDue, !isCompletePeriod, mc);
|
||||
if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) {
|
||||
installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount());
|
||||
}
|
||||
@@ -651,7 +651,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
scheduleParams.getPeriodStartDate(), transactionDate, currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
|
||||
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
|
||||
currentPeriodParams.fetchTotalAmountForPeriod(), false);
|
||||
currentPeriodParams.fetchTotalAmountForPeriod(), false, mc);
|
||||
scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(interestTillDate.interestPaymentDueToGrace());
|
||||
}
|
||||
}
|
||||
@@ -789,7 +789,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
|
||||
scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod,
|
||||
scheduleParams.getOutstandingBalance(), interestForCurrentInstallment, feeChargesForInstallment,
|
||||
penaltyChargesForInstallment, totalInstallmentDue, true);
|
||||
penaltyChargesForInstallment, totalInstallmentDue, true, mc);
|
||||
periods.add(installment);
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate);
|
||||
@@ -1416,7 +1416,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
|
||||
LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(),
|
||||
startDate, transactionDate, totalInterest.zero(), totalInterest.zero(), totalInterest, totalInterest.zero(),
|
||||
totalInterest.zero(), totalInterest, true);
|
||||
totalInterest.zero(), totalInterest, true, mc);
|
||||
params.incrementInstalmentNumber();
|
||||
periods.add(installment);
|
||||
totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
|
||||
@@ -1489,7 +1489,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
if (totalInterest.isGreaterThanZero()) {
|
||||
LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(),
|
||||
startDate, params.getActualRepaymentDate(), totalInterest.zero(), totalInterest.zero(), totalInterest,
|
||||
totalInterest.zero(), totalInterest.zero(), totalInterest, true);
|
||||
totalInterest.zero(), totalInterest.zero(), totalInterest, true, mc);
|
||||
params.incrementInstalmentNumber();
|
||||
periods.add(installment);
|
||||
params.getCompoundingDateVariations().put(startDate, new TreeMap<>(params.getCompoundingMap()));
|
||||
@@ -2189,7 +2189,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
|
||||
// for complete schedule generation
|
||||
LoanScheduleParams loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails,
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation);
|
||||
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation, mc);
|
||||
|
||||
List<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||
final List<LoanRepaymentScheduleInstallment> retainedInstallments = new ArrayList<>();
|
||||
@@ -2402,7 +2402,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
installment.resetDerivedComponents();
|
||||
newRepaymentScheduleInstallments.add(installment);
|
||||
outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency));
|
||||
final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance);
|
||||
final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance, mc);
|
||||
periods.add(loanScheduleModelPeriod);
|
||||
totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency));
|
||||
totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency));
|
||||
@@ -2493,7 +2493,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
|
||||
uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
|
||||
newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
|
||||
currency, applyInterestRecalculation);
|
||||
currency, applyInterestRecalculation, mc);
|
||||
retainedInstallments.addAll(newRepaymentScheduleInstallments);
|
||||
loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
|
||||
loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged()));
|
||||
@@ -2729,13 +2729,13 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
}
|
||||
|
||||
private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment,
|
||||
final Money outstandingPrincipal) {
|
||||
final Money outstandingPrincipal, final MathContext mc) {
|
||||
final MonetaryCurrency currency = outstandingPrincipal.getCurrency();
|
||||
return LoanScheduleModelRepaymentPeriod.repayment(installment.getInstallmentNumber(), installment.getFromDate(),
|
||||
installment.getDueDate(), installment.getPrincipal(currency), outstandingPrincipal,
|
||||
installment.getInterestCharged(currency), installment.getFeeChargesCharged(currency),
|
||||
installment.getPenaltyChargesCharged(currency), installment.getDue(currency),
|
||||
installment.isRecalculatedInterestComponent());
|
||||
installment.getPenaltyChargesCharged(currency), installment.getDue(currency), installment.isRecalculatedInterestComponent(),
|
||||
mc);
|
||||
}
|
||||
|
||||
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
|
||||
|
||||
+464
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.fortuna.ical4j.model.Recur;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||
import org.apache.fineract.organisation.holiday.service.HolidayUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
|
||||
import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType;
|
||||
import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper;
|
||||
import org.apache.fineract.portfolio.calendar.domain.Calendar;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
|
||||
@Override
|
||||
public List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(final MathContext mc, final LocalDate scheduleStartDate,
|
||||
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||
final Money zeroAmount = Money.zero(loanApplicationTerms.getCurrency(), mc);
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
final ArrayList<LoanScheduleModelRepaymentPeriod> repaymentPeriods = new ArrayList<>(numberOfRepayments);
|
||||
LocalDate lastRepaymentDate = scheduleStartDate;
|
||||
LocalDate nextRepaymentDate;
|
||||
boolean isFirstRepayment = true;
|
||||
for (int repaymentPeriodNumber = 1; repaymentPeriodNumber <= numberOfRepayments; repaymentPeriodNumber++) {
|
||||
nextRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
|
||||
|
||||
if (repaymentPeriodNumber == numberOfRepayments) { // last period
|
||||
if (loanApplicationTerms.getFixedLength() != null
|
||||
&& loanApplicationTerms.calculateMaxDateForFixedLength().compareTo(nextRepaymentDate) != 0) {
|
||||
nextRepaymentDate = loanApplicationTerms.calculateMaxDateForFixedLength();
|
||||
}
|
||||
nextRepaymentDate = adjustRepaymentDate(nextRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
|
||||
}
|
||||
nextRepaymentDate = applyLoanTermVariations(loanApplicationTerms, nextRepaymentDate);
|
||||
repaymentPeriods.add(LoanScheduleModelRepaymentPeriod.repayment(repaymentPeriodNumber, lastRepaymentDate, nextRepaymentDate,
|
||||
zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, false, mc));
|
||||
lastRepaymentDate = nextRepaymentDate;
|
||||
isFirstRepayment = false;
|
||||
}
|
||||
return repaymentPeriods;
|
||||
}
|
||||
|
||||
private LocalDate applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, final LocalDate scheduledDueDate) {
|
||||
LocalDate modifiedScheduledDueDate = scheduledDueDate;
|
||||
// due date changes should be applied only for that dueDate
|
||||
if (loanApplicationTerms.getLoanTermVariations() != null) {
|
||||
if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) {
|
||||
LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
|
||||
if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) {
|
||||
modifiedScheduledDueDate = loanTermVariationsData.getDateValue();
|
||||
loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate));
|
||||
}
|
||||
}
|
||||
}
|
||||
return modifiedScheduledDueDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
|
||||
LocalDate lastRepaymentDate = loanApplicationTerms.getRepaymentStartDate();
|
||||
boolean isFirstRepayment = true;
|
||||
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
|
||||
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
|
||||
isFirstRepayment = false;
|
||||
}
|
||||
if (loanApplicationTerms.getFixedLength() != null
|
||||
&& loanApplicationTerms.calculateMaxDateForFixedLength().compareTo(lastRepaymentDate) != 0) {
|
||||
lastRepaymentDate = loanApplicationTerms.calculateMaxDateForFixedLength();
|
||||
}
|
||||
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
|
||||
return lastRepaymentDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms,
|
||||
boolean isFirstRepayment) {
|
||||
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate();
|
||||
LocalDate dueRepaymentPeriodDate = null;
|
||||
if (isFirstRepayment && firstRepaymentPeriodDate != null) {
|
||||
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
|
||||
|
||||
} else {
|
||||
LocalDate seedDate = null;
|
||||
String reccuringString = null;
|
||||
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
|
||||
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
|
||||
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate);
|
||||
dueRepaymentPeriodDate = (LocalDate) CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
|
||||
loanApplicationTerms.getRepaymentPeriodFrequencyType());
|
||||
if (currentCalendar != null) {
|
||||
// If we have currentCalendar object, this means there is a
|
||||
// calendar associated with
|
||||
// the loan, and we should use it in order to calculate next
|
||||
// repayment
|
||||
|
||||
CalendarHistory calendarHistory = null;
|
||||
CalendarHistoryDataWrapper calendarHistoryDataWrapper = loanApplicationTerms.getCalendarHistoryDataWrapper();
|
||||
if (calendarHistoryDataWrapper != null) {
|
||||
calendarHistory = loanApplicationTerms.getCalendarHistoryDataWrapper().getCalendarHistory(dueRepaymentPeriodDate);
|
||||
}
|
||||
|
||||
// get the start date from the calendar history
|
||||
if (calendarHistory == null) {
|
||||
seedDate = currentCalendar.getStartDateLocalDate();
|
||||
reccuringString = currentCalendar.getRecurrence();
|
||||
} else {
|
||||
seedDate = calendarHistory.getStartDate();
|
||||
reccuringString = calendarHistory.getRecurrence();
|
||||
}
|
||||
|
||||
dueRepaymentPeriodDate = CalendarUtils.getNextRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate,
|
||||
loanApplicationTerms.getRepaymentEvery(),
|
||||
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
|
||||
loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(), loanApplicationTerms.getNumberOfdays());
|
||||
}
|
||||
}
|
||||
|
||||
return dueRepaymentPeriodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
|
||||
final boolean isFirstRepayment, final Integer periodNumber) {
|
||||
LocalDate dueRepaymentPeriodDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
|
||||
|
||||
// Fixed Length validation only for Last Installment
|
||||
if (loanApplicationTerms.isLastPeriod(periodNumber)) {
|
||||
final LocalDate maxDateForFixedLength = loanApplicationTerms.calculateMaxDateForFixedLength();
|
||||
if (maxDateForFixedLength != null && dueRepaymentPeriodDate.compareTo(maxDateForFixedLength) != 0) {
|
||||
dueRepaymentPeriodDate = maxDateForFixedLength;
|
||||
}
|
||||
}
|
||||
|
||||
return dueRepaymentPeriodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdjustedDateDetailsDTO adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate,
|
||||
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||
final LocalDate adjustedDate = dueRepaymentPeriodDate;
|
||||
return getAdjustedDateDetailsDTO(dueRepaymentPeriodDate, loanApplicationTerms, holidayDetailDTO, adjustedDate);
|
||||
}
|
||||
|
||||
private AdjustedDateDetailsDTO getAdjustedDateDetailsDTO(final LocalDate dueRepaymentPeriodDate,
|
||||
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final LocalDate adjustedDate) {
|
||||
final boolean isFirstRepayment = false;
|
||||
final LocalDate nextRepaymentPeriodDueDate = generateNextRepaymentDate(adjustedDate, loanApplicationTerms, isFirstRepayment);
|
||||
final AdjustedDateDetailsDTO newAdjustedDateDetailsDTO = new AdjustedDateDetailsDTO(adjustedDate, dueRepaymentPeriodDate,
|
||||
nextRepaymentPeriodDueDate);
|
||||
return recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(newAdjustedDateDetailsDTO,
|
||||
loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively checking non working days and holidays and working days exemption to generate next repayment period
|
||||
* date Base on the configuration
|
||||
*
|
||||
* @param adjustedDateDetailsDTO
|
||||
* @param loanApplicationTerms
|
||||
* @param holidayDetailDTO
|
||||
* @param isFirstRepayment
|
||||
* @return
|
||||
*/
|
||||
private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(
|
||||
final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms,
|
||||
final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) {
|
||||
if (holidayDetailDTO != null) {
|
||||
final Recur recur = CalendarUtils.getICalRecur(holidayDetailDTO.getWorkingDays().getRecurrence());
|
||||
final boolean isSevenDaysWeek = (recur.getDayList().size() == 7); // 7 Seven days in the week
|
||||
// If Workings days are not seven day week
|
||||
if (!isSevenDaysWeek) {
|
||||
checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
|
||||
isFirstRepayment);
|
||||
}
|
||||
// Check Holidays If applied
|
||||
checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
|
||||
isFirstRepayment);
|
||||
|
||||
/**
|
||||
* Check Changed Schedule Date is holiday or is not a working day Then re-call this method to get the non
|
||||
* holiday and working day
|
||||
*/
|
||||
if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
|
||||
holidayDetailDTO.getHolidays()) != null)
|
||||
|| WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(),
|
||||
adjustedDateDetailsDTO.getChangedScheduleDate())) {
|
||||
recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO,
|
||||
loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
|
||||
}
|
||||
}
|
||||
return adjustedDateDetailsDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method to check and update the working day if repayment date is holiday
|
||||
*
|
||||
* @param adjustedDateDetailsDTO
|
||||
* @param holidayDetailDTO
|
||||
* @param loanApplicationTerms
|
||||
* @param isFirstRepayment
|
||||
*/
|
||||
private void checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
|
||||
final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
|
||||
if (holidayDetailDTO.isHolidayEnabled()) {
|
||||
Holiday applicableHolidayForNewAdjustedDate = null;
|
||||
while ((applicableHolidayForNewAdjustedDate = HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
|
||||
holidayDetailDTO.getHolidays())) != null) {
|
||||
if (applicableHolidayForNewAdjustedDate.getReScheduleType().isResheduleToNextRepaymentDate()) {
|
||||
LocalDate nextRepaymentPeriodDueDate = adjustedDateDetailsDTO.getChangedActualRepaymentDate();
|
||||
while (!DateUtils.isAfter(nextRepaymentPeriodDueDate, adjustedDateDetailsDTO.getChangedScheduleDate())) {
|
||||
nextRepaymentPeriodDueDate = generateNextRepaymentDate(nextRepaymentPeriodDueDate, loanApplicationTerms,
|
||||
isFirstRepayment);
|
||||
}
|
||||
adjustedDateDetailsDTO.setChangedScheduleDate(nextRepaymentPeriodDueDate);
|
||||
adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
|
||||
adjustedDateDetailsDTO.setChangedActualRepaymentDate(adjustedDateDetailsDTO.getChangedScheduleDate());
|
||||
} else {
|
||||
HolidayUtil.updateRepaymentRescheduleDateToWorkingDayIfItIsHoliday(adjustedDateDetailsDTO,
|
||||
applicableHolidayForNewAdjustedDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method to check and update the working day if repayment date is non working day
|
||||
*
|
||||
* @param adjustedDateDetailsDTO
|
||||
* @param holidayDetailDTO
|
||||
* @param isFirstRepayment
|
||||
* @param loanApplicationTerms
|
||||
*/
|
||||
private void checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO,
|
||||
final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) {
|
||||
|
||||
while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) {
|
||||
final RepaymentRescheduleType repaymentRescheduleType = WorkingDaysUtil
|
||||
.getRepaymentRescheduleType(holidayDetailDTO.getWorkingDays());
|
||||
|
||||
if (repaymentRescheduleType.isMoveToNextRepaymentDay()) {
|
||||
LocalDate nextRepaymentPeriodDueDate = adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate();
|
||||
while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextRepaymentPeriodDueDate)
|
||||
|| DateUtils.isAfter(adjustedDateDetailsDTO.getChangedScheduleDate(), nextRepaymentPeriodDueDate)) {
|
||||
nextRepaymentPeriodDueDate = generateNextRepaymentDate(nextRepaymentPeriodDueDate, loanApplicationTerms,
|
||||
isFirstRepayment);
|
||||
}
|
||||
adjustedDateDetailsDTO.setNextRepaymentPeriodDueDate(nextRepaymentPeriodDueDate);
|
||||
}
|
||||
WorkingDaysUtil.updateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO.getWorkingDays());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate) {
|
||||
LocalDate dueRepaymentPeriodDate = startDate;
|
||||
switch (frequency) {
|
||||
case DAYS:
|
||||
dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
|
||||
break;
|
||||
case WEEKS:
|
||||
dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
|
||||
break;
|
||||
case MONTHS:
|
||||
dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
|
||||
break;
|
||||
case YEARS:
|
||||
dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
log.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
return dueRepaymentPeriodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate,
|
||||
final LocalDate date) {
|
||||
boolean isScheduledDate = false;
|
||||
switch (frequency) {
|
||||
case DAYS:
|
||||
int diff = Math.toIntExact(ChronoUnit.DAYS.between(startDate, date));
|
||||
isScheduledDate = (diff % repaidEvery) == 0;
|
||||
break;
|
||||
case WEEKS:
|
||||
int weekDiff = Math.toIntExact(ChronoUnit.WEEKS.between(startDate, date));
|
||||
isScheduledDate = (weekDiff % repaidEvery) == 0;
|
||||
if (isScheduledDate) {
|
||||
LocalDate modifiedDate = startDate.plusWeeks(weekDiff);
|
||||
isScheduledDate = DateUtils.isEqual(modifiedDate, date);
|
||||
}
|
||||
break;
|
||||
case MONTHS:
|
||||
int monthDiff = Math.toIntExact(ChronoUnit.MONTHS.between(startDate, date));
|
||||
isScheduledDate = (monthDiff % repaidEvery) == 0;
|
||||
if (isScheduledDate) {
|
||||
LocalDate modifiedDate = startDate.plusMonths(monthDiff);
|
||||
isScheduledDate = DateUtils.isEqual(modifiedDate, date);
|
||||
}
|
||||
break;
|
||||
case YEARS:
|
||||
int yearDiff = Math.toIntExact(ChronoUnit.YEARS.between(startDate, date));
|
||||
isScheduledDate = (yearDiff % repaidEvery) == 0;
|
||||
if (isScheduledDate) {
|
||||
LocalDate modifiedDate = startDate.plusYears(yearDiff);
|
||||
isScheduledDate = DateUtils.isEqual(modifiedDate, date);
|
||||
}
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
log.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
return isScheduledDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||
final int repaidEvery, final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO,
|
||||
final LoanApplicationTerms loanApplicationTerms) {
|
||||
|
||||
LocalDate idealDisbursementDate = null;
|
||||
|
||||
switch (repaymentPeriodFrequencyType) {
|
||||
case DAYS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusDays(repaidEvery);
|
||||
break;
|
||||
case WEEKS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusWeeks(repaidEvery);
|
||||
break;
|
||||
case MONTHS:
|
||||
if (loanCalendar == null) {
|
||||
idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery);
|
||||
} else {
|
||||
idealDisbursementDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(),
|
||||
firstRepaymentDate.minusMonths(repaidEvery), firstRepaymentDate.minusMonths(repaidEvery), repaidEvery,
|
||||
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType),
|
||||
holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
|
||||
loanApplicationTerms.getNumberOfdays());
|
||||
}
|
||||
break;
|
||||
case YEARS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusYears(repaidEvery);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
|
||||
return idealDisbursementDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate,
|
||||
LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||
|
||||
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
boolean isFirstRepayment = true;
|
||||
while (!DateUtils.isAfter(generatedDate, lastRepaymentDate)) {
|
||||
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
|
||||
isFirstRepayment = false;
|
||||
}
|
||||
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
|
||||
return generatedDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(LocalDate lastRepaymentDate,
|
||||
LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||
|
||||
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
boolean isFirstRepayment = true;
|
||||
if (loanApplicationTerms.getNewScheduledDueDateStart() != null) {
|
||||
generatedDate = loanApplicationTerms.getNewScheduledDueDateStart();
|
||||
isFirstRepayment = false;
|
||||
}
|
||||
LocalDate adjustedDate = generatedDate;
|
||||
while (!DateUtils.isAfter(adjustedDate, lastRepaymentDate)) {
|
||||
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
|
||||
adjustedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
|
||||
isFirstRepayment = false;
|
||||
}
|
||||
return adjustedDate;
|
||||
}
|
||||
|
||||
public LocalDate generateNextRepaymentDateWhenHolidayApply(final LocalDate lastRepaymentDate,
|
||||
final LoanApplicationTerms loanApplicationTerms) {
|
||||
LocalDate seedDate;
|
||||
String reccuringString;
|
||||
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
|
||||
LocalDate dueRepaymentPeriodDate = lastRepaymentDate;
|
||||
dueRepaymentPeriodDate = (LocalDate) CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
|
||||
loanApplicationTerms.getRepaymentPeriodFrequencyType());
|
||||
if (currentCalendar != null) {
|
||||
// If we have currentCalendar object, this means there is a
|
||||
// calendar associated with
|
||||
// the loan, and we should use it in order to calculate next
|
||||
// repayment
|
||||
|
||||
CalendarHistory calendarHistory = null;
|
||||
CalendarHistoryDataWrapper calendarHistoryDataWrapper = loanApplicationTerms.getCalendarHistoryDataWrapper();
|
||||
if (calendarHistoryDataWrapper != null) {
|
||||
calendarHistory = loanApplicationTerms.getCalendarHistoryDataWrapper().getCalendarHistory(dueRepaymentPeriodDate);
|
||||
}
|
||||
|
||||
// get the start date from the calendar history
|
||||
if (calendarHistory == null) {
|
||||
seedDate = currentCalendar.getStartDateLocalDate();
|
||||
reccuringString = currentCalendar.getRecurrence();
|
||||
} else {
|
||||
seedDate = calendarHistory.getStartDate();
|
||||
reccuringString = calendarHistory.getRecurrence();
|
||||
}
|
||||
|
||||
dueRepaymentPeriodDate = CalendarUtils.getNextRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate,
|
||||
loanApplicationTerms.getRepaymentEvery(),
|
||||
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
|
||||
loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(), loanApplicationTerms.getNumberOfdays());
|
||||
}
|
||||
|
||||
return dueRepaymentPeriodDate;
|
||||
}
|
||||
}
|
||||
+29
-12
@@ -249,6 +249,8 @@ public final class LoanApplicationTerms {
|
||||
this.inArrearsTolerance = builder.inArrearsTolerance;
|
||||
this.disbursementDatas = builder.disbursementDatas;
|
||||
this.downPaymentAmount = builder.downPaymentAmount;
|
||||
this.submittedOnDate = builder.submittedOnDate;
|
||||
this.seedDate = builder.seedDate;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@@ -272,6 +274,8 @@ public final class LoanApplicationTerms {
|
||||
private Money inArrearsTolerance;
|
||||
private List<DisbursementData> disbursementDatas;
|
||||
private Money downPaymentAmount;
|
||||
private LocalDate submittedOnDate;
|
||||
private LocalDate seedDate;
|
||||
|
||||
public Builder currency(ApplicationCurrency currency) {
|
||||
this.currency = currency;
|
||||
@@ -343,11 +347,6 @@ public final class LoanApplicationTerms {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder variationsDataWrapper(LoanTermVariationsDataWrapper variationsDataWrapper) {
|
||||
this.variationsDataWrapper = variationsDataWrapper;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fixedLength(Integer fixedLength) {
|
||||
this.fixedLength = fixedLength;
|
||||
return this;
|
||||
@@ -368,23 +367,41 @@ public final class LoanApplicationTerms {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder submittedOnDate(LocalDate submittedOnDate) {
|
||||
this.submittedOnDate = submittedOnDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder seedDate(LocalDate seedDate) {
|
||||
this.seedDate = seedDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanApplicationTerms build() {
|
||||
return new LoanApplicationTerms(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData) {
|
||||
Money principal = Money.of(modelData.currency().toData(), modelData.disbursementAmount());
|
||||
Money downPaymentAmount = Money.zero(modelData.currency().toData());
|
||||
public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData, MathContext mc) {
|
||||
Money principal = Money.of(modelData.currency().toData(), modelData.disbursementAmount(), mc);
|
||||
Money downPaymentAmount = Money.zero(modelData.currency().toData(), mc);
|
||||
|
||||
if (modelData.downPaymentEnabled()) {
|
||||
downPaymentAmount = Money.of(modelData.currency().toData(),
|
||||
MathUtil.percentageOf(principal.getAmount(), modelData.disbursementAmount(), 19));
|
||||
MathUtil.percentageOf(principal.getAmount(), modelData.disbursementAmount(), mc), mc);
|
||||
if (modelData.installmentAmountInMultiplesOf() != null) {
|
||||
downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, modelData.installmentAmountInMultiplesOf());
|
||||
downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, modelData.installmentAmountInMultiplesOf(), mc);
|
||||
}
|
||||
}
|
||||
|
||||
LocalDate seedDate;
|
||||
|
||||
if (modelData.disbursementDate() != null) {
|
||||
seedDate = modelData.disbursementDate();
|
||||
} else {
|
||||
seedDate = modelData.scheduleGenerationStartDate();
|
||||
}
|
||||
|
||||
return new Builder().currency(modelData.currency()).loanTermFrequency(modelData.numberOfRepayments())
|
||||
.loanTermPeriodFrequencyType(PeriodFrequencyType.valueOf(modelData.repaymentFrequencyType()))
|
||||
.numberOfRepayments(modelData.numberOfRepayments()).repaymentEvery(modelData.repaymentFrequency())
|
||||
@@ -394,8 +411,8 @@ public final class LoanApplicationTerms {
|
||||
.annualNominalInterestRate(modelData.annualNominalInterestRate()).principal(principal)
|
||||
.expectedDisbursementDate(modelData.disbursementDate()).repaymentsStartingFromDate(modelData.scheduleGenerationStartDate())
|
||||
.daysInMonthType(modelData.daysInMonth()).daysInYearType(modelData.daysInYear()).fixedLength(modelData.fixedLength())
|
||||
.inArrearsTolerance(Money.zero(modelData.currency().toData())).disbursementDatas(new ArrayList<>())
|
||||
.downPaymentAmount(downPaymentAmount).build();
|
||||
.inArrearsTolerance(Money.zero(modelData.currency().toData(), mc)).disbursementDatas(new ArrayList<>())
|
||||
.downPaymentAmount(downPaymentAmount).submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate).build();
|
||||
}
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
|
||||
+14
-10
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -46,18 +47,20 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
|
||||
private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
|
||||
private boolean isEMIFixedSpecificToInstallment = false;
|
||||
BigDecimal rescheduleInterestPortion;
|
||||
private final MathContext mc;
|
||||
|
||||
public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
|
||||
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
|
||||
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent) {
|
||||
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent,
|
||||
final MathContext mc) {
|
||||
|
||||
return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance,
|
||||
interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent);
|
||||
interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent, mc);
|
||||
}
|
||||
|
||||
public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate,
|
||||
final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue,
|
||||
final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent) {
|
||||
final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent, final MathContext mc) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
@@ -68,6 +71,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
|
||||
this.penaltyChargesDue = penaltyChargesDue;
|
||||
this.totalDue = totalDue;
|
||||
this.recalculatedInterestComponent = recalculatedInterestComponent;
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,15 +148,15 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
|
||||
|
||||
@Override
|
||||
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
|
||||
this.feeChargesDue = this.feeChargesDue.plus(feeCharge);
|
||||
this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge);
|
||||
this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge);
|
||||
this.feeChargesDue = this.feeChargesDue.plus(feeCharge, mc);
|
||||
this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge, mc);
|
||||
this.totalDue = this.totalDue.plus(feeCharge, mc).plus(penaltyCharge, mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrincipalAmount(final Money principalDue) {
|
||||
this.principalDue = this.principalDue.plus(principalDue);
|
||||
this.totalDue = this.totalDue.plus(principalDue);
|
||||
this.principalDue = this.principalDue.plus(principalDue, mc);
|
||||
this.totalDue = this.totalDue.plus(principalDue, mc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,8 +166,8 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
|
||||
|
||||
@Override
|
||||
public void addInterestAmount(Money interestDue) {
|
||||
this.interestDue = this.interestDue.plus(interestDue);
|
||||
this.totalDue = this.totalDue.plus(interestDue);
|
||||
this.interestDue = this.interestDue.plus(interestDue, mc);
|
||||
this.totalDue = this.totalDue.plus(interestDue, mc);
|
||||
}
|
||||
|
||||
public void setPeriodNumber(int periodNumber) {
|
||||
|
||||
+3
-2
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
|
||||
@@ -27,8 +28,8 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
|
||||
public interface ScheduledDateGenerator {
|
||||
|
||||
List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms,
|
||||
HolidayDetailDTO holidayDetailDTO);
|
||||
List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(MathContext mc, LocalDate scheduledDueDate,
|
||||
LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
|
||||
+5
-2
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
@@ -64,6 +65,7 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -142,11 +144,12 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
|
||||
@NotNull
|
||||
private LoanRepaymentScheduleInstallment createPeriod(int periodId, LocalDate start, LocalDate end) {
|
||||
LoanRepaymentScheduleInstallment period = Mockito.mock(LoanRepaymentScheduleInstallment.class);
|
||||
MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
|
||||
Mockito.when(period.getInstallmentNumber()).thenReturn(periodId);
|
||||
Mockito.when(period.getFromDate()).thenReturn(start);
|
||||
Mockito.when(period.getDueDate()).thenReturn(end);
|
||||
Money principal = Money.of(currency, new BigDecimal("1000.0"));
|
||||
Money interest = Money.of(currency, BigDecimal.ZERO);
|
||||
Money principal = Money.of(currency, new BigDecimal("1000.0"), mc);
|
||||
Money interest = Money.of(currency, BigDecimal.ZERO, mc);
|
||||
|
||||
Mockito.when(period.getPrincipal(eq(currency))).thenReturn(principal);
|
||||
Mockito.when(period.getInterestCharged(eq(currency))).thenReturn(interest);
|
||||
|
||||
Reference in New Issue
Block a user