diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java index 2e0779ef9..4e2178d9a 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java @@ -463,13 +463,7 @@ public final class MathUtil { * @return */ public static BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage, final int precision) { - BigDecimal percentageOf = BigDecimal.ZERO; - if (isGreaterThanZero(value)) { - final MathContext mc = new MathContext(precision, MoneyHelper.getRoundingMode()); - final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100L), mc); - percentageOf = value.multiply(multiplicand, mc); - } - return percentageOf; + return percentageOf(value, percentage, new MathContext(precision, MoneyHelper.getRoundingMode())); } /** diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java index 8ec1a84fc..c157a5486 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java @@ -18,30 +18,19 @@ */ package org.apache.fineract.organisation.monetary.domain; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; import java.math.BigDecimal; import java.math.MathContext; -import java.math.RoundingMode; import java.util.Iterator; import lombok.Getter; import org.apache.fineract.organisation.monetary.data.CurrencyData; -@Embeddable @Getter public class Money implements Comparable { - @Column(name = "currency_code", length = 3) - private String currencyCode; - - @Column(name = "currency_digits") - private int currencyDigitsAfterDecimal; - - @Column(name = "currency_multiplesof") - private Integer inMultiplesOf; - - @Column(name = "amount", scale = 6, precision = 19) - private BigDecimal amount; + private final String currencyCode; + private final int currencyDigitsAfterDecimal; + private final Integer inMultiplesOf; + private final BigDecimal amount; private final transient MathContext mc; @@ -53,6 +42,24 @@ public class Money implements Comparable { this.mc = getMc(); } + private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf, + final MathContext mc) { + this.currencyCode = currencyCode; + this.currencyDigitsAfterDecimal = digitsAfterDecimal; + this.inMultiplesOf = inMultiplesOf; + this.mc = mc; + + final BigDecimal amountZeroed = defaultToZeroIfNull(amount); + BigDecimal amountScaled = amountZeroed.stripTrailingZeros(); + + // round monetary amounts into multiples of say 20/50. + if (inMultiplesOf != null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf > 0 && amountScaled.doubleValue() > 0) { + final double existingVal = amountScaled.doubleValue(); + amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf)); + } + this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, getMc().getRoundingMode()); + } + public static Money total(final Money... monies) { if (monies.length == 0) { throw new IllegalArgumentException("Money array must not be empty"); @@ -82,13 +89,11 @@ public class Money implements Comparable { } public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) { - return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount), - currency.getCurrencyInMultiplesOf(), null); + return of(currency, newAmount, MoneyHelper.getMathContext()); } public static Money of(final CurrencyData currency, final BigDecimal newAmount) { - return new Money(currency.getCode(), currency.getDecimalPlaces(), defaultToZeroIfNull(newAmount), currency.getInMultiplesOf(), - null); + return of(currency, newAmount, MoneyHelper.getMathContext()); } public static Money of(final CurrencyData currency, final BigDecimal newAmount, final MathContext mc) { @@ -96,7 +101,7 @@ public class Money implements Comparable { } public static Money zero(final MonetaryCurrency currency) { - return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf(), null); + return zero(currency, MoneyHelper.getMathContext()); } public static Money zero(final MonetaryCurrency currency, MathContext mc) { @@ -108,30 +113,11 @@ public class Money implements Comparable { } public static Money zero(final CurrencyData currency) { - return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf(), null); - } - - private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf, - final MathContext mc) { - this.currencyCode = currencyCode; - this.currencyDigitsAfterDecimal = digitsAfterDecimal; - this.inMultiplesOf = inMultiplesOf; - this.mc = mc; - - final BigDecimal amountZeroed = defaultToZeroIfNull(amount); - final BigDecimal amountStripped = amountZeroed.stripTrailingZeros(); - BigDecimal amountScaled = amountStripped; - - // round monetary amounts into multiplesof say 20/50. - if (inMultiplesOf != null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf > 0 && amountScaled.doubleValue() > 0) { - final double existingVal = amountScaled.doubleValue(); - amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf)); - } - this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, getMc().getRoundingMode()); + return zero(currency, MoneyHelper.getMathContext()); } public static double roundToMultiplesOf(final double existingVal, final Integer inMultiplesOf) { - double amountScaled = existingVal; + double amountScaled; final double ceilingOfValue = ceiling(existingVal, inMultiplesOf); final double floorOfValue = floor(existingVal, inMultiplesOf); @@ -148,7 +134,7 @@ public class Money implements Comparable { public static BigDecimal roundToMultiplesOf(final BigDecimal existingVal, final Integer inMultiplesOf) { BigDecimal amountScaled = existingVal; - BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue()); + BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf); if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) { amountScaled = existingVal.divide(inMultiplesOfValue, 0, MoneyHelper.getRoundingMode()).multiply(inMultiplesOfValue); } @@ -161,7 +147,7 @@ public class Money implements Comparable { public static Money roundToMultiplesOf(final Money existingVal, final Integer inMultiplesOf, final MathContext mc) { BigDecimal amountScaled = existingVal.getAmount(); - BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue()); + BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf); if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) { amountScaled = amountScaled.divide(inMultiplesOfValue, 0, mc.getRoundingMode()).multiply(inMultiplesOfValue); } @@ -186,7 +172,7 @@ public class Money implements Comparable { if ((n < 0 && s > 0) || (n > 0 && s < 0) || (s == 0 && n != 0)) { f = Double.NaN; } else { - f = (n == 0 || s == 0) ? 0 : Math.floor(n / s) * s; + f = n == 0 ? 0 : Math.floor(n / s) * s; } return f; @@ -200,6 +186,14 @@ public class Money implements Comparable { return result; } + private static BigDecimal defaultToNullIfZero(final BigDecimal value) { + BigDecimal result = value; + if (value != null && BigDecimal.ZERO.compareTo(value) == 0) { + result = null; + } + return result; + } + public Money copy() { return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf, this.mc); } @@ -214,8 +208,7 @@ public class Money implements Comparable { } public Money plus(final Money moneyToAdd) { - final Money toAdd = checkCurrencyEqual(moneyToAdd); - return this.plus(toAdd.getAmount()); + return plus(moneyToAdd, getMc()); } public Money plus(final Money moneyToAdd, final MathContext mc) { @@ -224,11 +217,7 @@ public class Money implements Comparable { } public Money plus(final BigDecimal amountToAdd) { - if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { - return this; - } - final BigDecimal newAmount = this.amount.add(amountToAdd); - return Money.of(monetaryCurrency(), newAmount); + return plus(amountToAdd, getMc()); } public Money plus(final BigDecimal amountToAdd, MathContext mc) { @@ -248,8 +237,7 @@ public class Money implements Comparable { } public Money minus(final Money moneyToSubtract) { - final Money toSubtract = checkCurrencyEqual(moneyToSubtract); - return this.minus(toSubtract.getAmount()); + return minus(moneyToSubtract, getMc()); } public Money minus(final Money moneyToSubtract, final MathContext mc) { @@ -258,8 +246,7 @@ public class Money implements Comparable { } public Money add(final Money moneyToAdd) { - final Money toAdd = checkCurrencyEqual(moneyToAdd); - return this.add(toAdd.getAmount()); + return add(moneyToAdd, getMc()); } public Money add(final Money moneyToAdd, final MathContext mc) { @@ -268,11 +255,7 @@ public class Money implements Comparable { } public Money add(final BigDecimal amountToAdd) { - if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { - return this; - } - final BigDecimal newAmount = this.amount.add(amountToAdd); - return Money.of(monetaryCurrency(), newAmount); + return add(amountToAdd, getMc()); } public Money add(final BigDecimal amountToAdd, final MathContext mc) { @@ -284,11 +267,7 @@ public class Money implements Comparable { } public Money minus(final BigDecimal amountToSubtract) { - if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) { - return this; - } - final BigDecimal newAmount = this.amount.subtract(amountToSubtract); - return Money.of(monetaryCurrency(), newAmount); + return minus(amountToSubtract, getMc()); } public Money minus(final BigDecimal amountToSubtract, final MathContext mc) { @@ -310,59 +289,39 @@ public class Money implements Comparable { return this.currencyCode.equals(money.getCurrencyCode()); } - public Money dividedBy(final BigDecimal valueToDivideBy, final RoundingMode roundingMode) { + public Money dividedBy(final BigDecimal valueToDivideBy, final MathContext mc) { if (valueToDivideBy.compareTo(BigDecimal.ONE) == 0) { return this; } - final BigDecimal newAmount = this.amount.divide(valueToDivideBy, roundingMode); - return Money.of(monetaryCurrency(), newAmount); - } - - public Money dividedBy(final BigDecimal valueToDivideBy, final RoundingMode roundingMode, final MathContext mc) { - if (valueToDivideBy.compareTo(BigDecimal.ONE) == 0) { - return this; - } - final BigDecimal newAmount = this.amount.divide(valueToDivideBy, roundingMode); + final BigDecimal newAmount = this.amount.divide(valueToDivideBy, mc); return Money.of(monetaryCurrency(), newAmount, mc); } - public Money dividedBy(final double valueToDivideBy, final RoundingMode roundingMode) { + public Money dividedBy(final double valueToDivideBy, final MathContext mc) { if (valueToDivideBy == 1) { return this; } - final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), roundingMode); - return Money.of(monetaryCurrency(), newAmount); + final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), mc); + return Money.of(monetaryCurrency(), newAmount, mc); } - public Money dividedBy(final long valueToDivideBy, final RoundingMode roundingMode) { + public Money dividedBy(final long valueToDivideBy, final MathContext mc) { if (valueToDivideBy == 1) { return this; } - final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), roundingMode); - return Money.of(monetaryCurrency(), newAmount); - } - - public Money dividedBy(final long valueToDivideBy, final RoundingMode roundingMode, final MathContext mc) { - if (valueToDivideBy == 1) { - return this; - } - final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), roundingMode); + final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), mc); return Money.of(monetaryCurrency(), newAmount, mc); } public Money multipliedBy(final BigDecimal valueToMultiplyBy) { - if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { - return this; - } - final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy); - return Money.of(monetaryCurrency(), newAmount); + return multipliedBy(valueToMultiplyBy, getMc()); } public Money multipliedBy(final BigDecimal valueToMultiplyBy, final MathContext mc) { if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { return this; } - final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy); + final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy, mc); return Money.of(monetaryCurrency(), newAmount, mc); } @@ -375,37 +334,33 @@ public class Money implements Comparable { } public Money multipliedBy(final long valueToMultiplyBy) { - if (valueToMultiplyBy == 1) { - return this; - } - final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy)); - return Money.of(monetaryCurrency(), newAmount); + return multipliedBy(valueToMultiplyBy, getMc()); } public Money multipliedBy(final long valueToMultiplyBy, final MathContext mc) { if (valueToMultiplyBy == 1) { return this; } - final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy)); + final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy), mc); return Money.of(monetaryCurrency(), newAmount, mc); } - public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final RoundingMode roundingMode) { + public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final MathContext mc) { if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { return this; } - BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy); - newAmount = newAmount.setScale(this.currencyDigitsAfterDecimal, roundingMode); - return Money.of(monetaryCurrency(), newAmount); + BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy, mc); + newAmount = newAmount.setScale(this.currencyDigitsAfterDecimal, mc.getRoundingMode()); + return Money.of(monetaryCurrency(), newAmount, mc); } - public Money multiplyRetainScale(final double valueToMultiplyBy, final RoundingMode roundingMode) { - return this.multiplyRetainScale(BigDecimal.valueOf(valueToMultiplyBy), roundingMode); + public Money multiplyRetainScale(final double valueToMultiplyBy, final MathContext mc) { + return this.multiplyRetainScale(BigDecimal.valueOf(valueToMultiplyBy), mc); } - public Money percentageOf(BigDecimal percentage, final RoundingMode roundingMode) { - final BigDecimal newAmount = this.amount.multiply(percentage).divide(BigDecimal.valueOf(100), roundingMode); - return Money.of(monetaryCurrency(), newAmount); + public Money percentageOf(BigDecimal percentage, final MathContext mc) { + final BigDecimal newAmount = this.amount.multiply(percentage).divide(BigDecimal.valueOf(100), mc); + return Money.of(monetaryCurrency(), newAmount, mc); } @Override @@ -417,7 +372,7 @@ public class Money implements Comparable { } public boolean isZero() { - return isEqualTo(Money.zero(getCurrency())); + return isZero(getMc()); } public boolean isZero(final MathContext mc) { @@ -441,7 +396,7 @@ public class Money implements Comparable { } public boolean isGreaterThanZero() { - return isGreaterThan(Money.zero(getCurrency())); + return isGreaterThanZero(getMc()); } public boolean isGreaterThanZero(MathContext mc) { @@ -453,7 +408,7 @@ public class Money implements Comparable { } public boolean isLessThanZero() { - return isLessThan(Money.zero(getCurrency())); + return isLessThanZero(getMc()); } public boolean isLessThanZero(final MathContext mc) { @@ -468,24 +423,13 @@ public class Money implements Comparable { return defaultToNullIfZero(this.amount); } - private static BigDecimal defaultToNullIfZero(final BigDecimal value) { - BigDecimal result = value; - if (value != null && BigDecimal.ZERO.compareTo(value) == 0) { - result = null; - } - return result; - } - @Override public String toString() { return new StringBuilder().append(this.currencyCode).append(' ').append(this.amount.toPlainString()).toString(); } public Money negated() { - if (isZero()) { - return this; - } - return Money.of(monetaryCurrency(), this.amount.negate()); + return negated(getMc()); } public Money negated(final MathContext mc) { @@ -496,7 +440,7 @@ public class Money implements Comparable { } public Money abs() { - return isLessThanZero() ? negated() : this; + return abs(getMc()); } public Money abs(MathContext mc) { @@ -512,7 +456,7 @@ public class Money implements Comparable { } public Money zero() { - return Money.zero(getCurrency()); + return zero(getMc()); } public Money zero(MathContext mc) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleModelDownPaymentPeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleModelDownPaymentPeriod.java index d969c2e2f..ad31e819b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleModelDownPaymentPeriod.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleModelDownPaymentPeriod.java @@ -21,10 +21,12 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.data; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Set; +import lombok.Getter; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; +@Getter public final class LoanScheduleModelDownPaymentPeriod implements LoanScheduleModelPeriod { private final int periodNumber; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index e4fb8fd2b..a441f595b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -248,9 +248,20 @@ public final class LoanApplicationTerms { this.fixedLength = builder.fixedLength; this.inArrearsTolerance = builder.inArrearsTolerance; this.disbursementDatas = builder.disbursementDatas; - this.downPaymentAmount = builder.downPaymentAmount; this.submittedOnDate = builder.submittedOnDate; this.seedDate = builder.seedDate; + this.isDownPaymentEnabled = builder.isDownPaymentEnabled; + this.disbursedAmountPercentageForDownPayment = builder.downPaymentPercentage; + if (isDownPaymentEnabled) { + this.downPaymentAmount = Money.of(getCurrency(), + MathUtil.percentageOf(getPrincipal().getAmount(), getDisbursedAmountPercentageForDownPayment(), builder.mc), + builder.mc); + if (getInstallmentAmountInMultiplesOf() != null) { + this.downPaymentAmount = Money.roundToMultiplesOf(this.downPaymentAmount, getInstallmentAmountInMultiplesOf(), builder.mc); + } + } else { + this.downPaymentAmount = Money.zero(getCurrency(), builder.mc); + } } public static class Builder { @@ -273,9 +284,11 @@ public final class LoanApplicationTerms { private Integer fixedLength; private Money inArrearsTolerance; private List disbursementDatas; - private Money downPaymentAmount; + private BigDecimal downPaymentPercentage; + private boolean isDownPaymentEnabled; private LocalDate submittedOnDate; private LocalDate seedDate; + private MathContext mc; public Builder currency(ApplicationCurrency currency) { this.currency = currency; @@ -362,8 +375,8 @@ public final class LoanApplicationTerms { return this; } - public Builder downPaymentAmount(Money downPaymentAmount) { - this.downPaymentAmount = downPaymentAmount; + public Builder downPaymentPercentage(BigDecimal downPaymentPercentage) { + this.downPaymentPercentage = downPaymentPercentage; return this; } @@ -377,6 +390,16 @@ public final class LoanApplicationTerms { return this; } + public Builder isDownPaymentEnabled(boolean isDownPaymentEnabled) { + this.isDownPaymentEnabled = isDownPaymentEnabled; + return this; + } + + public Builder mc(MathContext mc) { + this.mc = mc; + return this; + } + public LoanApplicationTerms build() { return new LoanApplicationTerms(this); } @@ -384,15 +407,7 @@ public final class LoanApplicationTerms { 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(), mc), mc); - if (modelData.installmentAmountInMultiplesOf() != null) { - downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, modelData.installmentAmountInMultiplesOf(), mc); - } - } + BigDecimal downPaymentPercentage = modelData.downPaymentPercentage(); LocalDate seedDate; @@ -412,7 +427,8 @@ public final class LoanApplicationTerms { .expectedDisbursementDate(modelData.disbursementDate()).repaymentsStartingFromDate(modelData.scheduleGenerationStartDate()) .daysInMonthType(modelData.daysInMonth()).daysInYearType(modelData.daysInYear()).fixedLength(modelData.fixedLength()) .inArrearsTolerance(Money.zero(modelData.currency().toData(), mc)).disbursementDatas(new ArrayList<>()) - .downPaymentAmount(downPaymentAmount).submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate).build(); + .isDownPaymentEnabled(modelData.downPaymentEnabled()).downPaymentPercentage(downPaymentPercentage) + .submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate).mc(mc).build(); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -679,7 +695,7 @@ public final class LoanApplicationTerms { } else if (this.actualFixedEmiAmount != null) { final Money difference = this.principal.minus(totalCumulativePrincipalToDate); final Money principalThreshold = principalForPeriod.multipliedBy(this.principalThresholdForLastInstalment).dividedBy(100, - MoneyHelper.getRoundingMode()); + MoneyHelper.getMathContext()); if (difference.isLessThan(principalThreshold)) { adjusted = principalForPeriod.plus(difference.abs()); } @@ -734,8 +750,7 @@ public final class LoanApplicationTerms { final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); - final Money totalGraceOnInterestCharged = totalInterestPerInstallment.multiplyRetainScale(getInterestChargingGrace(), - mc.getRoundingMode()); + final Money totalGraceOnInterestCharged = totalInterestPerInstallment.multiplyRetainScale(getInterestChargingGrace(), mc); totalInterestCharged = totalInterestChargedForLoanTerm.minus(totalGraceOnInterestCharged); break; @@ -901,7 +916,7 @@ public final class LoanApplicationTerms { case FLAT: final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); totalInterestDue = this.disbursedPrincipal.minus(totalPrincipalAccountedForInterestCalcualtion) - .multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); + .multiplyRetainScale(interestRateForLoanTerm, mc); break; case DECLINING_BALANCE: @@ -1041,12 +1056,12 @@ public final class LoanApplicationTerms { } private Money flatInterestPerInstallment(final MathContext mc, final Money totalInterestForLoanTerm) { - Money interestPerInstallment = totalInterestForLoanTerm.dividedBy( - Long.valueOf(this.actualNumberOfRepayments) - defaultToZeroIfNull(this.excludePeriodsForCalculation), mc.getRoundingMode()); + Money interestPerInstallment = totalInterestForLoanTerm + .dividedBy(Long.valueOf(this.actualNumberOfRepayments) - defaultToZeroIfNull(this.excludePeriodsForCalculation), mc); if (this.excludePeriodsForCalculation < this.periodsCompleted) { Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted); - interestPerInstallment = interestLeft.dividedBy( - Long.valueOf(this.actualNumberOfRepayments) - defaultToZeroIfNull(this.periodsCompleted), mc.getRoundingMode()); + interestPerInstallment = interestLeft + .dividedBy(Long.valueOf(this.actualNumberOfRepayments) - defaultToZeroIfNull(this.periodsCompleted), mc); } return interestPerInstallment; @@ -1059,19 +1074,17 @@ public final class LoanApplicationTerms { if (getFixedEmiAmount() == null) { if (this.fixedPrincipalPercentagePerInstallment != null) { principalPerPeriod = this.principal.minus(totalPrincipalAccounted) - .percentageOf(this.fixedPrincipalPercentagePerInstallment, mc.getRoundingMode()) - .plus(this.adjustPrincipalForFlatLoans); + .percentageOf(this.fixedPrincipalPercentagePerInstallment, mc).plus(this.adjustPrincipalForFlatLoans); } else { - principalPerPeriod = this.principal.minus(totalPrincipalAccounted) - .dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(this.adjustPrincipalForFlatLoans); + principalPerPeriod = this.principal.minus(totalPrincipalAccounted).dividedBy(totalRepaymentsWithCapitalPayment, mc) + .plus(this.adjustPrincipalForFlatLoans); } if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { principalPerPeriod = principalPerPeriod.zero(); } if (!isPrincipalGraceApplicableForThisPeriod(periodNumber) && currentPeriodFixedPrincipalAmount != null) { - this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans - .plus(principalPerPeriod.minus(currentPeriodFixedPrincipalAmount) - .dividedBy(this.actualNumberOfRepayments - periodNumber, mc.getRoundingMode())); + this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans.plus(principalPerPeriod + .minus(currentPeriodFixedPrincipalAmount).dividedBy(this.actualNumberOfRepayments - periodNumber, mc)); principalPerPeriod = this.principal.zero().plus(currentPeriodFixedPrincipalAmount); } @@ -1146,13 +1159,12 @@ public final class LoanApplicationTerms { Integer interestPaymentDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments, this.excludePeriodsForCalculation); - interestForInstallment = realTotalInterestForLoan.dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), - mc.getRoundingMode()); + interestForInstallment = realTotalInterestForLoan.dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc); if (this.excludePeriodsForCalculation < this.periodsCompleted) { Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted); Integer interestDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments, this.periodsCompleted); - interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc.getRoundingMode()); + interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc); } if (!this.periodNumbersApplicableForInterestGrace.isEmpty()) { int periodsElapsed = calculateLastInterestGracePeriod(periodNumber); @@ -1160,7 +1172,7 @@ public final class LoanApplicationTerms { Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted); Integer interestDuePeriods = calculateNumberOfRemainingInterestPaymentPeriods(this.actualNumberOfRepayments, periodsElapsed); - interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc.getRoundingMode()); + interestForInstallment = interestLeft.dividedBy(Long.valueOf(interestDuePeriods), mc); } } @@ -1416,7 +1428,7 @@ public final class LoanApplicationTerms { final BigDecimal periodicInterestRate = periodicInterestRate(calculator, mc, this.daysInMonthType, this.daysInYearType, periodStartDate, periodEndDate);// 0.021232877 ob:14911.64 - interestDue = outstandingBalance.multiplyRetainScale(periodicInterestRate, mc.getRoundingMode()); + interestDue = outstandingBalance.multiplyRetainScale(periodicInterestRate, mc); return interestDue; } @@ -1463,11 +1475,11 @@ public final class LoanApplicationTerms { if (this.fixedPrincipalAmount == null) { final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, periodNumber); - principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); + principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc); this.fixedPrincipalAmount = principal.getAmount(); } if (this.fixedPrincipalPercentagePerInstallment != null) { - principal = this.principal.percentageOf(this.fixedPrincipalPercentagePerInstallment, mc.getRoundingMode()); + principal = this.principal.percentageOf(this.fixedPrincipalPercentagePerInstallment, mc); } else { principal = Money.of(getCurrency(), getFixedPrincipalAmount()); } @@ -1480,7 +1492,7 @@ public final class LoanApplicationTerms { public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) { final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, periodNumber - 1); - Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); + Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods, mc); this.fixedPrincipalAmount = principal.getAmount(); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java index 9b36bce95..ee484bfcc 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import lombok.Getter; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @@ -30,16 +31,20 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP /** * Domain representation of a Loan Schedule (not used for persistence) */ +@Getter public final class LoanScheduleModel { + @Getter private final List periods; private final ApplicationCurrency applicationCurrency; private final int loanTermInDays; private final Money totalPrincipalDisbursed; private final BigDecimal totalPrincipalExpected; private final BigDecimal totalPrincipalPaid; + @Getter private final BigDecimal totalInterestCharged; private final BigDecimal totalFeeChargesCharged; + @Getter private final BigDecimal totalPenaltyChargesCharged; private final BigDecimal totalRepaymentExpected; private final BigDecimal totalOutstanding; @@ -114,16 +119,4 @@ public final class LoanScheduleModel { totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits); } - public List getPeriods() { - return this.periods; - } - - public BigDecimal getTotalPenaltyChargesCharged() { - return this.totalPenaltyChargesCharged; - } - - public BigDecimal getTotalInterestCharged() { - return this.totalInterestCharged; - } - } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java index e5e31e91d..1cffa8bc5 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Set; +import lombok.Getter; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; @@ -28,6 +29,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP /** * Domain representation of a Loan Schedule Disbursement Period (not used for persistence) */ +@Getter public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleModelPeriod { @SuppressWarnings("unused") diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 6375353af..2852da4ba 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -828,7 +828,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep Money amortizableAmount = disbursementTransaction.getAmount(transactionCtx.getCurrency()).minus(downPaymentAmount); if (amortizableAmount.isGreaterThanZero()) { - Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, mc.getRoundingMode()); + Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, MoneyHelper.getMathContext()); MoneyHolder moneyHolder = new MoneyHolder(amortizableAmount); candidateRepaymentInstallments.forEach(i -> { @@ -1194,7 +1194,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep case IN_ADVANCE -> { int numberOfInstallments = inAdvanceInstallments.size(); if (numberOfInstallments > 0) { - Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode()); + Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getMathContext()); Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments)); for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) { Set inAdvanceInstallmentCharges = getLoanChargesOfInstallment(charges, inAdvanceInstallment, @@ -1262,7 +1262,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep int numberOfInstallments = currentInstallments.size(); refundedPortion = zero; if (numberOfInstallments > 0) { - Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode()); + Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getMathContext()); Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments)); for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) { currentInstallment = internalCurrentInstallment; @@ -1467,7 +1467,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep if (numberOfInstallments > 0) { // This will be the same amount as transactionAmountUnprocessed in case of the future // installment allocation is NEXT_INSTALLMENT or LAST_INSTALLMENT - Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode()); + Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getMathContext()); // Adjustment might be needed due to the divide operation and the rounding mode Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments)); for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) { @@ -1645,7 +1645,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep if (numberOfInstallments > 0) { // This will be the same amount as transactionAmountUnprocessed in case of the future // installment allocation is NEXT_INSTALLMENT or LAST_INSTALLMENT - Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode()); + Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getMathContext()); // Adjustment might be needed due to the divide operation and the rounding mode Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments)); for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) { @@ -1723,7 +1723,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep Money adjustCalculatedPrincipal = Money.zero(currency); if (outstandingPrincipalBalance.get().isGreaterThanZero()) { calculatedPrincipal = outstandingPrincipalBalance.get() - .dividedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(), MoneyHelper.getRoundingMode()); + .dividedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(), MoneyHelper.getMathContext()); Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf(); if (installmentAmountInMultiplesOf != null) { calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal, installmentAmountInMultiplesOf); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlan.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlan.java new file mode 100644 index 000000000..8b82b91e9 --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlan.java @@ -0,0 +1,90 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.data; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import lombok.Data; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelDisbursementPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; + +@Data +public class LoanSchedulePlan { + + private final List periods; + private final CurrencyData currency; + private final int loanTermInDays; + private final BigDecimal totalDisbursedAmount; + private final BigDecimal totalPrincipalAmount; + private final BigDecimal totalInterestAmount; + private final BigDecimal totalFeeAmount; + private final BigDecimal totalPenaltyAmount; + private final BigDecimal totalRepaymentAmount; + private final BigDecimal totalOutstandingAmount; + + public static LoanSchedulePlan from(LoanScheduleModel model) { + List periods = new ArrayList<>(); + + model.getPeriods().forEach(periodModel -> { + LoanSchedulePlanPeriod periodPlan = null; + if (periodModel instanceof LoanScheduleModelDisbursementPeriod disbursementPeriod) { + periodPlan = new LoanSchedulePlanDisbursementPeriod(disbursementPeriod.getDisbursementDate(), // + disbursementPeriod.getDisbursementDate(), // + disbursementPeriod.getPrincipalDisbursed().getAmount(), // + disbursementPeriod.getPrincipalDisbursed().getAmount());// + } else if (periodModel instanceof LoanScheduleModelDownPaymentPeriod downPaymentPeriod) { + periodPlan = new LoanSchedulePlanDownPaymentPeriod(downPaymentPeriod.getPeriodNumber(), // + downPaymentPeriod.getPeriodDate(), // + downPaymentPeriod.getPeriodDate(), // + downPaymentPeriod.getPrincipalDue().getAmount(), // + downPaymentPeriod.getPrincipalDue().getAmount(), // + downPaymentPeriod.getOutstandingLoanBalance().getAmount());// + } else if (periodModel instanceof LoanScheduleModelRepaymentPeriod repaymentPeriod) { + periodPlan = new LoanSchedulePlanRepaymentPeriod(repaymentPeriod.getPeriodNumber(), // + repaymentPeriod.getFromDate(), // + repaymentPeriod.getDueDate(), // + repaymentPeriod.getPrincipalDue().getAmount(), // + repaymentPeriod.getInterestDue().getAmount(), // + repaymentPeriod.getFeeChargesDue().getAmount(), // + repaymentPeriod.getPenaltyChargesDue().getAmount(), // + repaymentPeriod.getTotalDue().getAmount(), // + repaymentPeriod.getOutstandingLoanBalance().getAmount());// + } + if (periodPlan != null) { + periods.add(periodPlan); + } + }); + + return new LoanSchedulePlan(periods, // + model.getApplicationCurrency().toData(), // + model.getLoanTermInDays(), // + model.getTotalPrincipalDisbursed().getAmount(), // + model.getTotalPrincipalExpected(), // + model.getTotalInterestCharged(), // + model.getTotalFeeChargesCharged(), // + model.getTotalPenaltyChargesCharged(), // + model.getTotalRepaymentExpected(), // + model.getTotalOutstanding()// + + ); + } +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanDisbursementPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanDisbursementPeriod.java new file mode 100644 index 000000000..3de91b867 --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanDisbursementPeriod.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Data; + +@Data +public final class LoanSchedulePlanDisbursementPeriod implements LoanSchedulePlanPeriod { + + private final LocalDate periodFromDate; + private final LocalDate periodDueDate; + private final BigDecimal principalAmount; + private final BigDecimal outstandingLoanBalance; + + @Override + public Integer periodNumber() { + return null; + } + + @Override + public LocalDate periodFromDate() { + return periodFromDate; + } + + @Override + public LocalDate periodDueDate() { + return periodDueDate; + } +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanDownPaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanDownPaymentPeriod.java new file mode 100644 index 000000000..2d7929a43 --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanDownPaymentPeriod.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Data; + +@Data +public final class LoanSchedulePlanDownPaymentPeriod implements LoanSchedulePlanPeriod { + + private final int periodNumber; + private final LocalDate periodFromDate; + private final LocalDate periodDueDate; + private final BigDecimal principalAmount; + private final BigDecimal totalDueAmount; + private final BigDecimal outstandingLoanBalance; + + @Override + public Integer periodNumber() { + return periodNumber; + } + + @Override + public LocalDate periodFromDate() { + return periodFromDate; + } + + @Override + public LocalDate periodDueDate() { + return periodDueDate; + } +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanPeriod.java new file mode 100644 index 000000000..72a243687 --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanPeriod.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.data; + +import java.time.LocalDate; + +public interface LoanSchedulePlanPeriod { + + Integer periodNumber(); + + LocalDate periodFromDate(); + + LocalDate periodDueDate(); +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanRepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanRepaymentPeriod.java new file mode 100644 index 000000000..03a11d250 --- /dev/null +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlanRepaymentPeriod.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Data; + +@Data +public final class LoanSchedulePlanRepaymentPeriod implements LoanSchedulePlanPeriod { + + private final int periodNumber; + private final LocalDate periodFromDate; + private final LocalDate periodDueDate; + private final BigDecimal principalAmount; + private final BigDecimal interestAmount; + private final BigDecimal feeAmount; + private final BigDecimal penaltyAmount; + private final BigDecimal totalDueAmount; + private final BigDecimal outstandingLoanBalance; + + @Override + public Integer periodNumber() { + return periodNumber; + } + + @Override + public LocalDate periodFromDate() { + return periodFromDate; + } + + @Override + public LocalDate periodDueDate() { + return periodDueDate; + } +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index e4382e54d..bb4b69e3d 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PayableDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; @@ -165,11 +166,10 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding); } - public LoanScheduleModel generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) { + public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) { LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(modelData, mc); - - return generate(mc, loanApplicationTerms, null, null); + return LoanSchedulePlan.from(generate(mc, loanApplicationTerms, null, null)); } private void prepareDisbursementsOnLoanApplicationTerms(final LoanApplicationTerms loanApplicationTerms) { @@ -221,22 +221,22 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency(), mc); if (loanApplicationTerms.isDownPaymentEnabled()) { downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), MathUtil.percentageOf(disbursedAmount.getAmount(), - loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); + loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), mc), mc); if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) { downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, - loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); } LoanScheduleModelDownPaymentPeriod downPaymentPeriod = LoanScheduleModelDownPaymentPeriod.downPayment( scheduleParams.getInstalmentNumber(), disbursementDate, downPaymentAmount, - outstandingBalance.plus(disbursedAmount).minus(downPaymentAmount)); + outstandingBalance.plus(disbursedAmount, mc).minus(downPaymentAmount, mc)); periods.add(downPaymentPeriod); scheduleParams.addTotalRepaymentExpected(downPaymentAmount); scheduleParams.incrementInstalmentNumber(); } - final Money disbursementRemainingBalance = disbursedAmount.minus(downPaymentAmount); + final Money disbursementRemainingBalance = disbursedAmount.minus(downPaymentAmount, mc); scheduleParams.addPrincipalToBeScheduled(disbursementRemainingBalance); emiCalculator.addDisbursement(interestScheduleModel, disbursementDate, disbursementRemainingBalance); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 29013b834..dab618d09 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -237,7 +237,7 @@ public final class ProgressiveEMICalculator implements EMICalculator { .isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods), mc)); if (shouldBeAdjusted) { - Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, mc.getRoundingMode(), mc); + Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, mc); Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel, originalEmi.plus(adjustment, mc)); if (adjustedEqualMonthlyInstallmentValue.isEqualTo(originalEmi)) { diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java index c5b7f65d1..3e42964e1 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java @@ -19,79 +19,135 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; -import java.util.List; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanDisbursementPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanDownPaymentPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanRepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) class LoanScheduleGeneratorTest { private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(); private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$"); private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22); + private static final BigDecimal DISBURSEMENT_AMOUNT_100 = BigDecimal.valueOf(100); private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99); private static final int NUMBER_OF_REPAYMENTS = 6; private static final int REPAYMENT_FREQUENCY = 1; private static final String REPAYMENT_FREQUENCY_TYPE = "MONTHS"; private static final LocalDate DISBURSEMENT_DATE = LocalDate.of(2024, 1, 15); private static final MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN); + private static final BigDecimal DOWN_PAYMENT_PORTION = BigDecimal.valueOf(25); @Test void testGenerateLoanSchedule() { - List periods = getLoanScheduleModelPeriods(); - - assertEquals(7, periods.size(), "Expected 7 periods including the downpayment period."); - - LoanScheduleModelDisbursementPeriod disbursementPeriod = (LoanScheduleModelDisbursementPeriod) periods.get(0); - assertNotNull(disbursementPeriod); - - checkPeriod(periods.get(1), 1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1), BigDecimal.valueOf(31.97), - BigDecimal.valueOf(0.88), BigDecimal.valueOf(32.85), BigDecimal.valueOf(160.25)); - checkPeriod(periods.get(2), 2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1), BigDecimal.valueOf(31.52), - BigDecimal.valueOf(1.33), BigDecimal.valueOf(32.85), BigDecimal.valueOf(128.73)); - checkPeriod(periods.get(3), 3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1), BigDecimal.valueOf(31.78), - BigDecimal.valueOf(1.07), BigDecimal.valueOf(32.85), BigDecimal.valueOf(96.95)); - checkPeriod(periods.get(4), 4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1), BigDecimal.valueOf(32.04), - BigDecimal.valueOf(0.81), BigDecimal.valueOf(32.85), BigDecimal.valueOf(64.91)); - checkPeriod(periods.get(5), 5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1), BigDecimal.valueOf(32.31), - BigDecimal.valueOf(0.54), BigDecimal.valueOf(32.85), BigDecimal.valueOf(32.60)); - checkPeriod(periods.get(6), 6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1), BigDecimal.valueOf(32.60), - BigDecimal.valueOf(0.27), BigDecimal.valueOf(32.87), BigDecimal.ZERO); - } - - private static List getLoanScheduleModelPeriods() { LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY, DISBURSEMENT_AMOUNT, DISBURSEMENT_DATE, NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE, - NOMINAL_INTEREST_RATE, true, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null); + NOMINAL_INTEREST_RATE, false, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null); ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator); - LoanScheduleModel loanSchedule = generator.generate(mc, modelData); - return loanSchedule.getPeriods(); + LoanSchedulePlan loanSchedule = generator.generate(mc, modelData); + + assertEquals(7, loanSchedule.getPeriods().size(), "Expected 7 periods without the downpayment period."); + + checkDisbursementPeriod((LoanSchedulePlanDisbursementPeriod) loanSchedule.getPeriods().get(0), LocalDate.of(2024, 1, 15), + DISBURSEMENT_AMOUNT, DISBURSEMENT_AMOUNT); + + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(1), 1, LocalDate.of(2024, 1, 1), + LocalDate.of(2024, 2, 1), BigDecimal.valueOf(31.97), BigDecimal.valueOf(0.88), BigDecimal.valueOf(32.85), + BigDecimal.valueOf(160.25)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(2), 2, LocalDate.of(2024, 2, 1), + LocalDate.of(2024, 3, 1), BigDecimal.valueOf(31.52), BigDecimal.valueOf(1.33), BigDecimal.valueOf(32.85), + BigDecimal.valueOf(128.73)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(3), 3, LocalDate.of(2024, 3, 1), + LocalDate.of(2024, 4, 1), BigDecimal.valueOf(31.78), BigDecimal.valueOf(1.07), BigDecimal.valueOf(32.85), + BigDecimal.valueOf(96.95)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(4), 4, LocalDate.of(2024, 4, 1), + LocalDate.of(2024, 5, 1), BigDecimal.valueOf(32.04), BigDecimal.valueOf(0.81), BigDecimal.valueOf(32.85), + BigDecimal.valueOf(64.91)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(5), 5, LocalDate.of(2024, 5, 1), + LocalDate.of(2024, 6, 1), BigDecimal.valueOf(32.31), BigDecimal.valueOf(0.54), BigDecimal.valueOf(32.85), + BigDecimal.valueOf(32.60)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(6), 6, LocalDate.of(2024, 6, 1), + LocalDate.of(2024, 7, 1), BigDecimal.valueOf(32.60), BigDecimal.valueOf(0.27), BigDecimal.valueOf(32.87), BigDecimal.ZERO); } - private void checkPeriod(LoanScheduleModelPeriod period, int expectedPeriodNumber, LocalDate expectedFromDate, + @Test + void testGenerateLoanScheduleWithDownPayment() { + LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY, + DISBURSEMENT_AMOUNT_100, LocalDate.of(2024, 1, 1), NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE, + NOMINAL_INTEREST_RATE, true, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, DOWN_PAYMENT_PORTION, null, null); + + ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator(); + ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator); + + LoanSchedulePlan loanSchedule = generator.generate(mc, modelData); + + assertEquals(8, loanSchedule.getPeriods().size(), "Expected 8 periods with the downpayment period."); + + checkDisbursementPeriod((LoanSchedulePlanDisbursementPeriod) loanSchedule.getPeriods().get(0), LocalDate.of(2024, 1, 1), + DISBURSEMENT_AMOUNT_100, DISBURSEMENT_AMOUNT_100); + checkDownPaymentPeriod((LoanSchedulePlanDownPaymentPeriod) loanSchedule.getPeriods().get(1), 1, LocalDate.of(2024, 1, 1), + LocalDate.of(2024, 1, 1), BigDecimal.valueOf(25.0), BigDecimal.valueOf(25.0), BigDecimal.valueOf(75.0)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(2), 2, LocalDate.of(2024, 1, 1), + LocalDate.of(2024, 2, 1), BigDecimal.valueOf(12.25), BigDecimal.valueOf(0.62), BigDecimal.valueOf(12.87), + BigDecimal.valueOf(62.75)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(3), 3, LocalDate.of(2024, 2, 1), + LocalDate.of(2024, 3, 1), BigDecimal.valueOf(12.35), BigDecimal.valueOf(0.52), BigDecimal.valueOf(12.87), + BigDecimal.valueOf(50.40)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(4), 4, LocalDate.of(2024, 3, 1), + LocalDate.of(2024, 4, 1), BigDecimal.valueOf(12.45), BigDecimal.valueOf(0.42), BigDecimal.valueOf(12.87), + BigDecimal.valueOf(37.95)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(5), 5, LocalDate.of(2024, 4, 1), + LocalDate.of(2024, 5, 1), BigDecimal.valueOf(12.55), BigDecimal.valueOf(0.32), BigDecimal.valueOf(12.87), + BigDecimal.valueOf(25.40)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(6), 6, LocalDate.of(2024, 5, 1), + LocalDate.of(2024, 6, 1), BigDecimal.valueOf(12.66), BigDecimal.valueOf(0.21), BigDecimal.valueOf(12.87), + BigDecimal.valueOf(12.74)); + checkRepaymentPeriod((LoanSchedulePlanRepaymentPeriod) loanSchedule.getPeriods().get(7), 7, LocalDate.of(2024, 6, 1), + LocalDate.of(2024, 7, 1), BigDecimal.valueOf(12.74), BigDecimal.valueOf(0.11), BigDecimal.valueOf(12.85), BigDecimal.ZERO); + } + + private void checkDisbursementPeriod(LoanSchedulePlanDisbursementPeriod period, LocalDate disbursementDate, BigDecimal principalAmount, + BigDecimal outstandingAmount) { + assertEquals(disbursementDate, period.getPeriodFromDate()); + assertEquals(disbursementDate, period.getPeriodDueDate()); + assertEquals(0, principalAmount.compareTo(period.getPrincipalAmount())); + assertEquals(0, outstandingAmount.compareTo(period.getOutstandingLoanBalance())); + + } + + private void checkRepaymentPeriod(LoanSchedulePlanRepaymentPeriod period, int expectedPeriodNumber, LocalDate expectedFromDate, LocalDate expectedDueDate, BigDecimal expectedPrincipalDue, BigDecimal expectedInterestDue, BigDecimal expectedTotalDue, BigDecimal expectedOutstandingLoanBalance) { - LoanScheduleModelRepaymentPeriod repaymentPeriod = (LoanScheduleModelRepaymentPeriod) period; - assertEquals(expectedPeriodNumber, repaymentPeriod.getPeriodNumber()); - assertEquals(expectedFromDate, repaymentPeriod.getFromDate()); - assertEquals(expectedDueDate, repaymentPeriod.getDueDate()); - assertEquals(0, expectedPrincipalDue.compareTo(repaymentPeriod.getPrincipalDue().getAmount())); - assertEquals(0, expectedInterestDue.compareTo(repaymentPeriod.getInterestDue().getAmount())); - assertEquals(0, expectedTotalDue.compareTo(repaymentPeriod.getTotalDue().getAmount())); - assertEquals(0, expectedOutstandingLoanBalance.compareTo(repaymentPeriod.getOutstandingLoanBalance().getAmount())); + assertEquals(expectedPeriodNumber, period.getPeriodNumber()); + assertEquals(expectedFromDate, period.getPeriodFromDate()); + assertEquals(expectedDueDate, period.getPeriodDueDate()); + assertEquals(0, expectedPrincipalDue.compareTo(period.getPrincipalAmount())); + assertEquals(0, expectedInterestDue.compareTo(period.getInterestAmount())); + assertEquals(0, expectedTotalDue.compareTo(period.getTotalDueAmount())); + assertEquals(0, expectedOutstandingLoanBalance.compareTo(period.getOutstandingLoanBalance())); + } + + private void checkDownPaymentPeriod(LoanSchedulePlanDownPaymentPeriod period, int expectedPeriodNumber, LocalDate expectedFromDate, + LocalDate expectedDueDate, BigDecimal expectedPrincipalDue, BigDecimal expectedTotalDue, + BigDecimal expectedOutstandingLoanBalance) { + assertEquals(expectedPeriodNumber, period.getPeriodNumber()); + assertEquals(expectedFromDate, period.getPeriodFromDate()); + assertEquals(expectedDueDate, period.getPeriodDueDate()); + assertEquals(0, expectedPrincipalDue.compareTo(period.getPrincipalAmount())); + assertEquals(0, expectedTotalDue.compareTo(period.getTotalDueAmount())); + assertEquals(0, expectedOutstandingLoanBalance.compareTo(period.getOutstandingLoanBalance())); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java index 5a683d1c3..65a0e753f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/provisioning/service/ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl.java @@ -186,7 +186,7 @@ public class ProvisioningEntriesWritePlatformServiceJpaRepositoryImpl implements GLAccount expenseAccount = glAccountRepository.findById(data.getExpenseAccount()).orElseThrow(); MonetaryCurrency currency = loanProduct.getPrincipalAmount().getCurrency(); Money money = Money.of(currency, data.getBalance()); - Money amountToReserve = money.percentageOf(data.getPercentage(), MoneyHelper.getRoundingMode()); + Money amountToReserve = money.percentageOf(data.getPercentage(), MoneyHelper.getMathContext()); Long criteraId = data.getCriteriaId(); LoanProductProvisioningEntry entry = new LoanProductProvisioningEntry().setLoanProduct(loanProduct).setOffice(office) .setCurrencyCode(data.getCurrencyCode()).setProvisioningCategory(provisioningCategory)