FINERACT-2326: Improve null-safety
This commit is contained in:
+4
@@ -335,6 +335,10 @@ public final class MathUtil {
|
|||||||
return nullToDefault(value, Money.zero(currency));
|
return nullToDefault(value, Money.zero(currency));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Money nullToZero(Money value, @NotNull MonetaryCurrency currency, @NotNull MathContext mc) {
|
||||||
|
return nullToDefault(value, Money.zero(currency, mc));
|
||||||
|
}
|
||||||
|
|
||||||
public static Money nullToDefault(Money value, Money def) {
|
public static Money nullToDefault(Money value, Money def) {
|
||||||
return value == null ? def : value;
|
return value == null ? def : value;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-23
@@ -125,8 +125,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
private void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||||
scheduleModel.repaymentPeriods().stream().filter(rp -> !operation.getSubmittedOnDate().isAfter(rp.getFromDate())).forEach(
|
scheduleModel.repaymentPeriods().stream().filter(rp -> !operation.getSubmittedOnDate().isAfter(rp.getFromDate()))
|
||||||
rp -> rp.setTotalDisbursedAmount(MathUtil.nullToZero(rp.getTotalDisbursedAmount()).add(operation.getAmount().getAmount())));
|
.forEach(rp -> rp.setTotalDisbursedAmount(rp.getTotalDisbursedAmount().add(operation.getAmount())));
|
||||||
|
|
||||||
scheduleModel
|
scheduleModel
|
||||||
.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), operation.getAmount(),
|
.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), operation.getAmount(),
|
||||||
@@ -162,8 +162,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
|
|
||||||
private void addCapitalizedIncome(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
private void addCapitalizedIncome(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||||
scheduleModel.repaymentPeriods().stream().filter(rp -> !operation.getSubmittedOnDate().isAfter(rp.getFromDate()))
|
scheduleModel.repaymentPeriods().stream().filter(rp -> !operation.getSubmittedOnDate().isAfter(rp.getFromDate()))
|
||||||
.forEach(rp -> rp.setTotalCapitalizedIncomeAmount(
|
.forEach(rp -> rp.setTotalCapitalizedIncomeAmount(rp.getTotalCapitalizedIncomeAmount().plus(operation.getAmount())));
|
||||||
MathUtil.nullToZero(rp.getTotalCapitalizedIncomeAmount()).add(operation.getAmount().getAmount())));
|
|
||||||
|
|
||||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), scheduleModel.zero(),
|
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), scheduleModel.zero(),
|
||||||
scheduleModel.zero(), operation.getAmount()).ifPresent((repaymentPeriod) -> {
|
scheduleModel.zero(), operation.getAmount()).ifPresent((repaymentPeriod) -> {
|
||||||
@@ -225,7 +224,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
|
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
|
||||||
boolean transactionDateIsBefore = transactionDate.isBefore(repaymentPeriod.get().getFromDate());
|
|
||||||
repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
|
repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
|
||||||
// If it is paid late, we need to calculate with the period due date
|
// If it is paid late, we need to calculate with the period due date
|
||||||
LocalDate balanceCorrectionDate = DateUtils.isBefore(repaymentPeriodDueDate, transactionDate) ? repaymentPeriodDueDate
|
LocalDate balanceCorrectionDate = DateUtils.isBefore(repaymentPeriodDueDate, transactionDate) ? repaymentPeriodDueDate
|
||||||
@@ -235,6 +233,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
repaymentPeriod.ifPresent(rp -> {
|
repaymentPeriod.ifPresent(rp -> {
|
||||||
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
|
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
|
||||||
// fully paid
|
// fully paid
|
||||||
|
boolean transactionDateIsBefore = transactionDate.isBefore(repaymentPeriod.get().getFromDate());
|
||||||
if (transactionDateIsBefore
|
if (transactionDateIsBefore
|
||||||
&& rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest())) {
|
&& rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest())) {
|
||||||
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
|
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalCreditedAmount()));
|
||||||
@@ -368,7 +367,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
return recalculatedScheduleModelTillDate.getLastRepaymentPeriod();
|
return recalculatedScheduleModelTillDate.getLastRepaymentPeriod();
|
||||||
} else {
|
} else {
|
||||||
// if target date is before 1st disbursement date, we use 1st repayment period
|
// if target date is before 1st disbursement date, we use 1st repayment period
|
||||||
return recalculatedScheduleModelTillDate.repaymentPeriods().get(0);
|
return recalculatedScheduleModelTillDate.repaymentPeriods().getFirst();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -406,7 +405,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate targetDate) {
|
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate targetDate) {
|
||||||
MathContext mc = scheduleModel.mc();
|
MathContext mc = scheduleModel.mc();
|
||||||
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
|
||||||
boolean isBeforeFirstDisbursement = targetDate.isBefore(scheduleModelCopy.repaymentPeriods().get(0).getFromDate());
|
boolean isBeforeFirstDisbursement = targetDate.isBefore(scheduleModelCopy.repaymentPeriods().getFirst().getFromDate());
|
||||||
boolean isAfterMaturityDate = !targetDate.isBefore(scheduleModelCopy.getLastRepaymentPeriod().getDueDate());
|
boolean isAfterMaturityDate = !targetDate.isBefore(scheduleModelCopy.getLastRepaymentPeriod().getDueDate());
|
||||||
if (isBeforeFirstDisbursement) {
|
if (isBeforeFirstDisbursement) {
|
||||||
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().clear());
|
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().clear());
|
||||||
@@ -436,13 +435,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
.subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
|
.subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
|
||||||
});
|
});
|
||||||
} else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod)
|
} else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod)
|
||||||
&& (rp.getInterestPeriods().get(0).getCreditedInterest().isGreaterThanZero()
|
&& (rp.getInterestPeriods().getFirst().getCreditedInterest().isGreaterThanZero()
|
||||||
|| rp.getInterestPeriods().get(0).getCreditedPrincipal().isGreaterThanZero())) {
|
|| rp.getInterestPeriods().getFirst().getCreditedPrincipal().isGreaterThanZero())) {
|
||||||
// NOTE: we need to check whether there is credited on the 1st interest period of the next
|
// NOTE: we need to check whether there is credited on the 1st interest period of the next
|
||||||
// period
|
// period
|
||||||
// if so, we need to retain that interest period, but need to update due date to match with from
|
// if so, we need to retain that interest period, but need to update due date to match with from
|
||||||
// date -> 0 interest
|
// date -> 0 interest
|
||||||
rp.getInterestPeriods().get(0).setDueDate(rp.getInterestPeriods().get(0).getFromDate());
|
rp.getInterestPeriods().getFirst().setDueDate(rp.getInterestPeriods().getFirst().getFromDate());
|
||||||
if (rp.getInterestPeriods().size() > 1) {
|
if (rp.getInterestPeriods().size() > 1) {
|
||||||
rp.getInterestPeriods().subList(1, rp.getInterestPeriods().size()).clear();
|
rp.getInterestPeriods().subList(1, rp.getInterestPeriods().size()).clear();
|
||||||
}
|
}
|
||||||
@@ -580,7 +579,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
if (newScheduleModel == null) {
|
if (newScheduleModel == null) {
|
||||||
newScheduleModel = scheduleModel.deepCopy(mc);
|
newScheduleModel = scheduleModel.deepCopy(mc);
|
||||||
}
|
}
|
||||||
final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate();
|
final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.getFirst().getDueDate();
|
||||||
newScheduleModel.repaymentPeriods().forEach(period -> {
|
newScheduleModel.repaymentPeriods().forEach(period -> {
|
||||||
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)
|
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)
|
||||||
&& !adjustedEqualMonthlyInstallmentValue.isLessThan(period.getTotalPaidAmount())) {
|
&& !adjustedEqualMonthlyInstallmentValue.isLessThan(period.getTotalPaidAmount())) {
|
||||||
@@ -913,8 +912,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
|
|
||||||
final MathContext mc = scheduleModel.mc();
|
final MathContext mc = scheduleModel.mc();
|
||||||
final CurrencyData currency = scheduleModel.loanProductRelatedDetail().getCurrencyData();
|
final CurrencyData currency = scheduleModel.loanProductRelatedDetail().getCurrencyData();
|
||||||
RepaymentPeriod first = repaymentPeriods.getFirst();
|
RepaymentPeriod firstRepaymentPeriod = repaymentPeriods.getFirst();
|
||||||
RepaymentPeriod last = repaymentPeriods.getLast();
|
RepaymentPeriod lastRepaymentPeriod = repaymentPeriods.getLast();
|
||||||
Money sumOfInterest = Money.zero(currency);
|
Money sumOfInterest = Money.zero(currency);
|
||||||
for (RepaymentPeriod rp : repaymentPeriods) {
|
for (RepaymentPeriod rp : repaymentPeriods) {
|
||||||
Money interest = rp.calculateCalculatedDueInterest();
|
Money interest = rp.calculateCalculatedDueInterest();
|
||||||
@@ -923,12 +922,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// already repaid principals should be subtracted from total disbursed amount to calculate correct EMI.
|
// already repaid principals should be subtracted from total disbursed amount to calculate correct EMI.
|
||||||
BigDecimal alreadyRepaidPrincipals = first.getPrevious()
|
Money alreadyRepaidPrincipals = firstRepaymentPeriod.getPrevious()
|
||||||
.map(rp -> rp.calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(null)
|
.map(rp -> rp.calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(null).minus(rp.getOutstandingLoanBalance()))
|
||||||
.subtract(rp.getOutstandingLoanBalance().getAmount()))
|
.orElse(null);
|
||||||
.orElse(BigDecimal.ZERO);
|
Money total = firstRepaymentPeriod
|
||||||
Money total = Money
|
.calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(firstRepaymentPeriod.getLastInterestPeriod())
|
||||||
.of(currency, first.calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(first.getLastInterestPeriod()))
|
|
||||||
.plus(sumOfInterest).minus(alreadyRepaidPrincipals);
|
.plus(sumOfInterest).minus(alreadyRepaidPrincipals);
|
||||||
|
|
||||||
Money periodEmi = total.dividedBy(repaymentPeriods.size(), mc);
|
Money periodEmi = total.dividedBy(repaymentPeriods.size(), mc);
|
||||||
@@ -936,7 +934,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
Money remainder = total.minus(periodEmiInMultiplesOf.multipliedBy(repaymentPeriods.size(), mc));
|
Money remainder = total.minus(periodEmiInMultiplesOf.multipliedBy(repaymentPeriods.size(), mc));
|
||||||
|
|
||||||
repaymentPeriods.forEach(rp -> {
|
repaymentPeriods.forEach(rp -> {
|
||||||
Money emi = rp.equals(last) ? periodEmiInMultiplesOf.add(remainder) : periodEmiInMultiplesOf;
|
Money emi = rp.equals(lastRepaymentPeriod) ? periodEmiInMultiplesOf.add(remainder) : periodEmiInMultiplesOf;
|
||||||
rp.setEmi(emi);
|
rp.setEmi(emi);
|
||||||
rp.setOriginalEmi(emi);
|
rp.setOriginalEmi(emi);
|
||||||
rp.getInterestPeriods().forEach(InterestPeriod::updateOutstandingLoanBalance);
|
rp.getInterestPeriods().forEach(InterestPeriod::updateOutstandingLoanBalance);
|
||||||
@@ -959,7 +957,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
final MathContext mc = scheduleModel.mc();
|
final MathContext mc = scheduleModel.mc();
|
||||||
final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc));
|
final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc));
|
||||||
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc));
|
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc));
|
||||||
final RepaymentPeriod startPeriod = repaymentPeriods.get(0);
|
final RepaymentPeriod startPeriod = repaymentPeriods.getFirst();
|
||||||
|
|
||||||
final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation();
|
final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation();
|
||||||
|
|
||||||
@@ -985,7 +983,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
addDisbursement(scheduleModelCopy, operation.withZeroAmount());
|
addDisbursement(scheduleModelCopy, operation.withZeroAmount());
|
||||||
addCapitalizedIncome(scheduleModelCopy, operation.withZeroAmount());
|
addCapitalizedIncome(scheduleModelCopy, operation.withZeroAmount());
|
||||||
|
|
||||||
final LocalDate firstDueDate = repaymentPeriods.get(0).getDueDate();
|
final LocalDate firstDueDate = repaymentPeriods.getFirst().getDueDate();
|
||||||
scheduleModel.copyPeriodsFrom(firstDueDate, scheduleModelCopy.repaymentPeriods(), (newRepaymentPeriod, actualRepaymentPeriod) -> {
|
scheduleModel.copyPeriodsFrom(firstDueDate, scheduleModelCopy.repaymentPeriods(), (newRepaymentPeriod, actualRepaymentPeriod) -> {
|
||||||
actualRepaymentPeriod.setEmi(newRepaymentPeriod.getEmi());
|
actualRepaymentPeriod.setEmi(newRepaymentPeriod.getEmi());
|
||||||
actualRepaymentPeriod.setOriginalEmi(newRepaymentPeriod.getOriginalEmi());
|
actualRepaymentPeriod.setOriginalEmi(newRepaymentPeriod.getOriginalEmi());
|
||||||
@@ -1009,7 +1007,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
getUncountablePeriods(repaymentPeriods, penultimatePeriod.getEmi()));
|
getUncountablePeriods(repaymentPeriods, penultimatePeriod.getEmi()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new EmiAdjustment(repaymentPeriods.get(0).getEmi(), repaymentPeriods.get(0).getEmi().copy(0.0), repaymentPeriods, 0);
|
return new EmiAdjustment(repaymentPeriods.getFirst().getEmi(), repaymentPeriods.getFirst().getEmi().copy(0.0), repaymentPeriods, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void calculateRateFactorForScheduleTillDateInclusive(ProgressiveLoanInterestScheduleModel scheduleModelCopy,
|
private void calculateRateFactorForScheduleTillDateInclusive(ProgressiveLoanInterestScheduleModel scheduleModelCopy,
|
||||||
|
|||||||
+75
-36
@@ -32,13 +32,14 @@ import lombok.ToString;
|
|||||||
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString(exclude = { "repaymentPeriod" })
|
@ToString(exclude = { "repaymentPeriod" })
|
||||||
@EqualsAndHashCode(exclude = { "repaymentPeriod" })
|
@EqualsAndHashCode(exclude = { "repaymentPeriod" })
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
public class InterestPeriod implements Comparable<InterestPeriod> {
|
public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||||
|
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
@@ -49,6 +50,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
@Setter
|
@Setter
|
||||||
@NotNull
|
@NotNull
|
||||||
private LocalDate dueDate;
|
private LocalDate dueDate;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private BigDecimal rateFactor;
|
private BigDecimal rateFactor;
|
||||||
@Setter
|
@Setter
|
||||||
@@ -58,15 +60,14 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
private Money creditedPrincipal;
|
private Money creditedPrincipal;
|
||||||
/** Stores credited interest. Related transaction: Chargeback */
|
/** Stores credited interest. Related transaction: Chargeback */
|
||||||
private Money creditedInterest;
|
private Money creditedInterest;
|
||||||
|
|
||||||
private Money disbursementAmount;
|
private Money disbursementAmount;
|
||||||
private Money balanceCorrectionAmount;
|
private Money balanceCorrectionAmount;
|
||||||
private Money outstandingLoanBalance;
|
private Money outstandingLoanBalance;
|
||||||
|
|
||||||
private Money capitalizedIncomePrincipal;
|
private Money capitalizedIncomePrincipal;
|
||||||
|
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
|
@Getter
|
||||||
private final MathContext mc;
|
private final MathContext mc;
|
||||||
|
|
||||||
private final boolean isPaused;
|
private final boolean isPaused;
|
||||||
|
|
||||||
public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod, MathContext mc) {
|
public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod, MathContext mc) {
|
||||||
@@ -90,59 +91,60 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
|
|
||||||
public static InterestPeriod withEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate,
|
public static InterestPeriod withEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate,
|
||||||
LocalDate dueDate) {
|
LocalDate dueDate) {
|
||||||
final Money zero = repaymentPeriod.getEmi().zero();
|
final Money zero = repaymentPeriod.getZero();
|
||||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
||||||
zero.getMc(), false);
|
zero.getMc(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InterestPeriod withEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate, LocalDate dueDate,
|
public static InterestPeriod withEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate, LocalDate dueDate,
|
||||||
boolean isPaused) {
|
boolean isPaused) {
|
||||||
final Money zero = repaymentPeriod.getEmi().zero();
|
final Money zero = repaymentPeriod.getZero();
|
||||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
||||||
zero.getMc(), isPaused);
|
zero.getMc(), isPaused);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InterestPeriod withPausedAndEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate,
|
public static InterestPeriod withPausedAndEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate,
|
||||||
LocalDate dueDate) {
|
LocalDate dueDate) {
|
||||||
final Money zero = repaymentPeriod.getEmi().zero();
|
final Money zero = repaymentPeriod.getZero();
|
||||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
||||||
zero.getMc(), true);
|
zero.getMc(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@NotNull InterestPeriod o) {
|
public int compareTo(@NotNull InterestPeriod o) {
|
||||||
return dueDate.compareTo(o.dueDate);
|
return getDueDate().compareTo(o.getDueDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addBalanceCorrectionAmount(final Money balanceCorrectionAmount) {
|
public void addBalanceCorrectionAmount(final Money additionalBalanceCorrectionAmount) {
|
||||||
this.balanceCorrectionAmount = MathUtil.plus(this.balanceCorrectionAmount, balanceCorrectionAmount);
|
this.balanceCorrectionAmount = MathUtil.plus(this.getBalanceCorrectionAmount(), additionalBalanceCorrectionAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDisbursementAmount(final Money disbursementAmount) {
|
public void addDisbursementAmount(final Money additionalDisbursementAmount) {
|
||||||
this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount, mc);
|
this.disbursementAmount = MathUtil.plus(this.getDisbursementAmount(), additionalDisbursementAmount, getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCreditedPrincipalAmount(final Money creditedPrincipal) {
|
public void addCreditedPrincipalAmount(final Money additionalCreditedPrincipal) {
|
||||||
this.creditedPrincipal = MathUtil.plus(this.creditedPrincipal, creditedPrincipal, mc);
|
this.creditedPrincipal = MathUtil.plus(this.getCreditedPrincipal(), additionalCreditedPrincipal, getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCreditedInterestAmount(final Money creditedInterest) {
|
public void addCreditedInterestAmount(final Money additionalCreditedInterest) {
|
||||||
this.creditedInterest = MathUtil.plus(this.creditedInterest, creditedInterest, mc);
|
this.creditedInterest = MathUtil.plus(this.getCreditedInterest(), additionalCreditedInterest, getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCapitalizedIncomePrincipalAmount(final Money capitalizedIncomePrincipal) {
|
public void addCapitalizedIncomePrincipalAmount(final Money additionalCapitalizedIncomePrincipal) {
|
||||||
this.capitalizedIncomePrincipal = MathUtil.plus(this.capitalizedIncomePrincipal, capitalizedIncomePrincipal, mc);
|
this.capitalizedIncomePrincipal = MathUtil.plus(this.getCapitalizedIncomePrincipal(), additionalCapitalizedIncomePrincipal,
|
||||||
|
getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getCalculatedDueInterest() {
|
public BigDecimal getCalculatedDueInterest() {
|
||||||
if (isPaused) {
|
if (isPaused()) {
|
||||||
return creditedInterest.getAmount();
|
return getCreditedInterest().getAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
|
long lengthTillPeriodDueDate = getLengthTillPeriodDueDate();
|
||||||
final BigDecimal interestDueTillRepaymentDueDate = getCalculatedDueInterest(
|
final BigDecimal interestDueTillRepaymentDueDate = getCalculatedDueInterest(
|
||||||
getRepaymentPeriod().getLoanProductRelatedDetail().getInterestMethod(), lengthTillPeriodDueDate); //
|
getRepaymentPeriod().getLoanProductRelatedDetail().getInterestMethod(), lengthTillPeriodDueDate); //
|
||||||
return MathUtil.negativeToZero(MathUtil.add(mc, creditedInterest.getAmount(), interestDueTillRepaymentDueDate));
|
return MathUtil.negativeToZero(MathUtil.add(getMc(), getCreditedInterest().getAmount(), interestDueTillRepaymentDueDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getCalculatedDueInterest(InterestMethod method, long lengthTillPeriodDueDate) {
|
public BigDecimal getCalculatedDueInterest(InterestMethod method, long lengthTillPeriodDueDate) {
|
||||||
@@ -150,22 +152,22 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
BigDecimal baseAmount = switch (method) {
|
BigDecimal baseAmount = switch (method) {
|
||||||
case FLAT -> MathUtil.nullToZero(getRepaymentPeriod().calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(this));
|
case FLAT -> getRepaymentPeriod().calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(this).getAmount();
|
||||||
case DECLINING_BALANCE -> getOutstandingLoanBalance().getAmount();
|
case DECLINING_BALANCE -> getOutstandingLoanBalance().getAmount();
|
||||||
default -> throw new UnsupportedOperationException("Method not implemented: " + method);
|
default -> throw new UnsupportedOperationException("Method not implemented: " + method);
|
||||||
};
|
};
|
||||||
return baseAmount //
|
return baseAmount //
|
||||||
.multiply(getRateFactorTillPeriodDueDate(), mc) //
|
.multiply(getRateFactorTillPeriodDueDate(), getMc()) //
|
||||||
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
|
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), getMc()) //
|
||||||
.multiply(BigDecimal.valueOf(getLength()), mc);
|
.multiply(BigDecimal.valueOf(getLength()), getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLength() {
|
public long getLength() {
|
||||||
return DateUtils.getDifferenceInDays(fromDate, dueDate);
|
return DateUtils.getDifferenceInDays(getFromDate(), getDueDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getLengthTillPeriodDueDate() {
|
public long getLengthTillPeriodDueDate() {
|
||||||
return DateUtils.getDifferenceInDays(fromDate, repaymentPeriod.getDueDate());
|
return DateUtils.getDifferenceInDays(getFromDate(), getRepaymentPeriod().getDueDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateOutstandingLoanBalance() {
|
public void updateOutstandingLoanBalance() {
|
||||||
@@ -174,19 +176,19 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
if (previousRepaymentPeriod.isPresent()) {
|
if (previousRepaymentPeriod.isPresent()) {
|
||||||
InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getLastInterestPeriod();
|
InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getLastInterestPeriod();
|
||||||
this.outstandingLoanBalance = MathUtil.negativeToZero(previousInterestPeriod.getOutstandingLoanBalance()//
|
this.outstandingLoanBalance = MathUtil.negativeToZero(previousInterestPeriod.getOutstandingLoanBalance()//
|
||||||
.plus(previousInterestPeriod.getDisbursementAmount(), mc)//
|
.plus(previousInterestPeriod.getDisbursementAmount(), getMc())//
|
||||||
.plus(previousInterestPeriod.getCapitalizedIncomePrincipal(), mc)//
|
.plus(previousInterestPeriod.getCapitalizedIncomePrincipal(), getMc())//
|
||||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc)//
|
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), getMc())//
|
||||||
.minus(previousRepaymentPeriod.get().getDuePrincipal(), mc)//
|
.minus(previousRepaymentPeriod.get().getDuePrincipal(), getMc())//
|
||||||
.plus(previousRepaymentPeriod.get().getPaidPrincipal(), mc), mc);//
|
.plus(previousRepaymentPeriod.get().getPaidPrincipal(), getMc()), getMc());//
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int index = getRepaymentPeriod().getInterestPeriods().indexOf(this);
|
int index = getRepaymentPeriod().getInterestPeriods().indexOf(this);
|
||||||
InterestPeriod previousInterestPeriod = getRepaymentPeriod().getInterestPeriods().get(index - 1);
|
InterestPeriod previousInterestPeriod = getRepaymentPeriod().getInterestPeriods().get(index - 1);
|
||||||
this.outstandingLoanBalance = MathUtil.negativeToZero(previousInterestPeriod.getOutstandingLoanBalance() //
|
this.outstandingLoanBalance = MathUtil.negativeToZero(previousInterestPeriod.getOutstandingLoanBalance() //
|
||||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc) //
|
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), getMc()) //
|
||||||
.plus(previousInterestPeriod.getCapitalizedIncomePrincipal(), mc) //
|
.plus(previousInterestPeriod.getCapitalizedIncomePrincipal(), getMc()) //
|
||||||
.plus(previousInterestPeriod.getDisbursementAmount(), mc)); //
|
.plus(previousInterestPeriod.getDisbursementAmount(), getMc())); //
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,10 +196,47 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
|||||||
* Include principal like amounts (all disbursement amount + credited principal)
|
* Include principal like amounts (all disbursement amount + credited principal)
|
||||||
*/
|
*/
|
||||||
public Money getCreditedAmounts() {
|
public Money getCreditedAmounts() {
|
||||||
return getDisbursementAmount().plus(getCreditedPrincipal(), mc).plus(getCapitalizedIncomePrincipal(), mc);
|
return MathUtil.plus(mc, getDisbursementAmount(), getCreditedPrincipal(), getCapitalizedIncomePrincipal());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFirstInterestPeriod() {
|
public boolean isFirstInterestPeriod() {
|
||||||
return this.equals(getRepaymentPeriod().getFirstInterestPeriod());
|
return this.equals(getRepaymentPeriod().getFirstInterestPeriod());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MonetaryCurrency getCurrency() {
|
||||||
|
return getRepaymentPeriod().getCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getCreditedPrincipal() {
|
||||||
|
return MathUtil.nullToZero(creditedPrincipal, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getCreditedInterest() {
|
||||||
|
return MathUtil.nullToZero(creditedInterest, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getDisbursementAmount() {
|
||||||
|
return MathUtil.nullToZero(disbursementAmount, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getBalanceCorrectionAmount() {
|
||||||
|
return MathUtil.nullToZero(balanceCorrectionAmount, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getOutstandingLoanBalance() {
|
||||||
|
return MathUtil.nullToZero(outstandingLoanBalance, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getCapitalizedIncomePrincipal() {
|
||||||
|
return MathUtil.nullToZero(capitalizedIncomePrincipal, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getRateFactor() {
|
||||||
|
return MathUtil.nullToZero(rateFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getRateFactorTillPeriodDueDate() {
|
||||||
|
return MathUtil.nullToZero(rateFactorTillPeriodDueDate);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-69
@@ -33,13 +33,14 @@ import lombok.Setter;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||||
import org.apache.fineract.portfolio.util.Memo;
|
import org.apache.fineract.portfolio.util.Memo;
|
||||||
|
|
||||||
@ToString(exclude = { "previous" })
|
@ToString(exclude = { "previous" })
|
||||||
@EqualsAndHashCode(exclude = { "previous" })
|
@EqualsAndHashCode(exclude = { "previous" })
|
||||||
public final class RepaymentPeriod {
|
public class RepaymentPeriod {
|
||||||
|
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
private final RepaymentPeriod previous;
|
private final RepaymentPeriod previous;
|
||||||
@@ -52,20 +53,16 @@ public final class RepaymentPeriod {
|
|||||||
@Setter
|
@Setter
|
||||||
private List<InterestPeriod> interestPeriods;
|
private List<InterestPeriod> interestPeriods;
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
|
||||||
private Money emi;
|
private Money emi;
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
|
||||||
private Money originalEmi;
|
private Money originalEmi;
|
||||||
@Getter
|
|
||||||
private Money paidPrincipal;
|
private Money paidPrincipal;
|
||||||
@Getter
|
|
||||||
private Money paidInterest;
|
private Money paidInterest;
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
|
||||||
private Money futureUnrecognizedInterest;
|
private Money futureUnrecognizedInterest;
|
||||||
|
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
|
@Getter
|
||||||
private final MathContext mc;
|
private final MathContext mc;
|
||||||
|
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
@@ -81,18 +78,17 @@ public final class RepaymentPeriod {
|
|||||||
private boolean isInterestMoved = false;
|
private boolean isInterestMoved = false;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
private Money totalDisbursedAmount;
|
||||||
private BigDecimal totalDisbursedAmount;
|
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@Getter
|
private Money totalCapitalizedIncomeAmount;
|
||||||
private BigDecimal totalCapitalizedIncomeAmount;
|
|
||||||
|
|
||||||
@JsonExclude
|
@JsonExclude
|
||||||
@Getter
|
@Getter
|
||||||
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail;
|
private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail;
|
||||||
|
@JsonExclude
|
||||||
|
private MonetaryCurrency currency;
|
||||||
|
|
||||||
private RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
|
protected RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
|
||||||
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc,
|
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, Money futureUnrecognizedInterest, MathContext mc,
|
||||||
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
|
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail) {
|
||||||
this.previous = previous;
|
this.previous = previous;
|
||||||
@@ -119,34 +115,34 @@ public final class RepaymentPeriod {
|
|||||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
|
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
|
||||||
zero, mc, loanProductRelatedDetail);
|
zero, mc, loanProductRelatedDetail);
|
||||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod, fromDate, dueDate));
|
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod, fromDate, dueDate));
|
||||||
return newRepaymentPeriod;
|
return newRepaymentPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
|
||||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, repaymentPeriod.paidPrincipal,
|
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(),
|
||||||
repaymentPeriod.paidInterest, repaymentPeriod.futureUnrecognizedInterest, mc,
|
repaymentPeriod.getPaidPrincipal(), repaymentPeriod.getPaidInterest(), repaymentPeriod.getFutureUnrecognizedInterest(), mc,
|
||||||
repaymentPeriod.getLoanProductRelatedDetail());
|
repaymentPeriod.getLoanProductRelatedDetail());
|
||||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
||||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
|
newRepaymentPeriod.getInterestPeriods().add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod, mc));
|
||||||
}
|
}
|
||||||
return newRepaymentPeriod;
|
return newRepaymentPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||||
final Money zero = repaymentPeriod.emi.zero();
|
final Money zero = Money.zero(repaymentPeriod.getCurrency(), mc);
|
||||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.getFromDate(),
|
||||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, zero, zero, zero, mc,
|
repaymentPeriod.getDueDate(), new ArrayList<>(), repaymentPeriod.getEmi(), repaymentPeriod.getOriginalEmi(), zero, zero,
|
||||||
repaymentPeriod.getLoanProductRelatedDetail());
|
zero, mc, repaymentPeriod.getLoanProductRelatedDetail());
|
||||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
for (InterestPeriod interestPeriod : repaymentPeriod.getInterestPeriods()) {
|
||||||
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
|
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
|
||||||
if (!interestPeriodCopy.getBalanceCorrectionAmount().isZero()) {
|
if (!interestPeriodCopy.getBalanceCorrectionAmount().isZero()) {
|
||||||
interestPeriodCopy.addBalanceCorrectionAmount(interestPeriodCopy.getBalanceCorrectionAmount().negated());
|
interestPeriodCopy.addBalanceCorrectionAmount(interestPeriodCopy.getBalanceCorrectionAmount().negated());
|
||||||
}
|
}
|
||||||
newRepaymentPeriod.interestPeriods.add(interestPeriodCopy);
|
newRepaymentPeriod.getInterestPeriods().add(interestPeriodCopy);
|
||||||
}
|
}
|
||||||
return newRepaymentPeriod;
|
return newRepaymentPeriod;
|
||||||
}
|
}
|
||||||
@@ -186,17 +182,17 @@ public final class RepaymentPeriod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Money calculateCalculatedDueInterest() {
|
public Money calculateCalculatedDueInterest() {
|
||||||
Money calculatedDueInterest = getZero(mc);
|
Money calculatedDueInterest = getZero();
|
||||||
if (!isInterestMoved) {
|
if (!isInterestMoved()) {
|
||||||
calculatedDueInterest = Money.of(emi.getCurrencyData(),
|
calculatedDueInterest = Money.of(getEmi().getCurrencyData(),
|
||||||
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add),
|
getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(BigDecimal.ZERO, BigDecimal::add),
|
||||||
mc);
|
mc);
|
||||||
}
|
}
|
||||||
calculatedDueInterest = calculatedDueInterest.add(getFutureUnrecognizedInterest(), mc);
|
calculatedDueInterest = calculatedDueInterest.add(getFutureUnrecognizedInterest(), getMc());
|
||||||
if (getPrevious().isPresent()) {
|
if (getPrevious().isPresent()) {
|
||||||
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), mc);
|
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), getMc());
|
||||||
}
|
}
|
||||||
return MathUtil.negativeToZero(calculatedDueInterest, mc);
|
return MathUtil.negativeToZero(calculatedDueInterest, getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,7 +219,7 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest() {
|
public Money getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest() {
|
||||||
return getEmi().plus(getTotalCreditedAmount(), mc).plus(getFutureUnrecognizedInterest(), mc); //
|
return getEmi().plus(getTotalCreditedAmount(), mc).plus(getFutureUnrecognizedInterest(), getMc()); //
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -232,7 +228,8 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getCalculatedDuePrincipal() {
|
public Money getCalculatedDuePrincipal() {
|
||||||
return MathUtil.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getCalculatedDueInterest(), mc), mc);
|
return MathUtil.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getCalculatedDueInterest(), getMc()),
|
||||||
|
getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -241,9 +238,9 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getCreditedPrincipal() {
|
public Money getCreditedPrincipal() {
|
||||||
return MathUtil.negativeToZero(interestPeriods.stream() //
|
return MathUtil.negativeToZero(getInterestPeriods().stream() //
|
||||||
.map(InterestPeriod::getCreditedPrincipal) //
|
.map(InterestPeriod::getCreditedPrincipal) //
|
||||||
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
|
.reduce(getZero(), (value, previous) -> value.plus(previous, getMc())), getMc()); //
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,9 +249,9 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getCreditedInterest() {
|
public Money getCreditedInterest() {
|
||||||
return MathUtil.negativeToZero(interestPeriods.stream() //
|
return MathUtil.negativeToZero(getInterestPeriods().stream() //
|
||||||
.map(InterestPeriod::getCreditedInterest) //
|
.map(InterestPeriod::getCreditedInterest) //
|
||||||
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
|
.reduce(getZero(), (value, previous) -> value.plus(previous, getMc())), getMc()); //
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,9 +260,9 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getCapitalizedIncomePrincipal() {
|
public Money getCapitalizedIncomePrincipal() {
|
||||||
return MathUtil.negativeToZero(interestPeriods.stream() //
|
return MathUtil.negativeToZero(getInterestPeriods().stream() //
|
||||||
.map(InterestPeriod::getCapitalizedIncomePrincipal) //
|
.map(InterestPeriod::getCapitalizedIncomePrincipal) //
|
||||||
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
|
.reduce(getZero(), (value, previous) -> value.plus(previous, getMc())), getMc()); //
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,8 +272,8 @@ public final class RepaymentPeriod {
|
|||||||
*/
|
*/
|
||||||
public Money getDuePrincipal() {
|
public Money getDuePrincipal() {
|
||||||
// Due principal might be the maximum paid if there is pay-off or early repayment
|
// Due principal might be the maximum paid if there is pay-off or early repayment
|
||||||
return MathUtil.max(
|
return MathUtil.max(MathUtil
|
||||||
MathUtil.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getDueInterest(), mc), mc),
|
.negativeToZero(getEmiPlusCreditedAmountsPlusFutureUnrecognizedInterest().minus(getDueInterest(), getMc()), getMc()),
|
||||||
getPaidPrincipal(), false);
|
getPaidPrincipal(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +283,7 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getTotalCreditedAmount() {
|
public Money getTotalCreditedAmount() {
|
||||||
return getCreditedPrincipal().plus(getCreditedInterest(), mc);
|
return getCreditedPrincipal().plus(getCreditedInterest(), getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -295,7 +292,7 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getTotalPaidAmount() {
|
public Money getTotalPaidAmount() {
|
||||||
return getPaidPrincipal().plus(getPaidInterest());
|
return getPaidPrincipal().plus(getPaidInterest(), getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFullyPaid() {
|
public boolean isFullyPaid() {
|
||||||
@@ -309,35 +306,35 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getUnrecognizedInterest() {
|
public Money getUnrecognizedInterest() {
|
||||||
return getCalculatedDueInterest().minus(getDueInterest(), mc);
|
return getCalculatedDueInterest().minus(getDueInterest(), getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getCreditedAmounts() {
|
public Money getCreditedAmounts() {
|
||||||
return interestPeriods.stream().map(InterestPeriod::getCreditedAmounts).reduce(getZero(mc), (m1, m2) -> m1.plus(m2, mc));
|
return interestPeriods.stream().map(InterestPeriod::getCreditedAmounts).reduce(getZero(), (m1, m2) -> m1.plus(m2, getMc()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getOutstandingLoanBalance() {
|
public Money getOutstandingLoanBalance() {
|
||||||
if (outstandingBalanceCalculation == null) {
|
if (outstandingBalanceCalculation == null) {
|
||||||
outstandingBalanceCalculation = Memo.of(() -> {
|
outstandingBalanceCalculation = Memo.of(() -> {
|
||||||
InterestPeriod lastInterestPeriod = getInterestPeriods().get(getInterestPeriods().size() - 1);
|
InterestPeriod lastInterestPeriod = getInterestPeriods().getLast();
|
||||||
Money calculatedOutStandingLoanBalance = lastInterestPeriod.getOutstandingLoanBalance() //
|
Money calculatedOutStandingLoanBalance = lastInterestPeriod.getOutstandingLoanBalance() //
|
||||||
.plus(lastInterestPeriod.getBalanceCorrectionAmount(), mc) //
|
.plus(lastInterestPeriod.getBalanceCorrectionAmount(), getMc()) //
|
||||||
.plus(lastInterestPeriod.getCapitalizedIncomePrincipal(), mc) //
|
.plus(lastInterestPeriod.getCapitalizedIncomePrincipal(), getMc()) //
|
||||||
.plus(lastInterestPeriod.getDisbursementAmount(), mc) //
|
.plus(lastInterestPeriod.getDisbursementAmount(), getMc()) //
|
||||||
.minus(getDuePrincipal(), mc)//
|
.plus(getPaidPrincipal(), getMc()) //
|
||||||
.plus(getPaidPrincipal(), mc);//
|
.minus(getDuePrincipal(), getMc()); //
|
||||||
return MathUtil.negativeToZero(calculatedOutStandingLoanBalance, mc);
|
return MathUtil.negativeToZero(calculatedOutStandingLoanBalance, getMc());
|
||||||
}, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods, totalDisbursedAmount });
|
}, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods, totalDisbursedAmount });
|
||||||
}
|
}
|
||||||
return outstandingBalanceCalculation.get();
|
return outstandingBalanceCalculation.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPaidPrincipalAmount(Money paidPrincipal) {
|
public void addPaidPrincipalAmount(Money paidPrincipal) {
|
||||||
this.paidPrincipal = MathUtil.plus(this.paidPrincipal, paidPrincipal, mc);
|
this.paidPrincipal = MathUtil.plus(this.getPaidPrincipal(), paidPrincipal, getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPaidInterestAmount(Money paidInterest) {
|
public void addPaidInterestAmount(Money paidInterest) {
|
||||||
this.paidInterest = MathUtil.plus(this.paidInterest, paidInterest, mc);
|
this.paidInterest = MathUtil.plus(this.getPaidInterest(), paidInterest, getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getInitialBalanceForEmiRecalculation() {
|
public Money getInitialBalanceForEmiRecalculation() {
|
||||||
@@ -345,29 +342,28 @@ public final class RepaymentPeriod {
|
|||||||
if (getPrevious().isPresent()) {
|
if (getPrevious().isPresent()) {
|
||||||
initialBalance = getPrevious().get().getOutstandingLoanBalance();
|
initialBalance = getPrevious().get().getOutstandingLoanBalance();
|
||||||
} else {
|
} else {
|
||||||
initialBalance = getZero(mc);
|
initialBalance = getZero();
|
||||||
}
|
}
|
||||||
Money totalDisbursedAmount = getInterestPeriods().stream() //
|
Money totalDisbursedAmount = getInterestPeriods().stream() //
|
||||||
.map(InterestPeriod::getDisbursementAmount) //
|
.map(InterestPeriod::getDisbursementAmount) //
|
||||||
.reduce(getZero(mc), (m1, m2) -> m1.plus(m2, mc)); //
|
.reduce(getZero(), (m1, m2) -> m1.plus(m2, getMc())); //
|
||||||
Money totalCapitalizedIncomeAmount = getInterestPeriods().stream() //
|
Money totalCapitalizedIncomeAmount = getInterestPeriods().stream() //
|
||||||
.map(InterestPeriod::getCapitalizedIncomePrincipal) //
|
.map(InterestPeriod::getCapitalizedIncomePrincipal) //
|
||||||
.reduce(getZero(mc), (m1, m2) -> m1.plus(m2, mc)); //
|
.reduce(getZero(), (m1, m2) -> m1.plus(m2, getMc())); //
|
||||||
return initialBalance.add(totalDisbursedAmount, mc).add(totalCapitalizedIncomeAmount, mc);
|
return initialBalance.add(totalDisbursedAmount, getMc()).add(totalCapitalizedIncomeAmount, getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money getZero(MathContext mc) {
|
public Money getZero() {
|
||||||
// EMI is always initiated
|
return Money.zero(getCurrency(), getMc());
|
||||||
return this.emi.zero(mc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InterestPeriod getFirstInterestPeriod() {
|
public InterestPeriod getFirstInterestPeriod() {
|
||||||
return getInterestPeriods().get(0);
|
return getInterestPeriods().getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InterestPeriod getLastInterestPeriod() {
|
public InterestPeriod getLastInterestPeriod() {
|
||||||
List<InterestPeriod> interestPeriods = getInterestPeriods();
|
List<InterestPeriod> interestPeriods = getInterestPeriods();
|
||||||
return interestPeriods.get(interestPeriods.size() - 1);
|
return interestPeriods.getLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<InterestPeriod> findInterestPeriod(@NotNull LocalDate transactionDate) {
|
public Optional<InterestPeriod> findInterestPeriod(@NotNull LocalDate transactionDate) {
|
||||||
@@ -387,11 +383,11 @@ public final class RepaymentPeriod {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Money getOutstandingInterest() {
|
public Money getOutstandingInterest() {
|
||||||
return MathUtil.negativeToZero(getDueInterest().minus(getPaidInterest()), mc);
|
return MathUtil.negativeToZero(getDueInterest().minus(getPaidInterest()), getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money getOutstandingPrincipal() {
|
public Money getOutstandingPrincipal() {
|
||||||
return MathUtil.negativeToZero(getDuePrincipal().minus(getPaidPrincipal()), mc);
|
return MathUtil.negativeToZero(getDuePrincipal().minus(getPaidPrincipal()), getMc());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetDerivedComponents() {
|
public void resetDerivedComponents() {
|
||||||
@@ -404,21 +400,56 @@ public final class RepaymentPeriod {
|
|||||||
* can be null. if null it calculates total disbursement including last interest period.
|
* can be null. if null it calculates total disbursement including last interest period.
|
||||||
* @return disbursed and capitalized income amount till interest period.
|
* @return disbursed and capitalized income amount till interest period.
|
||||||
*/
|
*/
|
||||||
public BigDecimal calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(InterestPeriod tillPeriod) {
|
public Money calculateTotalDisbursedAndCapitalizedIncomeAmountTillGivenPeriod(InterestPeriod tillPeriod) {
|
||||||
BigDecimal res = MathUtil.nullToZero(getTotalDisbursedAmount()).add(MathUtil.nullToZero(getTotalCapitalizedIncomeAmount()));
|
Money res = MathUtil.plus(getMc(), getTotalDisbursedAmount(), getTotalCapitalizedIncomeAmount());
|
||||||
for (InterestPeriod interestPeriod : this.interestPeriods) {
|
for (InterestPeriod interestPeriod : this.getInterestPeriods()) {
|
||||||
if (interestPeriod.equals(tillPeriod)) {
|
if (interestPeriod.equals(tillPeriod)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!interestPeriod.getDueDate().equals(getFromDate())) {
|
if (!interestPeriod.getDueDate().equals(getFromDate())) {
|
||||||
if (interestPeriod.getDisbursementAmount() != null) {
|
if (interestPeriod.getDisbursementAmount() != null) {
|
||||||
res = res.add(interestPeriod.getDisbursementAmount().getAmount());
|
res = res.plus(interestPeriod.getDisbursementAmount(), getMc());
|
||||||
}
|
}
|
||||||
if (interestPeriod.getCapitalizedIncomePrincipal() != null) {
|
if (interestPeriod.getCapitalizedIncomePrincipal() != null) {
|
||||||
res = res.add(interestPeriod.getCapitalizedIncomePrincipal().getAmount());
|
res = res.plus(interestPeriod.getCapitalizedIncomePrincipal(), getMc());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MonetaryCurrency getCurrency() {
|
||||||
|
if (currency == null) {
|
||||||
|
currency = MonetaryCurrency.fromCurrencyData(loanProductRelatedDetail.getCurrencyData());
|
||||||
|
}
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getEmi() {
|
||||||
|
return MathUtil.nullToZero(emi, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getOriginalEmi() {
|
||||||
|
return MathUtil.nullToZero(originalEmi, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getPaidPrincipal() {
|
||||||
|
return MathUtil.nullToZero(paidPrincipal, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getPaidInterest() {
|
||||||
|
return MathUtil.nullToZero(paidInterest, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getFutureUnrecognizedInterest() {
|
||||||
|
return MathUtil.nullToZero(futureUnrecognizedInterest, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getTotalDisbursedAmount() {
|
||||||
|
return MathUtil.nullToZero(totalDisbursedAmount, getCurrency(), getMc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money getTotalCapitalizedIncomeAmount() {
|
||||||
|
return MathUtil.nullToZero(totalCapitalizedIncomeAmount, getCurrency(), getMc());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* 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.loanproduct.calc.data;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
class InterestPeriodTest {
|
||||||
|
|
||||||
|
private static final MathContext MC = new MathContext(12, RoundingMode.HALF_EVEN);
|
||||||
|
private static final CurrencyData USD = new CurrencyData("USD", "US Dollar", 2, 1, "$", "USD");
|
||||||
|
private static final Money ZERO = Money.of(USD, BigDecimal.ZERO, MC);
|
||||||
|
|
||||||
|
private static MockedStatic<MoneyHelper> moneyHelper;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void init() {
|
||||||
|
moneyHelper = Mockito.mockStatic(MoneyHelper.class);
|
||||||
|
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||||
|
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(MC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGettersNeverReturnNull() {
|
||||||
|
// Create an empty InterestPeriod with all null Money fields
|
||||||
|
RepaymentPeriod repaymentPeriod = createMinimalRepaymentPeriod();
|
||||||
|
InterestPeriod period = InterestPeriod.empty(repaymentPeriod, MC);
|
||||||
|
|
||||||
|
// Test all Money getters
|
||||||
|
assertNotNull(period.getCreditedPrincipal());
|
||||||
|
assertNotNull(period.getCreditedInterest());
|
||||||
|
assertNotNull(period.getDisbursementAmount());
|
||||||
|
assertNotNull(period.getBalanceCorrectionAmount());
|
||||||
|
assertNotNull(period.getOutstandingLoanBalance());
|
||||||
|
assertNotNull(period.getCapitalizedIncomePrincipal());
|
||||||
|
|
||||||
|
// Test BigDecimal getters
|
||||||
|
assertNotNull(period.getRateFactor());
|
||||||
|
assertNotNull(period.getRateFactorTillPeriodDueDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMethodsDoNotThrowNPE() {
|
||||||
|
RepaymentPeriod repaymentPeriod = createMinimalRepaymentPeriod();
|
||||||
|
InterestPeriod period = InterestPeriod.empty(repaymentPeriod, MC);
|
||||||
|
when(repaymentPeriod.getInterestPeriods()).thenReturn(List.of(period));
|
||||||
|
when(repaymentPeriod.getFirstInterestPeriod()).thenReturn(period);
|
||||||
|
LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = mock(
|
||||||
|
LoanProductMinimumRepaymentScheduleRelatedDetail.class);
|
||||||
|
when(loanProductRelatedDetail.getCurrencyData()).thenReturn(USD);
|
||||||
|
when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE);
|
||||||
|
when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY);
|
||||||
|
when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true);
|
||||||
|
when(repaymentPeriod.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||||
|
// Set some test data
|
||||||
|
period.setFromDate(LocalDate.now(ZoneId.of("UTC")));
|
||||||
|
period.setDueDate(LocalDate.now(ZoneId.of("UTC")).plusDays(30));
|
||||||
|
period.setRateFactor(BigDecimal.valueOf(0.01));
|
||||||
|
period.setRateFactorTillPeriodDueDate(BigDecimal.valueOf(0.01));
|
||||||
|
|
||||||
|
// Test methods that perform calculations
|
||||||
|
assertDoesNotThrow(period::updateOutstandingLoanBalance);
|
||||||
|
assertDoesNotThrow(period::getCreditedAmounts);
|
||||||
|
assertDoesNotThrow(() -> period.getCalculatedDueInterest());
|
||||||
|
assertDoesNotThrow(() -> period.getCalculatedDueInterest(InterestMethod.DECLINING_BALANCE, 30));
|
||||||
|
assertDoesNotThrow(period::getLength);
|
||||||
|
assertDoesNotThrow(period::getLengthTillPeriodDueDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWithNullFields() {
|
||||||
|
RepaymentPeriod repaymentPeriod = createMinimalRepaymentPeriod();
|
||||||
|
InterestPeriod period = new InterestPeriod(repaymentPeriod, null, // fromDate
|
||||||
|
null, // dueDate
|
||||||
|
null, // rateFactor
|
||||||
|
null, // rateFactorTillPeriodDueDate
|
||||||
|
null, // creditedPrincipal
|
||||||
|
null, // creditedInterest
|
||||||
|
null, // disbursementAmount
|
||||||
|
null, // balanceCorrectionAmount
|
||||||
|
null, // outstandingLoanBalance
|
||||||
|
null, // capitalizedIncomePrincipal
|
||||||
|
MC, false // isPaused
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that getters don't throw and return non-null
|
||||||
|
assertDoesNotThrow(period::getCreditedPrincipal);
|
||||||
|
assertDoesNotThrow(period::getCreditedInterest);
|
||||||
|
assertDoesNotThrow(period::getDisbursementAmount);
|
||||||
|
assertDoesNotThrow(period::getBalanceCorrectionAmount);
|
||||||
|
assertDoesNotThrow(period::getOutstandingLoanBalance);
|
||||||
|
assertDoesNotThrow(period::getCapitalizedIncomePrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RepaymentPeriod createMinimalRepaymentPeriod() {
|
||||||
|
RepaymentPeriod repaymentPeriod = mock(RepaymentPeriod.class);
|
||||||
|
when(repaymentPeriod.getZero()).thenReturn(ZERO);
|
||||||
|
when(repaymentPeriod.getDueDate()).thenReturn(LocalDate.now(ZoneId.of("UTC")).plusMonths(1));
|
||||||
|
when(repaymentPeriod.getCurrency()).thenReturn(ZERO.getCurrency());
|
||||||
|
when(repaymentPeriod.getMc()).thenReturn(MC);
|
||||||
|
return repaymentPeriod;
|
||||||
|
}
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* 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.loanproduct.calc.data;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
class RepaymentPeriodTest {
|
||||||
|
|
||||||
|
private static final MathContext MC = new MathContext(12, RoundingMode.HALF_EVEN);
|
||||||
|
private static final CurrencyData USD = new CurrencyData("USD", "US Dollar", 2, 1, "$", "USD");
|
||||||
|
private static final Money ZERO = Money.of(USD, BigDecimal.ZERO, MC);
|
||||||
|
|
||||||
|
private static MockedStatic<MoneyHelper> moneyHelper;
|
||||||
|
private static LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void init() {
|
||||||
|
moneyHelper = Mockito.mockStatic(MoneyHelper.class);
|
||||||
|
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||||
|
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(MC);
|
||||||
|
|
||||||
|
loanProductRelatedDetail = mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class);
|
||||||
|
when(loanProductRelatedDetail.getCurrencyData()).thenReturn(USD);
|
||||||
|
when(loanProductRelatedDetail.getInterestMethod()).thenReturn(InterestMethod.DECLINING_BALANCE);
|
||||||
|
when(loanProductRelatedDetail.getInterestCalculationPeriodMethod()).thenReturn(InterestCalculationPeriodMethod.DAILY);
|
||||||
|
when(loanProductRelatedDetail.isAllowPartialPeriodInterestCalculation()).thenReturn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGettersNeverReturnNull() {
|
||||||
|
// Create a minimal RepaymentPeriod
|
||||||
|
LocalDate now = LocalDate.now(ZoneId.of("UTC"));
|
||||||
|
Money emi = ZERO;
|
||||||
|
RepaymentPeriod period = RepaymentPeriod.create(null, now, now.plusMonths(1), emi, MC, loanProductRelatedDetail);
|
||||||
|
|
||||||
|
// Test all Money getters
|
||||||
|
assertNotNull(period.getEmi());
|
||||||
|
assertNotNull(period.getOriginalEmi());
|
||||||
|
assertNotNull(period.getPaidPrincipal());
|
||||||
|
assertNotNull(period.getPaidInterest());
|
||||||
|
assertNotNull(period.getFutureUnrecognizedInterest());
|
||||||
|
|
||||||
|
// Test BigDecimal getters
|
||||||
|
assertNotNull(period.getTotalDisbursedAmount());
|
||||||
|
assertNotNull(period.getTotalCapitalizedIncomeAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMethodsDoNotThrowNPE() {
|
||||||
|
LocalDate now = LocalDate.now(ZoneId.of("UTC"));
|
||||||
|
Money emi = ZERO;
|
||||||
|
RepaymentPeriod period = RepaymentPeriod.create(null, now, now.plusMonths(1), emi, MC, loanProductRelatedDetail);
|
||||||
|
|
||||||
|
// Add an interest period
|
||||||
|
InterestPeriod interestPeriod = InterestPeriod.withEmptyAmounts(period, now, now.plusMonths(1));
|
||||||
|
period.getInterestPeriods().add(interestPeriod);
|
||||||
|
|
||||||
|
// Test methods that perform calculations
|
||||||
|
assertDoesNotThrow(period::getCalculatedDueInterest);
|
||||||
|
assertDoesNotThrow(period::getDueInterest);
|
||||||
|
assertDoesNotThrow(period::getCalculatedDuePrincipal);
|
||||||
|
assertDoesNotThrow(period::getDuePrincipal);
|
||||||
|
assertDoesNotThrow(period::getTotalCreditedAmount);
|
||||||
|
assertDoesNotThrow(period::getTotalPaidAmount);
|
||||||
|
assertDoesNotThrow(period::getUnrecognizedInterest);
|
||||||
|
assertDoesNotThrow(period::getCreditedAmounts);
|
||||||
|
assertDoesNotThrow(period::getOutstandingLoanBalance);
|
||||||
|
assertDoesNotThrow(period::getInitialBalanceForEmiRecalculation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyRepaymentPeriod() {
|
||||||
|
// Create an empty repayment period with all null fields
|
||||||
|
RepaymentPeriod period = new RepaymentPeriod(null, // previous
|
||||||
|
null, // fromDate
|
||||||
|
null, // dueDate
|
||||||
|
null, // interestPeriods
|
||||||
|
null, // emi
|
||||||
|
null, // originalEmi
|
||||||
|
null, // paidPrincipal
|
||||||
|
null, // paidInterest
|
||||||
|
null, // futureUnrecognizedInterest
|
||||||
|
MC, // mc
|
||||||
|
loanProductRelatedDetail);
|
||||||
|
|
||||||
|
// Test that getters don't throw and return non-null
|
||||||
|
assertDoesNotThrow(period::getEmi);
|
||||||
|
assertDoesNotThrow(period::getOriginalEmi);
|
||||||
|
assertDoesNotThrow(period::getPaidPrincipal);
|
||||||
|
assertDoesNotThrow(period::getPaidInterest);
|
||||||
|
assertDoesNotThrow(period::getFutureUnrecognizedInterest);
|
||||||
|
assertDoesNotThrow(period::getTotalDisbursedAmount);
|
||||||
|
assertDoesNotThrow(period::getTotalCapitalizedIncomeAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user