FINERACT-1981: Remove hardcoded MoneyHelper dependency during progressive loan

This commit is contained in:
Andrii Kulminskyi
2024-10-22 18:45:28 +02:00
committed by Adam Saghy
parent 806ee69798
commit a9ecdf97a4
31 changed files with 536 additions and 355 deletions
@@ -359,6 +359,11 @@ public final class MathUtil {
return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrency()); return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrency());
} }
/** @return parameter value or ZERO if it is negative */
public static Money negativeToZero(Money value, MathContext mc) {
return value == null || isGreaterThanZero(value, mc) ? value : Money.zero(value.getCurrency(), mc);
}
public static boolean isEmpty(Money value) { public static boolean isEmpty(Money value) {
return value == null || value.isZero(); return value == null || value.isZero();
} }
@@ -367,6 +372,10 @@ public final class MathUtil {
return value != null && value.isGreaterThanZero(); return value != null && value.isGreaterThanZero();
} }
public static boolean isGreaterThanZero(Money value, MathContext mc) {
return value != null && value.isGreaterThanZero(mc);
}
public static boolean isLessThanZero(Money value) { public static boolean isLessThanZero(Money value) {
return value != null && value.isLessThanZero(); return value != null && value.isLessThanZero();
} }
@@ -387,10 +396,18 @@ public final class MathUtil {
return first == null ? second : second == null ? first : first.plus(second); return first == null ? second : second == null ? first : first.plus(second);
} }
public static Money plus(Money first, Money second, MathContext mc) {
return first == null ? second : second == null ? first : first.plus(second, mc);
}
public static Money plus(Money first, Money second, Money third) { public static Money plus(Money first, Money second, Money third) {
return plus(plus(first, second), third); return plus(plus(first, second), third);
} }
public static Money plus(Money first, Money second, Money third, MathContext mc) {
return plus(plus(first, second), third, mc);
}
public static Money plus(Money first, Money second, Money third, Money fourth) { public static Money plus(Money first, Money second, Money third, Money fourth) {
return plus(plus(plus(first, second), third), fourth); return plus(plus(plus(first, second), third), fourth);
} }
@@ -21,11 +21,14 @@ package org.apache.fineract.organisation.monetary.domain;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Embeddable; import jakarta.persistence.Embeddable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.Iterator; import java.util.Iterator;
import lombok.Getter;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
@Embeddable @Embeddable
@Getter
public class Money implements Comparable<Money> { public class Money implements Comparable<Money> {
@Column(name = "currency_code", length = 3) @Column(name = "currency_code", length = 3)
@@ -40,6 +43,16 @@ public class Money implements Comparable<Money> {
@Column(name = "amount", scale = 6, precision = 19) @Column(name = "amount", scale = 6, precision = 19)
private BigDecimal amount; private BigDecimal amount;
private final transient MathContext mc;
protected Money() {
this.currencyCode = null;
this.currencyDigitsAfterDecimal = 0;
this.inMultiplesOf = 0;
this.amount = null;
this.mc = getMc();
}
public static Money total(final Money... monies) { public static Money total(final Money... monies) {
if (monies.length == 0) { if (monies.length == 0) {
throw new IllegalArgumentException("Money array must not be empty"); throw new IllegalArgumentException("Money array must not be empty");
@@ -53,7 +66,7 @@ public class Money implements Comparable<Money> {
public static Money total(final Iterable<? extends Money> monies) { public static Money total(final Iterable<? extends Money> monies) {
final Iterator<? extends Money> it = monies.iterator(); final Iterator<? extends Money> it = monies.iterator();
if (it.hasNext() == false) { if (!it.hasNext()) {
throw new IllegalArgumentException("Money iterator must not be empty"); throw new IllegalArgumentException("Money iterator must not be empty");
} }
Money total = it.next(); Money total = it.next();
@@ -63,34 +76,47 @@ public class Money implements Comparable<Money> {
return total; return total;
} }
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount, final MathContext mc) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount),
currency.getCurrencyInMultiplesOf(), mc);
}
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) { public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount), return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount),
currency.getCurrencyInMultiplesOf()); currency.getCurrencyInMultiplesOf(), null);
} }
public static Money of(final CurrencyData currency, final BigDecimal newAmount) { public static Money of(final CurrencyData currency, final BigDecimal newAmount) {
return new Money(currency.getCode(), currency.getDecimalPlaces(), defaultToZeroIfNull(newAmount), currency.getInMultiplesOf()); return new Money(currency.getCode(), currency.getDecimalPlaces(), defaultToZeroIfNull(newAmount), currency.getInMultiplesOf(),
null);
}
public static Money of(final CurrencyData currency, final BigDecimal newAmount, final MathContext mc) {
return new Money(currency.getCode(), currency.getDecimalPlaces(), defaultToZeroIfNull(newAmount), currency.getInMultiplesOf(), mc);
} }
public static Money zero(final MonetaryCurrency currency) { public static Money zero(final MonetaryCurrency currency) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf()); return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf(), null);
}
public static Money zero(final MonetaryCurrency currency, MathContext mc) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf(), mc);
}
public static Money zero(final CurrencyData currency, MathContext mc) {
return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf(), mc);
} }
public static Money zero(final CurrencyData currency) { public static Money zero(final CurrencyData currency) {
return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf()); return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf(), null);
} }
protected Money() { private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf,
this.currencyCode = null; final MathContext mc) {
this.currencyDigitsAfterDecimal = 0;
this.inMultiplesOf = 0;
this.amount = null;
}
private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf) {
this.currencyCode = currencyCode; this.currencyCode = currencyCode;
this.currencyDigitsAfterDecimal = digitsAfterDecimal; this.currencyDigitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf; this.inMultiplesOf = inMultiplesOf;
this.mc = mc;
final BigDecimal amountZeroed = defaultToZeroIfNull(amount); final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
final BigDecimal amountStripped = amountZeroed.stripTrailingZeros(); final BigDecimal amountStripped = amountZeroed.stripTrailingZeros();
@@ -101,7 +127,7 @@ public class Money implements Comparable<Money> {
final double existingVal = amountScaled.doubleValue(); final double existingVal = amountScaled.doubleValue();
amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf)); amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf));
} }
this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, MoneyHelper.getRoundingMode()); this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, getMc().getRoundingMode());
} }
public static double roundToMultiplesOf(final double existingVal, final Integer inMultiplesOf) { public static double roundToMultiplesOf(final double existingVal, final Integer inMultiplesOf) {
@@ -130,10 +156,14 @@ public class Money implements Comparable<Money> {
} }
public static Money roundToMultiplesOf(final Money existingVal, final Integer inMultiplesOf) { public static Money roundToMultiplesOf(final Money existingVal, final Integer inMultiplesOf) {
return roundToMultiplesOf(existingVal, inMultiplesOf, MoneyHelper.getMathContext());
}
public static Money roundToMultiplesOf(final Money existingVal, final Integer inMultiplesOf, final MathContext mc) {
BigDecimal amountScaled = existingVal.getAmount(); BigDecimal amountScaled = existingVal.getAmount();
BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue()); BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue());
if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) { if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) {
amountScaled = amountScaled.divide(inMultiplesOfValue, 0, MoneyHelper.getRoundingMode()).multiply(inMultiplesOfValue); amountScaled = amountScaled.divide(inMultiplesOfValue, 0, mc.getRoundingMode()).multiply(inMultiplesOfValue);
} }
return Money.of(existingVal.getCurrency(), amountScaled); return Money.of(existingVal.getCurrency(), amountScaled);
} }
@@ -171,7 +201,7 @@ public class Money implements Comparable<Money> {
} }
public Money copy() { public Money copy() {
return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf); return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf, this.mc);
} }
public Money plus(final Iterable<? extends Money> moniesToAdd) { public Money plus(final Iterable<? extends Money> moniesToAdd) {
@@ -188,6 +218,11 @@ public class Money implements Comparable<Money> {
return this.plus(toAdd.getAmount()); return this.plus(toAdd.getAmount());
} }
public Money plus(final Money moneyToAdd, final MathContext mc) {
final Money toAdd = checkCurrencyEqual(moneyToAdd);
return this.plus(toAdd.getAmount(), mc);
}
public Money plus(final BigDecimal amountToAdd) { public Money plus(final BigDecimal amountToAdd) {
if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) {
return this; return this;
@@ -196,6 +231,14 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), newAmount); return Money.of(monetaryCurrency(), newAmount);
} }
public Money plus(final BigDecimal amountToAdd, MathContext mc) {
if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.add(amountToAdd);
return Money.of(monetaryCurrency(), newAmount, mc);
}
public Money plus(final double amountToAdd) { public Money plus(final double amountToAdd) {
if (amountToAdd == 0) { if (amountToAdd == 0) {
return this; return this;
@@ -209,11 +252,21 @@ public class Money implements Comparable<Money> {
return this.minus(toSubtract.getAmount()); return this.minus(toSubtract.getAmount());
} }
public Money minus(final Money moneyToSubtract, final MathContext mc) {
final Money toSubtract = checkCurrencyEqual(moneyToSubtract);
return this.minus(toSubtract.getAmount(), mc);
}
public Money add(final Money moneyToAdd) { public Money add(final Money moneyToAdd) {
final Money toAdd = checkCurrencyEqual(moneyToAdd); final Money toAdd = checkCurrencyEqual(moneyToAdd);
return this.add(toAdd.getAmount()); return this.add(toAdd.getAmount());
} }
public Money add(final Money moneyToAdd, final MathContext mc) {
final Money toAdd = checkCurrencyEqual(moneyToAdd);
return this.add(toAdd.getAmount(), mc);
}
public Money add(final BigDecimal amountToAdd) { public Money add(final BigDecimal amountToAdd) {
if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) {
return this; return this;
@@ -222,6 +275,14 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), newAmount); return Money.of(monetaryCurrency(), newAmount);
} }
public Money add(final BigDecimal amountToAdd, final MathContext mc) {
if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.add(amountToAdd);
return Money.of(monetaryCurrency(), newAmount, mc);
}
public Money minus(final BigDecimal amountToSubtract) { public Money minus(final BigDecimal amountToSubtract) {
if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) { if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) {
return this; return this;
@@ -230,8 +291,16 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), newAmount); return Money.of(monetaryCurrency(), newAmount);
} }
public Money minus(final BigDecimal amountToSubtract, final MathContext mc) {
if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.subtract(amountToSubtract);
return Money.of(monetaryCurrency(), newAmount, mc);
}
private Money checkCurrencyEqual(final Money money) { private Money checkCurrencyEqual(final Money money) {
if (isSameCurrency(money) == false) { if (!isSameCurrency(money)) {
throw new UnsupportedOperationException("currencies are different."); throw new UnsupportedOperationException("currencies are different.");
} }
return money; return money;
@@ -249,6 +318,14 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), newAmount); 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);
return Money.of(monetaryCurrency(), newAmount, mc);
}
public Money dividedBy(final double valueToDivideBy, final RoundingMode roundingMode) { public Money dividedBy(final double valueToDivideBy, final RoundingMode roundingMode) {
if (valueToDivideBy == 1) { if (valueToDivideBy == 1) {
return this; return this;
@@ -265,6 +342,14 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), newAmount); 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);
return Money.of(monetaryCurrency(), newAmount, mc);
}
public Money multipliedBy(final BigDecimal valueToMultiplyBy) { public Money multipliedBy(final BigDecimal valueToMultiplyBy) {
if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) {
return this; return this;
@@ -273,6 +358,14 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), newAmount); return Money.of(monetaryCurrency(), newAmount);
} }
public Money multipliedBy(final BigDecimal valueToMultiplyBy, final MathContext mc) {
if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) {
return this;
}
final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy);
return Money.of(monetaryCurrency(), newAmount, mc);
}
public Money multipliedBy(final double valueToMultiplyBy) { public Money multipliedBy(final double valueToMultiplyBy) {
if (valueToMultiplyBy == 1) { if (valueToMultiplyBy == 1) {
return this; return this;
@@ -289,6 +382,14 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), newAmount); return Money.of(monetaryCurrency(), newAmount);
} }
public Money multipliedBy(final long valueToMultiplyBy, final MathContext mc) {
if (valueToMultiplyBy == 1) {
return this;
}
final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy));
return Money.of(monetaryCurrency(), newAmount, mc);
}
public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final RoundingMode roundingMode) { public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final RoundingMode roundingMode) {
if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) {
return this; return this;
@@ -309,17 +410,20 @@ public class Money implements Comparable<Money> {
@Override @Override
public int compareTo(final Money other) { public int compareTo(final Money other) {
final Money otherMoney = other; if (!this.currencyCode.equals(other.currencyCode)) {
if (this.currencyCode.equals(otherMoney.currencyCode) == false) {
throw new UnsupportedOperationException("currencies arent different"); throw new UnsupportedOperationException("currencies arent different");
} }
return this.amount.compareTo(otherMoney.amount); return this.amount.compareTo(other.amount);
} }
public boolean isZero() { public boolean isZero() {
return isEqualTo(Money.zero(getCurrency())); return isEqualTo(Money.zero(getCurrency()));
} }
public boolean isZero(final MathContext mc) {
return isEqualTo(Money.zero(getCurrency(), mc));
}
public boolean isEqualTo(final Money other) { public boolean isEqualTo(final Money other) {
return compareTo(other) == 0; return compareTo(other) == 0;
} }
@@ -340,6 +444,10 @@ public class Money implements Comparable<Money> {
return isGreaterThan(Money.zero(getCurrency())); return isGreaterThan(Money.zero(getCurrency()));
} }
public boolean isGreaterThanZero(MathContext mc) {
return isGreaterThan(Money.zero(getCurrency(), mc));
}
public boolean isLessThan(final Money other) { public boolean isLessThan(final Money other) {
return compareTo(other) < 0; return compareTo(other) < 0;
} }
@@ -348,22 +456,14 @@ public class Money implements Comparable<Money> {
return isLessThan(Money.zero(getCurrency())); return isLessThan(Money.zero(getCurrency()));
} }
public String getCurrencyCode() { public boolean isLessThanZero(final MathContext mc) {
return this.currencyCode; return isLessThan(Money.zero(getCurrency(), mc));
}
public int getCurrencyDigitsAfterDecimal() {
return this.currencyDigitsAfterDecimal;
} }
public Integer getCurrencyInMultiplesOf() { public Integer getCurrencyInMultiplesOf() {
return this.inMultiplesOf; return this.inMultiplesOf;
} }
public BigDecimal getAmount() {
return this.amount;
}
public BigDecimal getAmountDefaultedToNullIfZero() { public BigDecimal getAmountDefaultedToNullIfZero() {
return defaultToNullIfZero(this.amount); return defaultToNullIfZero(this.amount);
} }
@@ -388,10 +488,21 @@ public class Money implements Comparable<Money> {
return Money.of(monetaryCurrency(), this.amount.negate()); return Money.of(monetaryCurrency(), this.amount.negate());
} }
public Money negated(final MathContext mc) {
if (isZero(mc)) {
return this;
}
return Money.of(monetaryCurrency(), this.amount.negate(), mc);
}
public Money abs() { public Money abs() {
return isLessThanZero() ? negated() : this; return isLessThanZero() ? negated() : this;
} }
public Money abs(MathContext mc) {
return isLessThanZero(mc) ? negated(mc) : this;
}
public MonetaryCurrency getCurrency() { public MonetaryCurrency getCurrency() {
return monetaryCurrency(); return monetaryCurrency();
} }
@@ -403,4 +514,12 @@ public class Money implements Comparable<Money> {
public Money zero() { public Money zero() {
return Money.zero(getCurrency()); return Money.zero(getCurrency());
} }
public Money zero(MathContext mc) {
return Money.zero(getCurrency(), mc);
}
public MathContext getMc() {
return mc != null ? mc : MoneyHelper.getMathContext();
}
} }
@@ -65,6 +65,7 @@ public class MoneyHelper {
public static void fetchRoundingModeFromGlobalConfig() { public static void fetchRoundingModeFromGlobalConfig() {
roundingMode = RoundingMode.valueOf(staticConfigurationDomainService.getRoundingMode()); roundingMode = RoundingMode.valueOf(staticConfigurationDomainService.getRoundingMode());
log.info("Fetch Rounding Mode from Global Config {}", roundingMode.name()); log.info("Fetch Rounding Mode from Global Config {}", roundingMode.name());
mathContext = null;
} }
} }
+5
View File
@@ -59,6 +59,11 @@ dependencies {
'org.apache.commons:commons-collections4' 'org.apache.commons:commons-collections4'
) )
implementation ('org.mnode.ical4j:ical4j') {
exclude group: 'commons-logging'
exclude group: 'javax.activation'
exclude group: 'com.sun.mail', module: 'javax.mail'
}
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor' annotationProcessor 'org.mapstruct:mapstruct-processor'
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.loanschedule.data; package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -98,6 +99,7 @@ public final class LoanScheduleParams {
private int loanTermInDays; private int loanTermInDays;
private final MonetaryCurrency currency; private final MonetaryCurrency currency;
private final boolean applyInterestRecalculation; private final boolean applyInterestRecalculation;
private final MathContext mc;
private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate, private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
@@ -108,7 +110,7 @@ public final class LoanScheduleParams {
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments, final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> recalculationDetails, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) { final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) {
this.periodNumber = periodNumber; this.periodNumber = periodNumber;
this.instalmentNumber = instalmentNumber; this.instalmentNumber = instalmentNumber;
this.loanTermInDays = loanTermInDays; this.loanTermInDays = loanTermInDays;
@@ -136,8 +138,9 @@ public final class LoanScheduleParams {
this.partialUpdate = partialUpdate; this.partialUpdate = partialUpdate;
this.currency = currency; this.currency = currency;
this.applyInterestRecalculation = applyInterestRecalculation; this.applyInterestRecalculation = applyInterestRecalculation;
this.mc = mc;
if (this.currency != null) { if (this.currency != null) {
this.compoundedInLastInstallment = Money.zero(this.currency); this.compoundedInLastInstallment = Money.zero(this.currency, mc);
} }
} }
@@ -150,19 +153,19 @@ public final class LoanScheduleParams {
final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails, final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final MonetaryCurrency currency, final boolean applyInterestRecalculation) { final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) {
final boolean partialUpdate = true; final boolean partialUpdate = true;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
} }
public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails, public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean applyInterestRecalculation) { final boolean applyInterestRecalculation, MathContext mc) {
final int periodNumber = 1; final int periodNumber = 1;
final int instalmentNumber = 1; final int instalmentNumber = 1;
final LocalDate periodStartDate = null; final LocalDate periodStartDate = null;
@@ -191,22 +194,22 @@ public final class LoanScheduleParams {
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
} }
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
final LocalDate periodStartDate, final Money principalToBeScheduled) { final LocalDate periodStartDate, final Money principalToBeScheduled, final MathContext mc) {
final int loanTermInDays = 0; final int loanTermInDays = 0;
final int periodNumber = 1; final int periodNumber = 1;
final int instalmentNumber = 1; final int instalmentNumber = 1;
final Money totalCumulativePrincipal = Money.zero(currency); final Money totalCumulativePrincipal = Money.zero(currency, mc);
final Money totalCumulativeInterest = Money.zero(currency); final Money totalCumulativeInterest = Money.zero(currency, mc);
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency, mc);
final LocalDate actualRepaymentDate = periodStartDate; final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement; final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency); final Money totalPenaltyChargesCharged = Money.zero(currency, mc);
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement; final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
final Money reducePrincipal = Money.zero(currency); final Money reducePrincipal = Money.zero(currency, mc);
final Map<LocalDate, Money> principalPortionMap = new HashMap<>(); final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
final Map<LocalDate, Money> latePaymentMap = new HashMap<>(); final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
final Map<LocalDate, Money> compoundingMap = new TreeMap<>(); final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
@@ -218,27 +221,28 @@ public final class LoanScheduleParams {
final Collection<RecalculationDetail> recalculationDetails = null; final Collection<RecalculationDetail> recalculationDetails = null;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null; final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
final LocalDate scheduleTillDate = null; final LocalDate scheduleTillDate = null;
final Money unCompoundedAmount = Money.zero(currency); final Money unCompoundedAmount = Money.zero(currency, mc);
final boolean applyInterestRecalculation = false; final boolean applyInterestRecalculation = false;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
} }
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams) { final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams,
MathContext mc) {
final int loanTermInDays = 0; final int loanTermInDays = 0;
final int periodNumber = 1; final int periodNumber = 1;
final int instalmentNumber = 1; final int instalmentNumber = 1;
final Money totalCumulativePrincipal = Money.zero(currency); final Money totalCumulativePrincipal = Money.zero(currency, mc);
final Money totalCumulativeInterest = Money.zero(currency); final Money totalCumulativeInterest = Money.zero(currency, mc);
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency, mc);
final LocalDate actualRepaymentDate = periodStartDate; final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement; final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency); final Money totalPenaltyChargesCharged = Money.zero(currency, mc);
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement; final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
final Money reducePrincipal = Money.zero(currency); final Money reducePrincipal = Money.zero(currency);
final Map<LocalDate, Money> principalPortionMap = new HashMap<>(); final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
@@ -253,13 +257,13 @@ public final class LoanScheduleParams {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor; final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor;
final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate; final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate;
final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation; final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation;
final Money unCompoundedAmount = Money.zero(currency); final Money unCompoundedAmount = Money.zero(currency, mc);
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc);
} }
public int getPeriodNumber() { public int getPeriodNumber() {
@@ -288,7 +292,7 @@ public final class LoanScheduleParams {
public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) { public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) {
if (this.totalCumulativePrincipal != null) { if (this.totalCumulativePrincipal != null) {
this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal); this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal, mc);
} else { } else {
this.totalCumulativePrincipal = totalCumulativePrincipal; this.totalCumulativePrincipal = totalCumulativePrincipal;
} }
@@ -300,7 +304,7 @@ public final class LoanScheduleParams {
public void addTotalCumulativeInterest(final Money totalCumulativeInterest) { public void addTotalCumulativeInterest(final Money totalCumulativeInterest) {
if (this.totalCumulativeInterest != null) { if (this.totalCumulativeInterest != null) {
this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest); this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest, mc);
} else { } else {
this.totalCumulativeInterest = totalCumulativeInterest; this.totalCumulativeInterest = totalCumulativeInterest;
} }
@@ -312,7 +316,7 @@ public final class LoanScheduleParams {
public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) { public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) {
if (this.totalFeeChargesCharged != null) { if (this.totalFeeChargesCharged != null) {
this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged); this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged, mc);
} else { } else {
this.totalFeeChargesCharged = totalFeeChargesCharged; this.totalFeeChargesCharged = totalFeeChargesCharged;
} }
@@ -324,7 +328,7 @@ public final class LoanScheduleParams {
public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) { public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) {
if (this.totalPenaltyChargesCharged != null) { if (this.totalPenaltyChargesCharged != null) {
this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged); this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged, mc);
} else { } else {
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged; this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
} }
@@ -336,7 +340,7 @@ public final class LoanScheduleParams {
public void addTotalRepaymentExpected(final Money totalRepaymentExpected) { public void addTotalRepaymentExpected(final Money totalRepaymentExpected) {
if (this.totalRepaymentExpected != null) { if (this.totalRepaymentExpected != null) {
this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected); this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected, mc);
} else { } else {
this.totalRepaymentExpected = totalRepaymentExpected; this.totalRepaymentExpected = totalRepaymentExpected;
} }
@@ -428,7 +432,7 @@ public final class LoanScheduleParams {
public void addReducePrincipal(Money reducePrincipal) { public void addReducePrincipal(Money reducePrincipal) {
if (this.reducePrincipal != null) { if (this.reducePrincipal != null) {
this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal); this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal, mc);
} else { } else {
this.reducePrincipal = reducePrincipal; this.reducePrincipal = reducePrincipal;
} }
@@ -436,13 +440,13 @@ public final class LoanScheduleParams {
public void reduceReducePrincipal(Money reducePrincipal) { public void reduceReducePrincipal(Money reducePrincipal) {
if (this.reducePrincipal != null) { if (this.reducePrincipal != null) {
this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal); this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal, mc);
} }
} }
public void addPrincipalToBeScheduled(Money principalToBeScheduled) { public void addPrincipalToBeScheduled(Money principalToBeScheduled) {
if (this.principalToBeScheduled != null) { if (this.principalToBeScheduled != null) {
this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled); this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled, mc);
} else { } else {
this.principalToBeScheduled = principalToBeScheduled; this.principalToBeScheduled = principalToBeScheduled;
} }
@@ -450,7 +454,7 @@ public final class LoanScheduleParams {
public void addOutstandingBalance(Money outstandingBalance) { public void addOutstandingBalance(Money outstandingBalance) {
if (this.outstandingBalance != null) { if (this.outstandingBalance != null) {
this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance); this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance, mc);
} else { } else {
this.outstandingBalance = outstandingBalance; this.outstandingBalance = outstandingBalance;
} }
@@ -458,13 +462,13 @@ public final class LoanScheduleParams {
public void reduceOutstandingBalance(Money outstandingBalance) { public void reduceOutstandingBalance(Money outstandingBalance) {
if (this.outstandingBalance != null) { if (this.outstandingBalance != null) {
this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance); this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance, mc);
} }
} }
public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
if (this.outstandingBalanceAsPerRest != null) { if (this.outstandingBalanceAsPerRest != null) {
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest); this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest, mc);
} else { } else {
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
} }
@@ -472,7 +476,7 @@ public final class LoanScheduleParams {
public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
if (this.outstandingBalanceAsPerRest != null) { if (this.outstandingBalanceAsPerRest != null) {
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest); this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest, mc);
} }
} }
@@ -522,7 +526,7 @@ public final class LoanScheduleParams {
public void minusUnCompoundedAmount(Money unCompoundedAmount) { public void minusUnCompoundedAmount(Money unCompoundedAmount) {
if (this.unCompoundedAmount != null) { if (this.unCompoundedAmount != null) {
this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount); this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount, mc);
} }
} }
@@ -94,10 +94,10 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
: loanApplicationTerms.getSubmittedOnDate(); : loanApplicationTerms.getSubmittedOnDate();
if (loanScheduleParams == null) { if (loanScheduleParams == null) {
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms)); periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), mc);
} else if (!loanScheduleParams.isPartialUpdate()) { } else if (!loanScheduleParams.isPartialUpdate()) {
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams); periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams, mc);
} else { } else {
scheduleParams = loanScheduleParams; scheduleParams = loanScheduleParams;
} }
@@ -345,7 +345,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(), scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(),
scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(), scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
totalInstallmentDue, !isCompletePeriod); totalInstallmentDue, !isCompletePeriod, mc);
if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) { if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) {
installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount()); installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount());
} }
@@ -651,7 +651,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
scheduleParams.getPeriodStartDate(), transactionDate, currentPeriodParams.getPrincipalForThisPeriod(), scheduleParams.getPeriodStartDate(), transactionDate, currentPeriodParams.getPrincipalForThisPeriod(),
scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(), scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
currentPeriodParams.fetchTotalAmountForPeriod(), false); currentPeriodParams.fetchTotalAmountForPeriod(), false, mc);
scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(interestTillDate.interestPaymentDueToGrace()); scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(interestTillDate.interestPaymentDueToGrace());
} }
} }
@@ -789,7 +789,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(), installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod, scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod,
scheduleParams.getOutstandingBalance(), interestForCurrentInstallment, feeChargesForInstallment, scheduleParams.getOutstandingBalance(), interestForCurrentInstallment, feeChargesForInstallment,
penaltyChargesForInstallment, totalInstallmentDue, true); penaltyChargesForInstallment, totalInstallmentDue, true, mc);
periods.add(installment); periods.add(installment);
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate);
@@ -1416,7 +1416,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(), LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(),
startDate, transactionDate, totalInterest.zero(), totalInterest.zero(), totalInterest, totalInterest.zero(), startDate, transactionDate, totalInterest.zero(), totalInterest.zero(), totalInterest, totalInterest.zero(),
totalInterest.zero(), totalInterest, true); totalInterest.zero(), totalInterest, true, mc);
params.incrementInstalmentNumber(); params.incrementInstalmentNumber();
periods.add(installment); periods.add(installment);
totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
@@ -1489,7 +1489,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
if (totalInterest.isGreaterThanZero()) { if (totalInterest.isGreaterThanZero()) {
LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(), LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(),
startDate, params.getActualRepaymentDate(), totalInterest.zero(), totalInterest.zero(), totalInterest, startDate, params.getActualRepaymentDate(), totalInterest.zero(), totalInterest.zero(), totalInterest,
totalInterest.zero(), totalInterest.zero(), totalInterest, true); totalInterest.zero(), totalInterest.zero(), totalInterest, true, mc);
params.incrementInstalmentNumber(); params.incrementInstalmentNumber();
periods.add(installment); periods.add(installment);
params.getCompoundingDateVariations().put(startDate, new TreeMap<>(params.getCompoundingMap())); params.getCompoundingDateVariations().put(startDate, new TreeMap<>(params.getCompoundingMap()));
@@ -2189,7 +2189,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
// for complete schedule generation // for complete schedule generation
LoanScheduleParams loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, LoanScheduleParams loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails,
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation, mc);
List<LoanScheduleModelPeriod> periods = new ArrayList<>(); List<LoanScheduleModelPeriod> periods = new ArrayList<>();
final List<LoanRepaymentScheduleInstallment> retainedInstallments = new ArrayList<>(); final List<LoanRepaymentScheduleInstallment> retainedInstallments = new ArrayList<>();
@@ -2402,7 +2402,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
installment.resetDerivedComponents(); installment.resetDerivedComponents();
newRepaymentScheduleInstallments.add(installment); newRepaymentScheduleInstallments.add(installment);
outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency)); outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency));
final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance); final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance, mc);
periods.add(loanScheduleModelPeriod); periods.add(loanScheduleModelPeriod);
totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency)); totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency));
totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency)); totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency));
@@ -2493,7 +2493,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
currency, applyInterestRecalculation); currency, applyInterestRecalculation, mc);
retainedInstallments.addAll(newRepaymentScheduleInstallments); retainedInstallments.addAll(newRepaymentScheduleInstallments);
loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged())); loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged()));
@@ -2729,13 +2729,13 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
} }
private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment, private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment,
final Money outstandingPrincipal) { final Money outstandingPrincipal, final MathContext mc) {
final MonetaryCurrency currency = outstandingPrincipal.getCurrency(); final MonetaryCurrency currency = outstandingPrincipal.getCurrency();
return LoanScheduleModelRepaymentPeriod.repayment(installment.getInstallmentNumber(), installment.getFromDate(), return LoanScheduleModelRepaymentPeriod.repayment(installment.getInstallmentNumber(), installment.getFromDate(),
installment.getDueDate(), installment.getPrincipal(currency), outstandingPrincipal, installment.getDueDate(), installment.getPrincipal(currency), outstandingPrincipal,
installment.getInterestCharged(currency), installment.getFeeChargesCharged(currency), installment.getInterestCharged(currency), installment.getFeeChargesCharged(currency),
installment.getPenaltyChargesCharged(currency), installment.getDue(currency), installment.getPenaltyChargesCharged(currency), installment.getDue(currency), installment.isRecalculatedInterestComponent(),
installment.isRecalculatedInterestComponent()); mc);
} }
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
@@ -45,9 +46,9 @@ import org.springframework.stereotype.Component;
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override @Override
public List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(final LocalDate scheduleStartDate, public List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(final MathContext mc, final LocalDate scheduleStartDate,
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
final Money zeroAmount = Money.zero(loanApplicationTerms.getCurrency()); final Money zeroAmount = Money.zero(loanApplicationTerms.getCurrency(), mc);
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
final ArrayList<LoanScheduleModelRepaymentPeriod> repaymentPeriods = new ArrayList<>(numberOfRepayments); final ArrayList<LoanScheduleModelRepaymentPeriod> repaymentPeriods = new ArrayList<>(numberOfRepayments);
LocalDate lastRepaymentDate = scheduleStartDate; LocalDate lastRepaymentDate = scheduleStartDate;
@@ -65,7 +66,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
} }
nextRepaymentDate = applyLoanTermVariations(loanApplicationTerms, nextRepaymentDate); nextRepaymentDate = applyLoanTermVariations(loanApplicationTerms, nextRepaymentDate);
repaymentPeriods.add(LoanScheduleModelRepaymentPeriod.repayment(repaymentPeriodNumber, lastRepaymentDate, nextRepaymentDate, repaymentPeriods.add(LoanScheduleModelRepaymentPeriod.repayment(repaymentPeriodNumber, lastRepaymentDate, nextRepaymentDate,
zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, false)); zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, false, mc));
lastRepaymentDate = nextRepaymentDate; lastRepaymentDate = nextRepaymentDate;
isFirstRepayment = false; isFirstRepayment = false;
} }
@@ -75,11 +76,13 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
private LocalDate applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, final LocalDate scheduledDueDate) { private LocalDate applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, final LocalDate scheduledDueDate) {
LocalDate modifiedScheduledDueDate = scheduledDueDate; LocalDate modifiedScheduledDueDate = scheduledDueDate;
// due date changes should be applied only for that dueDate // due date changes should be applied only for that dueDate
if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) { if (loanApplicationTerms.getLoanTermVariations() != null) {
LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) {
if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) { LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
modifiedScheduledDueDate = loanTermVariationsData.getDateValue(); if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) {
loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate)); modifiedScheduledDueDate = loanTermVariationsData.getDateValue();
loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate));
}
} }
} }
return modifiedScheduledDueDate; return modifiedScheduledDueDate;
@@ -197,28 +200,30 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate( private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(
final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms, final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms,
final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) { final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) {
final Recur recur = CalendarUtils.getICalRecur(holidayDetailDTO.getWorkingDays().getRecurrence()); if (holidayDetailDTO != null) {
final boolean isSevenDaysWeek = (recur.getDayList().size() == 7); // 7 Seven days in the week final Recur recur = CalendarUtils.getICalRecur(holidayDetailDTO.getWorkingDays().getRecurrence());
// If Workings days are not seven day week final boolean isSevenDaysWeek = (recur.getDayList().size() == 7); // 7 Seven days in the week
if (!isSevenDaysWeek) { // If Workings days are not seven day week
checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, if (!isSevenDaysWeek) {
checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
isFirstRepayment);
}
// Check Holidays If applied
checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
isFirstRepayment); isFirstRepayment);
}
// Check Holidays If applied
checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms,
isFirstRepayment);
/** /**
* Check Changed Schedule Date is holiday or is not a working day Then re-call this method to get the non * Check Changed Schedule Date is holiday or is not a working day Then re-call this method to get the non
* holiday and working day * holiday and working day
*/ */
if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(), if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(),
holidayDetailDTO.getHolidays()) != null) holidayDetailDTO.getHolidays()) != null)
|| WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) { || WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(),
recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO, adjustedDateDetailsDTO.getChangedScheduleDate())) {
loanApplicationTerms, holidayDetailDTO, isFirstRepayment); recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO,
loanApplicationTerms, holidayDetailDTO, isFirstRepayment);
}
} }
return adjustedDateDetailsDTO; return adjustedDateDetailsDTO;
} }
@@ -249,6 +249,8 @@ public final class LoanApplicationTerms {
this.inArrearsTolerance = builder.inArrearsTolerance; this.inArrearsTolerance = builder.inArrearsTolerance;
this.disbursementDatas = builder.disbursementDatas; this.disbursementDatas = builder.disbursementDatas;
this.downPaymentAmount = builder.downPaymentAmount; this.downPaymentAmount = builder.downPaymentAmount;
this.submittedOnDate = builder.submittedOnDate;
this.seedDate = builder.seedDate;
} }
public static class Builder { public static class Builder {
@@ -272,6 +274,8 @@ public final class LoanApplicationTerms {
private Money inArrearsTolerance; private Money inArrearsTolerance;
private List<DisbursementData> disbursementDatas; private List<DisbursementData> disbursementDatas;
private Money downPaymentAmount; private Money downPaymentAmount;
private LocalDate submittedOnDate;
private LocalDate seedDate;
public Builder currency(ApplicationCurrency currency) { public Builder currency(ApplicationCurrency currency) {
this.currency = currency; this.currency = currency;
@@ -343,11 +347,6 @@ public final class LoanApplicationTerms {
return this; return this;
} }
public Builder variationsDataWrapper(LoanTermVariationsDataWrapper variationsDataWrapper) {
this.variationsDataWrapper = variationsDataWrapper;
return this;
}
public Builder fixedLength(Integer fixedLength) { public Builder fixedLength(Integer fixedLength) {
this.fixedLength = fixedLength; this.fixedLength = fixedLength;
return this; return this;
@@ -368,23 +367,41 @@ public final class LoanApplicationTerms {
return this; return this;
} }
public Builder submittedOnDate(LocalDate submittedOnDate) {
this.submittedOnDate = submittedOnDate;
return this;
}
public Builder seedDate(LocalDate seedDate) {
this.seedDate = seedDate;
return this;
}
public LoanApplicationTerms build() { public LoanApplicationTerms build() {
return new LoanApplicationTerms(this); return new LoanApplicationTerms(this);
} }
} }
public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData) { public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData, MathContext mc) {
Money principal = Money.of(modelData.currency().toData(), modelData.disbursementAmount()); Money principal = Money.of(modelData.currency().toData(), modelData.disbursementAmount(), mc);
Money downPaymentAmount = Money.zero(modelData.currency().toData()); Money downPaymentAmount = Money.zero(modelData.currency().toData(), mc);
if (modelData.downPaymentEnabled()) { if (modelData.downPaymentEnabled()) {
downPaymentAmount = Money.of(modelData.currency().toData(), downPaymentAmount = Money.of(modelData.currency().toData(),
MathUtil.percentageOf(principal.getAmount(), modelData.disbursementAmount(), 19)); MathUtil.percentageOf(principal.getAmount(), modelData.disbursementAmount(), mc), mc);
if (modelData.installmentAmountInMultiplesOf() != null) { if (modelData.installmentAmountInMultiplesOf() != null) {
downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, modelData.installmentAmountInMultiplesOf()); downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, modelData.installmentAmountInMultiplesOf(), mc);
} }
} }
LocalDate seedDate;
if (modelData.disbursementDate() != null) {
seedDate = modelData.disbursementDate();
} else {
seedDate = modelData.scheduleGenerationStartDate();
}
return new Builder().currency(modelData.currency()).loanTermFrequency(modelData.numberOfRepayments()) return new Builder().currency(modelData.currency()).loanTermFrequency(modelData.numberOfRepayments())
.loanTermPeriodFrequencyType(PeriodFrequencyType.valueOf(modelData.repaymentFrequencyType())) .loanTermPeriodFrequencyType(PeriodFrequencyType.valueOf(modelData.repaymentFrequencyType()))
.numberOfRepayments(modelData.numberOfRepayments()).repaymentEvery(modelData.repaymentFrequency()) .numberOfRepayments(modelData.numberOfRepayments()).repaymentEvery(modelData.repaymentFrequency())
@@ -394,8 +411,8 @@ public final class LoanApplicationTerms {
.annualNominalInterestRate(modelData.annualNominalInterestRate()).principal(principal) .annualNominalInterestRate(modelData.annualNominalInterestRate()).principal(principal)
.expectedDisbursementDate(modelData.disbursementDate()).repaymentsStartingFromDate(modelData.scheduleGenerationStartDate()) .expectedDisbursementDate(modelData.disbursementDate()).repaymentsStartingFromDate(modelData.scheduleGenerationStartDate())
.daysInMonthType(modelData.daysInMonth()).daysInYearType(modelData.daysInYear()).fixedLength(modelData.fixedLength()) .daysInMonthType(modelData.daysInMonth()).daysInYearType(modelData.daysInYear()).fixedLength(modelData.fixedLength())
.inArrearsTolerance(Money.zero(modelData.currency().toData())).disbursementDatas(new ArrayList<>()) .inArrearsTolerance(Money.zero(modelData.currency().toData(), mc)).disbursementDatas(new ArrayList<>())
.downPaymentAmount(downPaymentAmount).build(); .downPaymentAmount(downPaymentAmount).submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate).build();
} }
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -46,18 +47,20 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>(); private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
private boolean isEMIFixedSpecificToInstallment = false; private boolean isEMIFixedSpecificToInstallment = false;
BigDecimal rescheduleInterestPortion; BigDecimal rescheduleInterestPortion;
private final MathContext mc;
public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate, public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent) { final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent,
final MathContext mc) {
return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance, return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance,
interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent); interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent, mc);
} }
public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate, public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate,
final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue,
final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent) { final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent, final MathContext mc) {
this.periodNumber = periodNumber; this.periodNumber = periodNumber;
this.fromDate = fromDate; this.fromDate = fromDate;
this.dueDate = dueDate; this.dueDate = dueDate;
@@ -68,6 +71,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
this.penaltyChargesDue = penaltyChargesDue; this.penaltyChargesDue = penaltyChargesDue;
this.totalDue = totalDue; this.totalDue = totalDue;
this.recalculatedInterestComponent = recalculatedInterestComponent; this.recalculatedInterestComponent = recalculatedInterestComponent;
this.mc = mc;
} }
@Override @Override
@@ -144,15 +148,15 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
@Override @Override
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) { public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
this.feeChargesDue = this.feeChargesDue.plus(feeCharge); this.feeChargesDue = this.feeChargesDue.plus(feeCharge, mc);
this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge); this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge, mc);
this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge); this.totalDue = this.totalDue.plus(feeCharge, mc).plus(penaltyCharge, mc);
} }
@Override @Override
public void addPrincipalAmount(final Money principalDue) { public void addPrincipalAmount(final Money principalDue) {
this.principalDue = this.principalDue.plus(principalDue); this.principalDue = this.principalDue.plus(principalDue, mc);
this.totalDue = this.totalDue.plus(principalDue); this.totalDue = this.totalDue.plus(principalDue, mc);
} }
@Override @Override
@@ -162,8 +166,8 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
@Override @Override
public void addInterestAmount(Money interestDue) { public void addInterestAmount(Money interestDue) {
this.interestDue = this.interestDue.plus(interestDue); this.interestDue = this.interestDue.plus(interestDue, mc);
this.totalDue = this.totalDue.plus(interestDue); this.totalDue = this.totalDue.plus(interestDue, mc);
} }
public void setPeriodNumber(int periodNumber) { public void setPeriodNumber(int periodNumber) {
@@ -18,6 +18,7 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
@@ -27,8 +28,8 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
public interface ScheduledDateGenerator { public interface ScheduledDateGenerator {
List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms, List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(MathContext mc, LocalDate scheduledDueDate,
HolidayDetailDTO holidayDetailDTO); LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO); LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
@@ -64,6 +65,7 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
@BeforeAll @BeforeAll
public static void init() { public static void init() {
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
} }
@Test @Test
@@ -142,11 +144,12 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
@NotNull @NotNull
private LoanRepaymentScheduleInstallment createPeriod(int periodId, LocalDate start, LocalDate end) { private LoanRepaymentScheduleInstallment createPeriod(int periodId, LocalDate start, LocalDate end) {
LoanRepaymentScheduleInstallment period = Mockito.mock(LoanRepaymentScheduleInstallment.class); LoanRepaymentScheduleInstallment period = Mockito.mock(LoanRepaymentScheduleInstallment.class);
MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
Mockito.when(period.getInstallmentNumber()).thenReturn(periodId); Mockito.when(period.getInstallmentNumber()).thenReturn(periodId);
Mockito.when(period.getFromDate()).thenReturn(start); Mockito.when(period.getFromDate()).thenReturn(start);
Mockito.when(period.getDueDate()).thenReturn(end); Mockito.when(period.getDueDate()).thenReturn(end);
Money principal = Money.of(currency, new BigDecimal("1000.0")); Money principal = Money.of(currency, new BigDecimal("1000.0"), mc);
Money interest = Money.of(currency, BigDecimal.ZERO); Money interest = Money.of(currency, BigDecimal.ZERO, mc);
Mockito.when(period.getPrincipal(eq(currency))).thenReturn(principal); Mockito.when(period.getPrincipal(eq(currency))).thenReturn(principal);
Mockito.when(period.getInterestCharged(eq(currency))).thenReturn(interest); Mockito.when(period.getInterestCharged(eq(currency))).thenReturn(interest);
@@ -176,7 +176,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf(); final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf();
final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateModel(loanProductRelatedDetail, ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateModel(loanProductRelatedDetail,
installmentAmountInMultiplesOf, installments); installmentAmountInMultiplesOf, installments, overpaymentHolder.getMoneyObject().getMc());
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
changedTransactionDetail, scheduleModel); changedTransactionDetail, scheduleModel);
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Optional; import java.util.Optional;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -44,9 +45,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
private Money disbursementAmount; private Money disbursementAmount;
private Money balanceCorrectionAmount; private Money balanceCorrectionAmount;
private Money outstandingLoanBalance; private Money outstandingLoanBalance;
private final MathContext mc;
public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate, LocalDate dueDate, BigDecimal rateFactor, public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate, LocalDate dueDate, BigDecimal rateFactor,
Money disbursementAmount, Money balanceCorrectionAmount, Money outstandingLoanBalance) { Money disbursementAmount, Money balanceCorrectionAmount, Money outstandingLoanBalance, MathContext mc) {
this.repaymentPeriod = repaymentPeriod; this.repaymentPeriod = repaymentPeriod;
this.fromDate = fromDate; this.fromDate = fromDate;
this.dueDate = dueDate; this.dueDate = dueDate;
@@ -54,12 +56,13 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
this.disbursementAmount = disbursementAmount; this.disbursementAmount = disbursementAmount;
this.balanceCorrectionAmount = balanceCorrectionAmount; this.balanceCorrectionAmount = balanceCorrectionAmount;
this.outstandingLoanBalance = outstandingLoanBalance; this.outstandingLoanBalance = outstandingLoanBalance;
this.mc = mc;
} }
public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod interestPeriod) { public InterestPeriod(RepaymentPeriod repaymentPeriod, InterestPeriod interestPeriod, MathContext mc) {
this(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), interestPeriod.getRateFactor(), this(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(), interestPeriod.getRateFactor(),
interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
interestPeriod.getOutstandingLoanBalance()); interestPeriod.getOutstandingLoanBalance(), mc);
} }
@Override @Override
@@ -72,11 +75,11 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
} }
public void addDisbursementAmount(final Money disbursementAmount) { public void addDisbursementAmount(final Money disbursementAmount) {
this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount); this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount, mc);
} }
public Money getCalculatedDueInterest() { public Money getCalculatedDueInterest() {
return getOutstandingLoanBalance().multipliedBy(getRateFactor()); return getOutstandingLoanBalance().multipliedBy(getRateFactor(), mc);
} }
public void updateOutstandingLoanBalance() { public void updateOutstandingLoanBalance() {
@@ -86,17 +89,17 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getInterestPeriods() InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getInterestPeriods()
.get(previousRepaymentPeriod.get().getInterestPeriods().size() - 1); .get(previousRepaymentPeriod.get().getInterestPeriods().size() - 1);
this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance()// this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance()//
.plus(previousInterestPeriod.getDisbursementAmount())// .plus(previousInterestPeriod.getDisbursementAmount(), mc)//
.plus(previousInterestPeriod.getBalanceCorrectionAmount())// .plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc)//
.minus(previousRepaymentPeriod.get().getDuePrincipal())// .minus(previousRepaymentPeriod.get().getDuePrincipal(), mc)//
.plus(previousRepaymentPeriod.get().getPaidPrincipal());// .plus(previousRepaymentPeriod.get().getPaidPrincipal(), mc);//
} }
} 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 = previousInterestPeriod.getOutstandingLoanBalance() // this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance() //
.plus(previousInterestPeriod.getBalanceCorrectionAmount()) // .plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc) //
.plus(previousInterestPeriod.getDisbursementAmount()); // .plus(previousInterestPeriod.getDisbursementAmount(), mc); //
} }
} }
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.data; package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
@@ -38,33 +39,36 @@ public class ProgressiveLoanInterestScheduleModel {
private final List<InterestRate> interestRates; private final List<InterestRate> interestRates;
private final LoanProductRelatedDetail loanProductRelatedDetail; private final LoanProductRelatedDetail loanProductRelatedDetail;
private final Integer installmentAmountInMultiplesOf; private final Integer installmentAmountInMultiplesOf;
private MathContext mc;
public ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail, public ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail,
Integer installmentAmountInMultiplesOf) { Integer installmentAmountInMultiplesOf, MathContext mc) {
this.repaymentPeriods = repaymentPeriods; this.repaymentPeriods = repaymentPeriods;
this.interestRates = new ArrayList<>(); this.interestRates = new ArrayList<>();
this.loanProductRelatedDetail = loanProductRelatedDetail; this.loanProductRelatedDetail = loanProductRelatedDetail;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.mc = mc;
} }
private ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, final List<InterestRate> interestRates, private ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, final List<InterestRate> interestRates,
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf) { LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) {
this.mc = mc;
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods); this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods);
this.interestRates = new ArrayList<>(interestRates); this.interestRates = new ArrayList<>(interestRates);
this.loanProductRelatedDetail = loanProductRelatedDetail; this.loanProductRelatedDetail = loanProductRelatedDetail;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
} }
public ProgressiveLoanInterestScheduleModel deepCopy() { public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) {
return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail, return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail,
installmentAmountInMultiplesOf); installmentAmountInMultiplesOf, mc);
} }
private List<RepaymentPeriod> copyRepaymentPeriods(final List<RepaymentPeriod> repaymentPeriods) { private List<RepaymentPeriod> copyRepaymentPeriods(final List<RepaymentPeriod> repaymentPeriods) {
final List<RepaymentPeriod> repaymentCopies = new ArrayList<>(repaymentPeriods.size()); final List<RepaymentPeriod> repaymentCopies = new ArrayList<>(repaymentPeriods.size());
RepaymentPeriod previousPeriod = null; RepaymentPeriod previousPeriod = null;
for (RepaymentPeriod repaymentPeriod : repaymentPeriods) { for (RepaymentPeriod repaymentPeriod : repaymentPeriods) {
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentPeriod); RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentPeriod, mc);
previousPeriod = currentPeriod; previousPeriod = currentPeriod;
repaymentCopies.add(currentPeriod); repaymentCopies.add(currentPeriod);
} }
@@ -175,11 +179,11 @@ public class ProgressiveLoanInterestScheduleModel {
previousInterestPeriod.addDisbursementAmount(disbursedAmount); previousInterestPeriod.addDisbursementAmount(disbursedAmount);
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount); previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate, final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate,
BigDecimal.ZERO, getZero(), getZero(), getZero()); BigDecimal.ZERO, getZero(mc), getZero(mc), getZero(mc), mc);
repaymentPeriod.getInterestPeriods().add(interestPeriod); repaymentPeriod.getInterestPeriods().add(interestPeriod);
} }
private Money getZero() { private Money getZero(MathContext mc) {
return Money.zero(loanProductRelatedDetail.getCurrency()); return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
} }
} }
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.data; package org.apache.fineract.portfolio.loanaccount.loanschedule.data;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -54,20 +55,23 @@ public class RepaymentPeriod {
private Memo<Money> calculatedDueInterestCalculation; private Memo<Money> calculatedDueInterestCalculation;
private Memo<Money> dueInterestCalculation; private Memo<Money> dueInterestCalculation;
private Memo<Money> outstandingBalanceCalculation; private Memo<Money> outstandingBalanceCalculation;
private final MathContext mc;
public RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi) { public RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc) {
this.previous = previous; this.previous = previous;
this.fromDate = fromDate; this.fromDate = fromDate;
this.dueDate = dueDate; this.dueDate = dueDate;
this.emi = emi; this.emi = emi;
this.mc = mc;
this.interestPeriods = new ArrayList<>(); this.interestPeriods = new ArrayList<>();
// 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
getInterestPeriods().add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, getZero(), getZero(), getZero())); getInterestPeriods()
this.paidInterest = getZero(); .add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, getZero(mc), getZero(mc), getZero(mc), mc));
this.paidPrincipal = getZero(); this.paidInterest = getZero(mc);
this.paidPrincipal = getZero(mc);
} }
public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod) { public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
this.previous = previous; this.previous = previous;
this.fromDate = repaymentPeriod.fromDate; this.fromDate = repaymentPeriod.fromDate;
this.dueDate = repaymentPeriod.dueDate; this.dueDate = repaymentPeriod.dueDate;
@@ -75,9 +79,10 @@ public class RepaymentPeriod {
this.interestPeriods = new ArrayList<>(); this.interestPeriods = new ArrayList<>();
this.paidPrincipal = repaymentPeriod.paidPrincipal; this.paidPrincipal = repaymentPeriod.paidPrincipal;
this.paidInterest = repaymentPeriod.paidInterest; this.paidInterest = repaymentPeriod.paidInterest;
this.mc = mc;
// 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.interestPeriods) {
interestPeriods.add(new InterestPeriod(this, interestPeriod)); interestPeriods.add(new InterestPeriod(this, interestPeriod, mc));
} }
} }
@@ -105,21 +110,21 @@ public class RepaymentPeriod {
} }
private Money calculateCalculatedDueInterest() { private Money calculateCalculatedDueInterest() {
Money calculatedDueInterest = getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(getZero(), Money calculatedDueInterest = getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(getZero(mc),
Money::plus); (m1, m2) -> m1.plus(m2, mc));
if (getPrevious().isPresent()) { if (getPrevious().isPresent()) {
calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest()); calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), mc);
} }
return calculatedDueInterest; return calculatedDueInterest;
} }
private Money getZero() { private Money getZero(MathContext mc) {
// EMI is always initiated // EMI is always initiated
return this.emi.zero(); return this.emi.zero(mc);
} }
public Money getCalculatedDuePrincipal() { public Money getCalculatedDuePrincipal() {
return getEmi().minus(getCalculatedDueInterest()); return getEmi().minus(getCalculatedDueInterest(), mc);
} }
public boolean isFullyPaid() { public boolean isFullyPaid() {
@@ -138,11 +143,11 @@ public 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(getEmi().minus(getDueInterest()), getPaidPrincipal(), false); return MathUtil.max(getEmi().minus(getDueInterest(), mc), getPaidPrincipal(), false);
} }
public Money getUnrecognizedInterest() { public Money getUnrecognizedInterest() {
return getCalculatedDueInterest().minus(getDueInterest()); return getCalculatedDueInterest().minus(getDueInterest(), mc);
} }
public Money getOutstandingLoanBalance() { public Money getOutstandingLoanBalance() {
@@ -150,22 +155,22 @@ public class RepaymentPeriod {
outstandingBalanceCalculation = Memo.of(() -> { outstandingBalanceCalculation = Memo.of(() -> {
InterestPeriod lastInstallmentPeriod = getInterestPeriods().get(getInterestPeriods().size() - 1); InterestPeriod lastInstallmentPeriod = getInterestPeriods().get(getInterestPeriods().size() - 1);
Money calculatedOutStandingLoanBalance = lastInstallmentPeriod.getOutstandingLoanBalance() // Money calculatedOutStandingLoanBalance = lastInstallmentPeriod.getOutstandingLoanBalance() //
.plus(lastInstallmentPeriod.getBalanceCorrectionAmount()) // .plus(lastInstallmentPeriod.getBalanceCorrectionAmount(), mc) //
.plus(lastInstallmentPeriod.getDisbursementAmount()) // .plus(lastInstallmentPeriod.getDisbursementAmount(), mc) //
.minus(getDuePrincipal())// .minus(getDuePrincipal(), mc)//
.plus(getPaidPrincipal());// .plus(getPaidPrincipal(), mc);//
return MathUtil.negativeToZero(calculatedOutStandingLoanBalance); return MathUtil.negativeToZero(calculatedOutStandingLoanBalance, mc);
}, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods }); }, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods });
} }
return outstandingBalanceCalculation.get(); return outstandingBalanceCalculation.get();
} }
public void addPaidPrincipalAmount(Money paidPrincipal) { public void addPaidPrincipalAmount(Money paidPrincipal) {
this.paidPrincipal = MathUtil.plus(this.paidPrincipal, paidPrincipal); this.paidPrincipal = MathUtil.plus(this.paidPrincipal, paidPrincipal, mc);
} }
public void addPaidInterestAmount(Money paidInterest) { public void addPaidInterestAmount(Money paidInterest) {
this.paidInterest = MathUtil.plus(this.paidInterest, paidInterest); this.paidInterest = MathUtil.plus(this.paidInterest, paidInterest, mc);
} }
public Money getInitialBalanceForEmiRecalculation() { public Money getInitialBalanceForEmiRecalculation() {
@@ -173,10 +178,10 @@ public class RepaymentPeriod {
if (getPrevious().isPresent()) { if (getPrevious().isPresent()) {
initialBalance = getPrevious().get().getOutstandingLoanBalance(); initialBalance = getPrevious().get().getOutstandingLoanBalance();
} else { } else {
initialBalance = getZero(); initialBalance = getZero(mc);
} }
Money totalDisbursedAmount = getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount).reduce(getZero(), Money totalDisbursedAmount = getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount).reduce(getZero(mc),
Money::plus); (m1, m2) -> m1.plus(m2, mc));
return initialBalance.add(totalDisbursedAmount); return initialBalance.add(totalDisbursedAmount, mc);
} }
} }
@@ -87,17 +87,17 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
: loanApplicationTerms.getSubmittedOnDate(); : loanApplicationTerms.getSubmittedOnDate();
final LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, final LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, Money.zero(currency)); Money.of(currency, chargesDueAtTimeOfDisbursement, mc), periodStartDate, Money.zero(currency, mc), mc);
// charges which depends on total loan interest will be added to this // charges which depends on total loan interest will be added to this
// set and handled separately after all installments generated // set and handled separately after all installments generated
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges); final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
.generateRepaymentPeriods(periodStartDate, loanApplicationTerms, holidayDetailDTO); periodStartDate, loanApplicationTerms, holidayDetailDTO);
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel(
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(),
loanApplicationTerms.getInstallmentAmountInMultiplesOf()); loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size()); final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms); prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms);
@@ -110,7 +110,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
scheduleParams.setActualRepaymentDate(repaymentPeriod.getDueDate()); scheduleParams.setActualRepaymentDate(repaymentPeriod.getDueDate());
processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods, processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods,
chargesDueAtTimeOfDisbursement, false); chargesDueAtTimeOfDisbursement, false, mc);
repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber()); repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber());
if (loanApplicationTerms.getLoanTermVariations() != null) { if (loanApplicationTerms.getLoanTermVariations() != null) {
@@ -135,7 +135,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
scheduleParams.addTotalCumulativePrincipal(principalDue); scheduleParams.addTotalCumulativePrincipal(principalDue);
scheduleParams.addTotalCumulativeInterest(interestDue); scheduleParams.addTotalCumulativeInterest(interestDue);
// add everything // add everything
scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue)); scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue, mc));
}); });
applyChargesForCurrentPeriod(repaymentPeriod, loanCharges, scheduleParams, currency, mc); applyChargesForCurrentPeriod(repaymentPeriod, loanCharges, scheduleParams, currency, mc);
@@ -147,7 +147,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
if (loanApplicationTerms.isMultiDisburseLoan()) { if (loanApplicationTerms.isMultiDisburseLoan()) {
processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods, processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods,
chargesDueAtTimeOfDisbursement, true); chargesDueAtTimeOfDisbursement, true, mc);
} }
// determine fees and penalties for charges which depends on total // determine fees and penalties for charges which depends on total
@@ -158,8 +158,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
final BigDecimal totalOutstanding = BigDecimal.ZERO; final BigDecimal totalOutstanding = BigDecimal.ZERO;
return LoanScheduleModel.from(periods, applicationCurrency, interestScheduleModel.getLoanTermInDays(), return LoanScheduleModel.from(periods, applicationCurrency, interestScheduleModel.getLoanTermInDays(),
scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()), scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount(), mc),
scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(), scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount(), mc).getAmount(),
totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(),
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding); scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
@@ -167,7 +167,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
public LoanScheduleModel generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) { public LoanScheduleModel generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) {
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(modelData); LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(modelData, mc);
return generate(mc, loanApplicationTerms, null, null); return generate(mc, loanApplicationTerms, null, null);
} }
@@ -184,7 +184,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, private void processDisbursements(final LoanApplicationTerms loanApplicationTerms,
final ArrayList<DisbursementData> disbursementDataList, final LoanScheduleParams scheduleParams, final ArrayList<DisbursementData> disbursementDataList, final LoanScheduleParams scheduleParams,
final ProgressiveLoanInterestScheduleModel interestScheduleModel, final List<LoanScheduleModelPeriod> periods, final ProgressiveLoanInterestScheduleModel interestScheduleModel, final List<LoanScheduleModelPeriod> periods,
final BigDecimal chargesDueAtTimeOfDisbursement, final boolean includeDisbursementsAfterMaturityDate) { final BigDecimal chargesDueAtTimeOfDisbursement, final boolean includeDisbursementsAfterMaturityDate, final MathContext mc) {
for (DisbursementData disbursementData : disbursementDataList) { for (DisbursementData disbursementData : disbursementDataList) {
final LocalDate disbursementDate = disbursementData.disbursementDate(); final LocalDate disbursementDate = disbursementData.disbursementDate();
@@ -202,7 +202,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
Money outstandingBalance = emiCalculator.getOutstandingLoanBalance(interestScheduleModel, periodDueDate, disbursementDate); Money outstandingBalance = emiCalculator.getOutstandingLoanBalance(interestScheduleModel, periodDueDate, disbursementDate);
final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal()); final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(), mc);
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
.disbursement(disbursementData.disbursementDate(), disbursedAmount, chargesDueAtTimeOfDisbursement); .disbursement(disbursementData.disbursementDate(), disbursedAmount, chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod); periods.add(disbursementPeriod);
@@ -218,7 +218,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
} }
} }
Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency()); Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency(), mc);
if (loanApplicationTerms.isDownPaymentEnabled()) { if (loanApplicationTerms.isDownPaymentEnabled()) {
downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), MathUtil.percentageOf(disbursedAmount.getAmount(), downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), MathUtil.percentageOf(disbursedAmount.getAmount(),
loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19));
@@ -321,7 +321,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
final MathContext mc) { final MathContext mc) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency, mc);
if (loanCharges != null) { if (loanCharges != null) {
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
@@ -356,7 +356,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
final MathContext mc) { final MathContext mc) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency, mc);
if (loanCharges != null) { if (loanCharges != null) {
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) { if (loanCharge.isPenaltyCharge()) {
@@ -407,8 +407,8 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) { final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod.isRepaymentPeriod()) { if (loanScheduleModelPeriod.isRepaymentPeriod()) {
PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()), PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue(), mc),
Money.of(currency, loanScheduleModelPeriod.interestDue()), null); Money.of(currency, loanScheduleModelPeriod.interestDue(), mc), null);
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanproduct.calc; package org.apache.fineract.portfolio.loanproduct.calc;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -33,10 +34,10 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
public interface EMICalculator { public interface EMICalculator {
ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(List<LoanScheduleModelRepaymentPeriod> periods, ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(List<LoanScheduleModelRepaymentPeriod> periods,
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf); LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc);
ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail, ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods); Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc);
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate); Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanproduct.calc; package org.apache.fineract.portfolio.loanproduct.calc;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.Year; import java.time.Year;
import java.util.ArrayList; import java.util.ArrayList;
@@ -29,7 +30,6 @@ import lombok.RequiredArgsConstructor;
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.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -40,12 +40,10 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLo
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
@DependsOn("moneyHelper")
public final class ProgressiveEMICalculator implements EMICalculator { public final class ProgressiveEMICalculator implements EMICalculator {
private static final BigDecimal DIVISOR_100 = new BigDecimal("100"); private static final BigDecimal DIVISOR_100 = new BigDecimal("100");
@@ -53,19 +51,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
public ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(final List<LoanScheduleModelRepaymentPeriod> periods, public ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(final List<LoanScheduleModelRepaymentPeriod> periods,
final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf) { final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) {
final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency()); final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency(), mc);
final ArrayList<RepaymentPeriod> interestRepaymentModelList = new ArrayList<>(periods.size()); final ArrayList<RepaymentPeriod> interestRepaymentModelList = new ArrayList<>(periods.size());
RepaymentPeriod previousPeriod = null; RepaymentPeriod previousPeriod = null;
for (final LoanScheduleModelRepaymentPeriod period : periods) { for (final LoanScheduleModelRepaymentPeriod period : periods) {
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, period.periodFromDate(), period.periodDueDate(), RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, period.periodFromDate(), period.periodDueDate(), zeroAmount,
zeroAmount); mc);
previousPeriod = currentPeriod; previousPeriod = currentPeriod;
interestRepaymentModelList.add(currentPeriod); interestRepaymentModelList.add(currentPeriod);
} }
return new ProgressiveLoanInterestScheduleModel(interestRepaymentModelList, loanProductRelatedDetail, return new ProgressiveLoanInterestScheduleModel(interestRepaymentModelList, loanProductRelatedDetail,
installmentAmountInMultiplesOf); installmentAmountInMultiplesOf, mc);
} }
@Override @Override
@@ -85,7 +83,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final Money disbursedAmount) { final Money disbursedAmount) {
scheduleModel scheduleModel
.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, .changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount,
Money.zero(disbursedAmount.getCurrency())) Money.zero(disbursedAmount.getCurrency(), scheduleModel.mc()))
.ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel)); .ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel));
} }
@@ -130,7 +128,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel, public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LocalDate repaymentPeriodDueDate, final LocalDate targetDate) { final LocalDate repaymentPeriodDueDate, final LocalDate targetDate) {
RepaymentPeriod repaymentPeriod = scheduleModel.deepCopy().repaymentPeriods().stream() MathContext mc = scheduleModel.mc();
RepaymentPeriod repaymentPeriod = scheduleModel.deepCopy(mc).repaymentPeriods().stream()
.filter(rp -> rp.getDueDate().equals(repaymentPeriodDueDate)).findFirst().orElseThrow(); .filter(rp -> rp.getDueDate().equals(repaymentPeriodDueDate)).findFirst().orElseThrow();
LocalDate adjustedTargetDate = targetDate; LocalDate adjustedTargetDate = targetDate;
InterestPeriod interestPeriod; InterestPeriod interestPeriod;
@@ -151,19 +150,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
// TODO: gather all the unrecognized interest from previous periods based on target date // TODO: gather all the unrecognized interest from previous periods based on target date
Money payableInterest = targetDate.isBefore(repaymentPeriod.getFromDate()) Money payableInterest = targetDate.isBefore(repaymentPeriod.getFromDate())
? Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()) ? Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc)
: repaymentPeriod.getDueInterest(); : repaymentPeriod.getDueInterest();
Money outstandingLoanBalance = interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount()); Money outstandingLoanBalance = interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount());
Money calculatedEmi = outstandingLoanBalance.plus(payableInterest); Money calculatedEmi = outstandingLoanBalance.plus(payableInterest, mc);
if (calculatedEmi.isLessThan(repaymentPeriod.getEmi())) { if (calculatedEmi.isLessThan(repaymentPeriod.getEmi())) {
// Review this logic // Review this logic
repaymentPeriod.setEmi(outstandingLoanBalance.plus(payableInterest).plus(repaymentPeriod.getPaidInterest()) repaymentPeriod.setEmi(outstandingLoanBalance.plus(payableInterest).plus(repaymentPeriod.getPaidInterest(), mc)
.plus(repaymentPeriod.getPaidPrincipal())); .plus(repaymentPeriod.getPaidPrincipal(), mc));
} }
Money payablePrincipal = repaymentPeriod.getEmi().minus(payableInterest); Money payablePrincipal = repaymentPeriod.getEmi().minus(payableInterest, mc);
return new PayableDetails(repaymentPeriod.getEmi(), payablePrincipal, payableInterest, return new PayableDetails(repaymentPeriod.getEmi(), payablePrincipal, payableInterest,
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount())); interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc));
} }
@Override @Override
@@ -189,18 +188,19 @@ public final class ProgressiveEMICalculator implements EMICalculator {
} }
private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) { private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) {
MathContext mc = scheduleModel.mc();
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest) Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest)
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()), Money::plus); // 1.46 .reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 1.46
Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmi) Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmi)
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()), Money::plus); // 101.48 .reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 101.48
Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream() Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount)) .flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()), Money::plus); // 100 .reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 100
Money diff = totalDisbursedAmount.plus(totalDueInterest).minus(totalEMI); Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()) Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
.reduce((first, second) -> second); .reduce((first, second) -> second);
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff))); findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc)));
} }
private void calculateOutstandingBalance(ProgressiveLoanInterestScheduleModel scheduleModel) { private void calculateOutstandingBalance(ProgressiveLoanInterestScheduleModel scheduleModel) {
@@ -209,24 +209,26 @@ public final class ProgressiveEMICalculator implements EMICalculator {
private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel, private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel,
final List<RepaymentPeriod> relatedRepaymentPeriods) { final List<RepaymentPeriod> relatedRepaymentPeriods) {
MathContext mc = scheduleModel.mc();
final Money emiDifference = getDifferenceBetweenLastTwoPeriod(relatedRepaymentPeriods, scheduleModel); final Money emiDifference = getDifferenceBetweenLastTwoPeriod(relatedRepaymentPeriods, scheduleModel);
final int numberOfRelatedPeriods = relatedRepaymentPeriods.size(); final int numberOfRelatedPeriods = relatedRepaymentPeriods.size();
double lowerHalfOfRelatedPeriods = Math.floor(numberOfRelatedPeriods / 2.0); double lowerHalfOfRelatedPeriods = Math.floor(numberOfRelatedPeriods / 2.0);
if (emiDifference.isZero() || lowerHalfOfRelatedPeriods == 0.0) { if (emiDifference.isZero(mc) || lowerHalfOfRelatedPeriods == 0.0) {
return; return;
} }
final Money originalEmi = relatedRepaymentPeriods.get(numberOfRelatedPeriods - 2).getEmi(); final Money originalEmi = relatedRepaymentPeriods.get(numberOfRelatedPeriods - 2).getEmi();
boolean shouldBeAdjusted = emiDifference.abs().multipliedBy(100) boolean shouldBeAdjusted = emiDifference.abs(mc).multipliedBy(100, mc)
.isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods))); .isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods), mc));
if (shouldBeAdjusted) { if (shouldBeAdjusted) {
Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, MoneyHelper.getMathContext().getRoundingMode()); Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, mc.getRoundingMode(), mc);
Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel, originalEmi.plus(adjustment)); Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel,
originalEmi.plus(adjustment, mc));
if (adjustedEqualMonthlyInstallmentValue.isEqualTo(originalEmi)) { if (adjustedEqualMonthlyInstallmentValue.isEqualTo(originalEmi)) {
return; return;
} }
final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate(); final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate();
final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy(); final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy(mc);
newScheduleModel.repaymentPeriods().forEach(period -> { newScheduleModel.repaymentPeriods().forEach(period -> {
if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)) { if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)) {
period.setEmi(adjustedEqualMonthlyInstallmentValue); period.setEmi(adjustedEqualMonthlyInstallmentValue);
@@ -235,7 +237,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
calculateOutstandingBalance(newScheduleModel); calculateOutstandingBalance(newScheduleModel);
calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel); calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel);
final Money newEmiDifference = getDifferenceBetweenLastTwoPeriod(newScheduleModel.repaymentPeriods(), scheduleModel); final Money newEmiDifference = getDifferenceBetweenLastTwoPeriod(newScheduleModel.repaymentPeriods(), scheduleModel);
final boolean newEmiHasLessDifference = newEmiDifference.abs().isLessThan(emiDifference.abs()); final boolean newEmiHasLessDifference = newEmiDifference.abs(mc).isLessThan(emiDifference.abs(mc));
if (!newEmiHasLessDifference) { if (!newEmiHasLessDifference) {
return; return;
} }
@@ -263,8 +265,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* *
* @return Rate Interest Rate in fraction format * @return Rate Interest Rate in fraction format
*/ */
BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate) { BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate, MathContext mc) {
return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, MoneyHelper.getMathContext()); return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, mc);
} }
/** /**
@@ -286,8 +288,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
*/ */
BigDecimal calculateRateFactorPerPeriod(final RepaymentPeriod repaymentPeriod, final InterestPeriod interestPeriod, BigDecimal calculateRateFactorPerPeriod(final RepaymentPeriod repaymentPeriod, final InterestPeriod interestPeriod,
final ProgressiveLoanInterestScheduleModel scheduleModel) { final ProgressiveLoanInterestScheduleModel scheduleModel) {
final MathContext mc = scheduleModel.mc();
final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriod.getFromDate())); final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriod.getFromDate()),
scheduleModel.mc());
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()); final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType()); final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType());
final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
@@ -306,13 +310,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
// TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30) // TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30)
// TODO refactor this logic to represent in interest period // TODO refactor this logic to represent in interest period
if (partialPeriodCalculationNeeded) { if (partialPeriodCalculationNeeded) {
final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriod); final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriod, mc);
return rateFactorByRepaymentPartialPeriod(interestRate, repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE, return rateFactorByRepaymentPartialPeriod(interestRate, repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE,
BigDecimal.ONE); BigDecimal.ONE, mc);
} }
return calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth, return calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth,
daysInYear, actualDaysInPeriod, calculatedDaysInPeriod); daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
} }
/** /**
@@ -321,7 +325,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* @param interestPeriod * @param interestPeriod
* @return * @return
*/ */
BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod) { BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod, MathContext mc) {
BigDecimal cumulatedRateFactor = BigDecimal.ZERO; BigDecimal cumulatedRateFactor = BigDecimal.ZERO;
int actualYear = interestPeriod.getFromDate().getYear(); int actualYear = interestPeriod.getFromDate().getYear();
int endYear = interestPeriod.getDueDate().getYear(); int endYear = interestPeriod.getDueDate().getYear();
@@ -332,8 +336,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
endOfActualYear = actualYear == endYear ? interestPeriod.getDueDate() : LocalDate.of(actualYear, 12, 31); endOfActualYear = actualYear == endYear ? interestPeriod.getDueDate() : LocalDate.of(actualYear, 12, 31);
BigDecimal numberOfDaysInYear = BigDecimal.valueOf(Year.of(actualYear).length()); BigDecimal numberOfDaysInYear = BigDecimal.valueOf(Year.of(actualYear).length());
BigDecimal calculatedDaysInActualYear = BigDecimal.valueOf(DateUtils.getDifferenceInDays(actualDate, endOfActualYear)); BigDecimal calculatedDaysInActualYear = BigDecimal.valueOf(DateUtils.getDifferenceInDays(actualDate, endOfActualYear));
cumulatedRateFactor = cumulatedRateFactor cumulatedRateFactor = cumulatedRateFactor.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, mc), mc);
.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, MoneyHelper.getMathContext()), MoneyHelper.getMathContext());
actualDate = endOfActualYear; actualDate = endOfActualYear;
actualYear++; actualYear++;
} }
@@ -354,14 +357,15 @@ public final class ProgressiveEMICalculator implements EMICalculator {
*/ */
BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate, BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate,
final PeriodFrequencyType repaymentFrequency, final BigDecimal repaymentEvery, final BigDecimal daysInMonth, final PeriodFrequencyType repaymentFrequency, final BigDecimal repaymentEvery, final BigDecimal daysInMonth,
final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) { final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
final MathContext mc) {
return switch (repaymentFrequency) { return switch (repaymentFrequency) {
case DAYS -> case DAYS ->
rateFactorByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod); rateFactorByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
case WEEKS -> case WEEKS ->
rateFactorByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod); rateFactorByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc);
case MONTHS -> rateFactorByRepaymentEveryMonth(interestRate, repaymentEvery, daysInMonth, daysInYear, actualDaysInPeriod, case MONTHS -> rateFactorByRepaymentEveryMonth(interestRate, repaymentEvery, daysInMonth, daysInYear, actualDaysInPeriod,
calculatedDaysInPeriod); calculatedDaysInPeriod, mc);
default -> throw new UnsupportedOperationException("Invalid repayment frequency"); // not supported yet default -> throw new UnsupportedOperationException("Invalid repayment frequency"); // not supported yet
}; };
} }
@@ -370,14 +374,15 @@ public final class ProgressiveEMICalculator implements EMICalculator {
if (repaymentPeriods.isEmpty()) { if (repaymentPeriods.isEmpty()) {
return; return;
} }
final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods)); final MathContext mc = scheduleModel.mc();
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods)); final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc));
final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc));
final RepaymentPeriod startPeriod = repaymentPeriods.get(0); final RepaymentPeriod startPeriod = repaymentPeriods.get(0);
// TODO: double check // TODO: double check
final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation(); final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation();
final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrency(), final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrency(),
calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult)); calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc);
final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment); final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment);
repaymentPeriods.forEach(period -> period.setEmi(finalEqualMonthlyInstallment)); repaymentPeriods.forEach(period -> period.setEmi(finalEqualMonthlyInstallment));
@@ -392,39 +397,40 @@ public final class ProgressiveEMICalculator implements EMICalculator {
Money getDifferenceBetweenLastTwoPeriod(final List<RepaymentPeriod> repaymentPeriods, Money getDifferenceBetweenLastTwoPeriod(final List<RepaymentPeriod> repaymentPeriods,
final ProgressiveLoanInterestScheduleModel scheduleModel) { final ProgressiveLoanInterestScheduleModel scheduleModel) {
MathContext mc = scheduleModel.mc();
int numberOfUpcomingPeriods = repaymentPeriods.size(); int numberOfUpcomingPeriods = repaymentPeriods.size();
if (numberOfUpcomingPeriods < 2) { if (numberOfUpcomingPeriods < 2) {
return Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()); return Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc);
} }
final RepaymentPeriod lastPeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 1); final RepaymentPeriod lastPeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 1);
final RepaymentPeriod penultimatePeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 2); final RepaymentPeriod penultimatePeriod = repaymentPeriods.get(numberOfUpcomingPeriods - 2);
return lastPeriod.getEmi().minus(penultimatePeriod.getEmi()); return lastPeriod.getEmi().minus(penultimatePeriod.getEmi(), mc);
} }
/** /**
* Calculate Rate Factor Product from rate factors * Calculate Rate Factor Product from rate factors
*/ */
BigDecimal calculateRateFactorPlus1N(final List<RepaymentPeriod> periods) { BigDecimal calculateRateFactorPlus1N(final List<RepaymentPeriod> periods, MathContext mc) {
return periods.stream().map(RepaymentPeriod::getRateFactorPlus1).reduce(BigDecimal.ONE, return periods.stream().map(RepaymentPeriod::getRateFactorPlus1).reduce(BigDecimal.ONE,
(BigDecimal acc, BigDecimal value) -> acc.multiply(value, MoneyHelper.getMathContext())); (BigDecimal acc, BigDecimal value) -> acc.multiply(value, mc));
} }
/** /**
* Summarize Fn values * Summarize Fn values
*/ */
BigDecimal calculateFnResult(final List<RepaymentPeriod> periods) { BigDecimal calculateFnResult(final List<RepaymentPeriod> periods, final MathContext mc) {
return periods.stream()// return periods.stream()//
.skip(1)// .skip(1)//
.map(RepaymentPeriod::getRateFactorPlus1)// .map(RepaymentPeriod::getRateFactorPlus1)//
.reduce(BigDecimal.ONE, this::fnValue);// .reduce(BigDecimal.ONE, (previousFnValue, currentRateFactor) -> fnValue(previousFnValue, currentRateFactor, mc));//
} }
/** /**
* Calculate the EMI (Equal Monthly Installment) value * Calculate the EMI (Equal Monthly Installment) value
*/ */
BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult) { BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult,
return rateFactorPlus1N.multiply(outstandingBalanceForRest, MoneyHelper.getMathContext()).divide(fnResult, MathContext mc) {
MoneyHelper.getMathContext()); return rateFactorPlus1N.multiply(outstandingBalanceForRest, mc).divide(fnResult, mc);
} }
/** /**
@@ -452,9 +458,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* @return Rate Factor for period * @return Rate Factor for period
*/ */
BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear, BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear,
final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) { final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) {
return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, daysInYear, actualDaysInPeriod, return rateFactorByRepaymentPeriod(interestRate, BigDecimal.ONE, repaymentEvery, daysInYear, actualDaysInPeriod,
calculatedDaysInPeriod); calculatedDaysInPeriod, mc);
} }
/** /**
@@ -482,9 +488,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* @return Rate Factor for period * @return Rate Factor for period
*/ */
BigDecimal rateFactorByRepaymentEveryWeek(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear, BigDecimal rateFactorByRepaymentEveryWeek(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInYear,
final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) { final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod, MathContext mc) {
return rateFactorByRepaymentPeriod(interestRate, ONE_WEEK_IN_DAYS, repaymentEvery, daysInYear, actualDaysInPeriod, return rateFactorByRepaymentPeriod(interestRate, ONE_WEEK_IN_DAYS, repaymentEvery, daysInYear, actualDaysInPeriod,
calculatedDaysInPeriod); calculatedDaysInPeriod, mc);
} }
/** /**
@@ -515,9 +521,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* @return Rate Factor for period * @return Rate Factor for period
*/ */
BigDecimal rateFactorByRepaymentEveryMonth(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInMonth, BigDecimal rateFactorByRepaymentEveryMonth(final BigDecimal interestRate, final BigDecimal repaymentEvery, final BigDecimal daysInMonth,
final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) { final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
final MathContext mc) {
return rateFactorByRepaymentPeriod(interestRate, daysInMonth, repaymentEvery, daysInYear, actualDaysInPeriod, return rateFactorByRepaymentPeriod(interestRate, daysInMonth, repaymentEvery, daysInYear, actualDaysInPeriod,
calculatedDaysInPeriod); calculatedDaysInPeriod, mc);
} }
/** /**
@@ -549,14 +556,14 @@ public final class ProgressiveEMICalculator implements EMICalculator {
*/ */
BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays, BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays,
final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod,
final BigDecimal calculatedDaysInPeriod) { final BigDecimal calculatedDaysInPeriod, final MathContext mc) {
final BigDecimal interestFractionPerPeriod = repaymentPeriodMultiplierInDays// final BigDecimal interestFractionPerPeriod = repaymentPeriodMultiplierInDays//
.multiply(repaymentEvery, MoneyHelper.getMathContext())// .multiply(repaymentEvery, mc)//
.divide(daysInYear, MoneyHelper.getMathContext());// .divide(daysInYear, mc);//
return interestRate// return interestRate//
.multiply(interestFractionPerPeriod, MoneyHelper.getMathContext())// .multiply(interestFractionPerPeriod, mc)//
.multiply(actualDaysInPeriod, MoneyHelper.getMathContext())// .multiply(actualDaysInPeriod, mc)//
.divide(calculatedDaysInPeriod, MoneyHelper.getMathContext());// .divide(calculatedDaysInPeriod, mc);//
} }
/** /**
@@ -564,12 +571,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* *
*/ */
BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery, BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, final BigDecimal repaymentEvery,
final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod) { final BigDecimal cumulatedPeriodRatio, final BigDecimal actualDaysInPeriod, final BigDecimal calculatedDaysInPeriod,
final MathContext mc) {
final BigDecimal interestFractionPerPeriod = repaymentEvery.multiply(cumulatedPeriodRatio); final BigDecimal interestFractionPerPeriod = repaymentEvery.multiply(cumulatedPeriodRatio);
return interestRate// return interestRate//
.multiply(interestFractionPerPeriod, MoneyHelper.getMathContext())// .multiply(interestFractionPerPeriod, mc)//
.multiply(actualDaysInPeriod, MoneyHelper.getMathContext())// .multiply(actualDaysInPeriod, mc)//
.divide(calculatedDaysInPeriod, MoneyHelper.getMathContext());// .divide(calculatedDaysInPeriod, mc);//
} }
/** /**
@@ -582,13 +590,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
* @param currentRateFactor * @param currentRateFactor
* *
*/ */
BigDecimal fnValue(final BigDecimal previousFnValue, final BigDecimal currentRateFactor) { BigDecimal fnValue(final BigDecimal previousFnValue, final BigDecimal currentRateFactor, final MathContext mc) {
return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, MoneyHelper.getMathContext()), MoneyHelper.getMathContext()); return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, mc), mc);
} }
@Override @Override
public ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail, public ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail,
Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods) { Integer installmentAmountInMultiplesOf, List<LoanRepaymentScheduleInstallment> repaymentPeriods, MathContext mc) {
List<LoanRepaymentScheduleInstallment> repaymentModelsWithoutDownPayment = repaymentPeriods.stream() List<LoanRepaymentScheduleInstallment> repaymentModelsWithoutDownPayment = repaymentPeriods.stream()
.filter(period -> !period.isDownPayment() && !period.isAdditional()).toList(); .filter(period -> !period.isDownPayment() && !period.isAdditional()).toList();
@@ -596,11 +604,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
RepaymentPeriod previousPeriod = null; RepaymentPeriod previousPeriod = null;
for (LoanRepaymentScheduleInstallment repaymentModel : repaymentModelsWithoutDownPayment) { for (LoanRepaymentScheduleInstallment repaymentModel : repaymentModelsWithoutDownPayment) {
RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentModel.getFromDate(), repaymentModel.getDueDate(), RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentModel.getFromDate(), repaymentModel.getDueDate(),
Money.zero(repaymentModel.getLoan().getCurrency())); Money.zero(repaymentModel.getLoan().getCurrency(), mc), mc);
previousPeriod = currentPeriod; previousPeriod = currentPeriod;
repaymentModels.add(currentPeriod); repaymentModels.add(currentPeriod);
} }
return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf); return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
} }
} }
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
@@ -90,11 +91,12 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1); private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1);
private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class); private static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
private AdvancedPaymentScheduleTransactionProcessor underTest; private AdvancedPaymentScheduleTransactionProcessor underTest;
private final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class); private static final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
@BeforeAll @BeforeAll
public static void init() { public static void init() {
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
} }
@AfterAll @AfterAll
@@ -20,77 +20,36 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator; import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class LoanScheduleGeneratorTest { class LoanScheduleGeneratorTest {
private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(); private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator();
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$"); private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$");
private static final MonetaryCurrency MONETARY_CURRENCY = MonetaryCurrency.fromApplicationCurrency(APPLICATION_CURRENCY);
private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22); private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22);
private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99); private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99);
private static final int NUMBER_OF_REPAYMENTS = 6; private static final int NUMBER_OF_REPAYMENTS = 6;
private static final int REPAYMENT_FREQUENCY = 1; private static final int REPAYMENT_FREQUENCY = 1;
private static final String REPAYMENT_FREQUENCY_TYPE = "MONTHS"; private static final String REPAYMENT_FREQUENCY_TYPE = "MONTHS";
private static final LocalDate DISBURSEMENT_DATE = LocalDate.of(2024, 1, 15); private static final LocalDate DISBURSEMENT_DATE = LocalDate.of(2024, 1, 15);
private static final MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
@BeforeAll
public static void init() {
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
}
@AfterAll
public static void destruct() {
moneyHelper.close();
}
@Test @Test
void testGenerateLoanSchedule() { void testGenerateLoanSchedule() {
LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY, List<LoanScheduleModelPeriod> periods = getLoanScheduleModelPeriods();
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);
final MathContext mc = MoneyHelper.getMathContext();
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = new ArrayList<>();
expectedRepaymentPeriods.add(repayment(1, LocalDate.of(2024, 1, 1), LocalDate.of(2024, 2, 1)));
expectedRepaymentPeriods.add(repayment(2, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 3, 1)));
expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1)));
expectedRepaymentPeriods.add(repayment(5, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 6, 1)));
expectedRepaymentPeriods.add(repayment(6, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 7, 1)));
ScheduledDateGenerator mockScheduledDateGenerator = Mockito.mock(ScheduledDateGenerator.class);
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(mockScheduledDateGenerator, emiCalculator);
when(mockScheduledDateGenerator.generateRepaymentPeriods(any(), any(), any())).thenReturn(expectedRepaymentPeriods);
LoanScheduleModel loanSchedule = generator.generate(mc, modelData);
List<LoanScheduleModelPeriod> periods = loanSchedule.getPeriods();
assertEquals(7, periods.size(), "Expected 7 periods including the downpayment period."); assertEquals(7, periods.size(), "Expected 7 periods including the downpayment period.");
@@ -111,6 +70,18 @@ class LoanScheduleGeneratorTest {
BigDecimal.valueOf(0.27), BigDecimal.valueOf(32.87), BigDecimal.ZERO); BigDecimal.valueOf(0.27), BigDecimal.valueOf(32.87), BigDecimal.ZERO);
} }
private static List<LoanScheduleModelPeriod> 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);
ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator);
LoanScheduleModel loanSchedule = generator.generate(mc, modelData);
return loanSchedule.getPeriods();
}
private void checkPeriod(LoanScheduleModelPeriod period, int expectedPeriodNumber, LocalDate expectedFromDate, private void checkPeriod(LoanScheduleModelPeriod period, int expectedPeriodNumber, LocalDate expectedFromDate,
LocalDate expectedDueDate, BigDecimal expectedPrincipalDue, BigDecimal expectedInterestDue, BigDecimal expectedTotalDue, LocalDate expectedDueDate, BigDecimal expectedPrincipalDue, BigDecimal expectedInterestDue, BigDecimal expectedTotalDue,
BigDecimal expectedOutstandingLoanBalance) { BigDecimal expectedOutstandingLoanBalance) {
@@ -123,10 +94,4 @@ class LoanScheduleGeneratorTest {
assertEquals(0, expectedTotalDue.compareTo(repaymentPeriod.getTotalDue().getAmount())); assertEquals(0, expectedTotalDue.compareTo(repaymentPeriod.getTotalDue().getAmount()));
assertEquals(0, expectedOutstandingLoanBalance.compareTo(repaymentPeriod.getOutstandingLoanBalance().getAmount())); assertEquals(0, expectedOutstandingLoanBalance.compareTo(repaymentPeriod.getOutstandingLoanBalance().getAmount()));
} }
private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {
final Money zeroAmount = Money.zero(MONETARY_CURRENCY);
return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
zeroAmount, zeroAmount, false);
}
} }
@@ -59,6 +59,7 @@ class ProgressiveEMICalculatorTest {
private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class); private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class);
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class); private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
private static MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
private static LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class); private static LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
private static final MonetaryCurrency monetaryCurrency = MonetaryCurrency private static final MonetaryCurrency monetaryCurrency = MonetaryCurrency
@@ -95,7 +96,7 @@ class ProgressiveEMICalculatorTest {
final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(period.getFromDate())); final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(period.getFromDate()));
final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(period.getFromDate())); final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(period.getFromDate()));
final BigDecimal rateFactor = emiCalculator.rateFactorByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear, final BigDecimal rateFactor = emiCalculator.rateFactorByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear,
daysInPeriod, daysInPeriod); daysInPeriod, daysInPeriod, mc);
return rateFactor.setScale(12, MoneyHelper.getRoundingMode()); return rateFactor.setScale(12, MoneyHelper.getRoundingMode());
} }
@@ -145,7 +146,7 @@ class ProgressiveEMICalculatorTest {
BigDecimal rateFactorPlus1 = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period).add(BigDecimal.ONE, BigDecimal rateFactorPlus1 = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period).add(BigDecimal.ONE,
MoneyHelper.getMathContext()); MoneyHelper.getMathContext());
final BigDecimal currentFnValue = emiCalculator.fnValue(previousFnValue, rateFactorPlus1); final BigDecimal currentFnValue = emiCalculator.fnValue(previousFnValue, rateFactorPlus1, mc);
fnValuesCalculated.add(currentFnValue); fnValuesCalculated.add(currentFnValue);
previousFnValue = currentFnValue; previousFnValue = currentFnValue;
@@ -171,7 +172,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator
.generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf); .generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
Assertions.assertTrue(interestScheduleModel != null); Assertions.assertTrue(interestScheduleModel != null);
Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null); Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null);
@@ -211,7 +212,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -256,7 +257,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -293,7 +294,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -330,7 +331,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -370,7 +371,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -414,7 +415,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -460,7 +461,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -577,7 +578,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -661,7 +662,7 @@ class ProgressiveEMICalculatorTest {
threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = toMoney(100.0); final Money disbursedAmount = toMoney(100.0);
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -739,7 +740,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -788,7 +789,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -837,7 +838,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount);
@@ -881,7 +882,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount);
@@ -915,7 +916,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(1000)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(1000));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -948,7 +949,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -981,7 +982,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1014,7 +1015,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods,
loanProductRelatedDetail, installmentAmountInMultiplesOf); loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100));
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount);
@@ -1031,7 +1032,7 @@ class ProgressiveEMICalculatorTest {
private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) { private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {
final Money zeroAmount = Money.zero(monetaryCurrency); final Money zeroAmount = Money.zero(monetaryCurrency);
return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount, return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
zeroAmount, zeroAmount, false); zeroAmount, zeroAmount, false, mc);
} }
@NotNull @NotNull
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@@ -88,6 +89,7 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
@BeforeAll @BeforeAll
public static void init() { public static void init() {
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
} }
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.when;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@@ -116,8 +117,12 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
@Mock @Mock
private AvroDateTimeMapper mapper; private AvroDateTimeMapper mapper;
private MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
ThreadLocalContextUtil ThreadLocalContextUtil
@@ -127,6 +132,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
@AfterEach @AfterEach
public void tearDown() { public void tearDown() {
ThreadLocalContextUtil.reset(); ThreadLocalContextUtil.reset();
moneyHelper.close();
} }
@Test @Test
@@ -141,7 +147,6 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
LoanAccountData loanAccountData = mock(LoanAccountData.class); LoanAccountData loanAccountData = mock(LoanAccountData.class);
CollectionData delinquentData = mock(CollectionData.class); CollectionData delinquentData = mock(CollectionData.class);
MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1); MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1);
MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
String delinquentDateAsStr = "2022-12-01"; String delinquentDateAsStr = "2022-12-01";
LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr); LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr);
@@ -168,8 +173,6 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
when(loanChargeReadPlatformService.retrieveLoanCharges(anyLong())).thenAnswer(a -> repaymentScheduleInstallments.get(0) when(loanChargeReadPlatformService.retrieveLoanCharges(anyLong())).thenAnswer(a -> repaymentScheduleInstallments.get(0)
.getInstallmentCharges().stream().map(c -> c.getLoanCharge().toData()).collect(Collectors.toList())); .getInstallmentCharges().stream().map(c -> c.getLoanCharge().toData()).collect(Collectors.toList()));
moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP);
// when // when
LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event); LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
@@ -190,8 +193,6 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
assertEquals(0, data.getAmount().getFeeAmount().compareTo(new BigDecimal("5.0"))); assertEquals(0, data.getAmount().getFeeAmount().compareTo(new BigDecimal("5.0")));
assertEquals(0, data.getAmount().getPenaltyAmount().compareTo(new BigDecimal("50.0"))); assertEquals(0, data.getAmount().getPenaltyAmount().compareTo(new BigDecimal("50.0")));
assertEquals(delinquentDateAsStr, data.getDelinquentDate()); assertEquals(delinquentDateAsStr, data.getDelinquentDate());
moneyHelper.close();
} }
@Test @Test
@@ -206,7 +207,6 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
LoanAccountData loanAccountData = mock(LoanAccountData.class); LoanAccountData loanAccountData = mock(LoanAccountData.class);
CollectionData delinquentData = mock(CollectionData.class); CollectionData delinquentData = mock(CollectionData.class);
MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1); MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1);
MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
String delinquentDateAsStr = "2022-12-01"; String delinquentDateAsStr = "2022-12-01";
LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr); LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr);
when(loanForProcessing.getId()).thenReturn(1L); when(loanForProcessing.getId()).thenReturn(1L);
@@ -263,8 +263,6 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
when(loanForProcessing.getLoanCharges()).thenAnswer(a -> repaymentScheduleInstallments.get(0).getInstallmentCharges().stream() when(loanForProcessing.getLoanCharges()).thenAnswer(a -> repaymentScheduleInstallments.get(0).getInstallmentCharges().stream()
.map(c -> c.getLoanCharge()).collect(Collectors.toList())); .map(c -> c.getLoanCharge()).collect(Collectors.toList()));
moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP);
// when // when
LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event); LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
@@ -314,7 +312,6 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
assertEquals(0, installmentDelinquencyBucketDataV1_2.getAmount().getFeeAmount().compareTo(new BigDecimal("0.0"))); assertEquals(0, installmentDelinquencyBucketDataV1_2.getAmount().getFeeAmount().compareTo(new BigDecimal("0.0")));
assertEquals(0, installmentDelinquencyBucketDataV1_2.getAmount().getPenaltyAmount().compareTo(new BigDecimal("0.0"))); assertEquals(0, installmentDelinquencyBucketDataV1_2.getAmount().getPenaltyAmount().compareTo(new BigDecimal("0.0")));
assertEquals(0, installmentDelinquencyBucketDataV1_2.getCharges().size()); assertEquals(0, installmentDelinquencyBucketDataV1_2.getCharges().size());
moneyHelper.close();
} }
@Test @Test
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@@ -85,7 +86,8 @@ public class LoanRepaymentBusinessEventSerializerTest {
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
ThreadLocalContextUtil ThreadLocalContextUtil
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
} }
@AfterEach @AfterEach
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@@ -104,7 +105,8 @@ public class LoanDelinquencyDomainServiceTest {
currency = new MonetaryCurrency("USD", 2, null); currency = new MonetaryCurrency("USD", 2, null);
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class); moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
moneyHelperStatic.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); moneyHelperStatic.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelperStatic.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
} }
@AfterEach @AfterEach
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
@@ -55,7 +56,8 @@ class DefaultLoanLifecycleStateMachineTest {
public void setUp() { public void setUp() {
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class); moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
moneyHelperStatic.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); moneyHelperStatic.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelperStatic.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService); underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService);
} }
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.ArgumentMatchers.refEq;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
@@ -87,6 +88,7 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
@BeforeAll @BeforeAll
public static void init() { public static void init() {
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
} }
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.ArgumentMatchers.refEq;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
@@ -88,6 +89,7 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
@BeforeAll @BeforeAll
public static void init() { public static void init() {
MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN));
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
} }
@@ -37,6 +37,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -82,6 +84,7 @@ public class DefaultScheduledDateGeneratorTest {
public void test_generateRepaymentPeriods() { public void test_generateRepaymentPeriods() {
// given // given
HolidayDetailDTO holidayDetailDTO = createHolidayDTO(); HolidayDetailDTO holidayDetailDTO = createHolidayDTO();
MathContext mathContext = new MathContext(12, RoundingMode.HALF_EVEN);
ApplicationCurrency dollarCurrency = new ApplicationCurrency("USD", "US Dollar", 2, 0, "currency.USD", "$"); ApplicationCurrency dollarCurrency = new ApplicationCurrency("USD", "US Dollar", 2, 0, "currency.USD", "$");
Money principalAmount = Money.of(fromApplicationCurrency(dollarCurrency), BigDecimal.valueOf(100)); Money principalAmount = Money.of(fromApplicationCurrency(dollarCurrency), BigDecimal.valueOf(100));
@@ -98,8 +101,8 @@ public class DefaultScheduledDateGeneratorTest {
submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null); submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null);
// when // when
List<? extends LoanScheduleModelPeriod> result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms, List<? extends LoanScheduleModelPeriod> result = underTest.generateRepaymentPeriods(mathContext, expectedDisbursementDate,
holidayDetailDTO); loanApplicationTerms, holidayDetailDTO);
// then // then
assertThat(result.size()).isEqualTo(4); assertThat(result.size()).isEqualTo(4);
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@@ -64,7 +65,8 @@ public class LoanCalculateRepaymentPastDueServiceTest {
ThreadLocalContextUtil ThreadLocalContextUtil
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
underTest = new LoanCalculateRepaymentPastDueService(); underTest = new LoanCalculateRepaymentPastDueService();
moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
} }
@AfterEach @AfterEach
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
@@ -75,7 +76,8 @@ public class LoanDownPaymentHandlerServiceImplTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService); underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService);
moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
} }
@AfterEach @AfterEach