From a9ecdf97a4ccb94ae50bad6dc7623f3d06ba0f0e Mon Sep 17 00:00:00 2001 From: Andrii Kulminskyi Date: Mon, 21 Oct 2024 13:48:03 +0300 Subject: [PATCH] FINERACT-1981: Remove hardcoded MoneyHelper dependency during progressive loan --- .../infrastructure/core/service/MathUtil.java | 17 ++ .../organisation/monetary/domain/Money.java | 177 +++++++++++++++--- .../monetary/domain/MoneyHelper.java | 1 + fineract-loan/dependencies.gradle | 5 + .../loanschedule/data/LoanScheduleParams.java | 72 +++---- ...stractCumulativeLoanScheduleGenerator.java | 26 +-- .../domain/DefaultScheduledDateGenerator.java | 61 +++--- .../domain/LoanApplicationTerms.java | 41 ++-- .../LoanScheduleModelRepaymentPeriod.java | 24 ++- .../domain/ScheduledDateGenerator.java | 5 +- ...epaymentScheduleProcessingWrapperTest.java | 7 +- ...edPaymentScheduleTransactionProcessor.java | 2 +- .../loanschedule/data/InterestPeriod.java | 25 +-- .../ProgressiveLoanInterestScheduleModel.java | 20 +- .../loanschedule/data/RepaymentPeriod.java | 55 +++--- .../ProgressiveLoanScheduleGenerator.java | 34 ++-- .../loanproduct/calc/EMICalculator.java | 5 +- .../calc/ProgressiveEMICalculator.java | 156 +++++++-------- ...ymentScheduleTransactionProcessorTest.java | 4 +- .../domain/LoanScheduleGeneratorTest.java | 63 ++----- .../calc/ProgressiveEMICalculatorTest.java | 43 ++--- ...kLoanRepaymentOverdueBusinessStepTest.java | 2 + ...ntDelinquencyRangeEventSerializerTest.java | 15 +- ...nRepaymentBusinessEventSerializerTest.java | 4 +- .../LoanDelinquencyDomainServiceTest.java | 4 +- .../DefaultLoanLifecycleStateMachineTest.java | 4 +- ...ymentScheduleTransactionProcessorTest.java | 2 + ...ymentScheduleTransactionProcessorTest.java | 2 + .../DefaultScheduledDateGeneratorTest.java | 7 +- ...nCalculateRepaymentPastDueServiceTest.java | 4 +- ...LoanDownPaymentHandlerServiceImplTest.java | 4 +- 31 files changed, 536 insertions(+), 355 deletions(-) rename {fineract-provider => fineract-loan}/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java (90%) diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java index 406f16c0856..2e0779ef999 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/MathUtil.java @@ -359,6 +359,11 @@ public static Money negativeToZero(Money value) { 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) { return value == null || value.isZero(); } @@ -367,6 +372,10 @@ public static boolean isGreaterThanZero(Money value) { 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) { return value != null && value.isLessThanZero(); } @@ -387,10 +396,18 @@ public static Money plus(Money first, Money 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) { 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) { return plus(plus(plus(first, second), third), fourth); } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java index 9e2d51af9f2..8ec1a84fcae 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/Money.java @@ -21,11 +21,14 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.util.Iterator; +import lombok.Getter; import org.apache.fineract.organisation.monetary.data.CurrencyData; @Embeddable +@Getter public class Money implements Comparable { @Column(name = "currency_code", length = 3) @@ -40,6 +43,16 @@ public class Money implements Comparable { @Column(name = "amount", scale = 6, precision = 19) 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) { if (monies.length == 0) { throw new IllegalArgumentException("Money array must not be empty"); @@ -53,7 +66,7 @@ public static Money total(final Money... monies) { public static Money total(final Iterable monies) { final Iterator it = monies.iterator(); - if (it.hasNext() == false) { + if (!it.hasNext()) { throw new IllegalArgumentException("Money iterator must not be empty"); } Money total = it.next(); @@ -63,34 +76,47 @@ public static Money total(final Iterable monies) { 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) { return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount), - currency.getCurrencyInMultiplesOf()); + currency.getCurrencyInMultiplesOf(), null); } 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) { - 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 CurrencyData currency) { - return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf()); + public static Money zero(final MonetaryCurrency currency, MathContext mc) { + return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf(), mc); } - protected Money() { - this.currencyCode = null; - this.currencyDigitsAfterDecimal = 0; - this.inMultiplesOf = 0; - this.amount = null; + 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) { + return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf(), null); } - private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf) { + private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf, + final MathContext mc) { this.currencyCode = currencyCode; this.currencyDigitsAfterDecimal = digitsAfterDecimal; this.inMultiplesOf = inMultiplesOf; + this.mc = mc; final BigDecimal amountZeroed = defaultToZeroIfNull(amount); final BigDecimal amountStripped = amountZeroed.stripTrailingZeros(); @@ -101,7 +127,7 @@ private Money(final String currencyCode, final int digitsAfterDecimal, final Big final double existingVal = amountScaled.doubleValue(); 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) { @@ -130,10 +156,14 @@ public static BigDecimal roundToMultiplesOf(final BigDecimal existingVal, final } 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 inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue()); 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); } @@ -171,7 +201,7 @@ private static BigDecimal defaultToZeroIfNull(final BigDecimal value) { } 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 moniesToAdd) { @@ -188,6 +218,11 @@ public Money plus(final Money moneyToAdd) { 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) { if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { return this; @@ -196,6 +231,14 @@ public Money plus(final BigDecimal amountToAdd) { 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) { if (amountToAdd == 0) { return this; @@ -209,11 +252,21 @@ public Money minus(final Money moneyToSubtract) { 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) { final Money toAdd = checkCurrencyEqual(moneyToAdd); 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) { if (amountToAdd == null || amountToAdd.compareTo(BigDecimal.ZERO) == 0) { return this; @@ -222,6 +275,14 @@ public Money add(final BigDecimal amountToAdd) { 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) { if (amountToSubtract == null || amountToSubtract.compareTo(BigDecimal.ZERO) == 0) { return this; @@ -230,8 +291,16 @@ public Money minus(final BigDecimal amountToSubtract) { 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) { - if (isSameCurrency(money) == false) { + if (!isSameCurrency(money)) { throw new UnsupportedOperationException("currencies are different."); } return money; @@ -249,6 +318,14 @@ public Money dividedBy(final BigDecimal valueToDivideBy, final RoundingMode roun 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) { if (valueToDivideBy == 1) { return this; @@ -265,6 +342,14 @@ public Money dividedBy(final long valueToDivideBy, final RoundingMode roundingMo 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) { if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { return this; @@ -273,6 +358,14 @@ public Money multipliedBy(final BigDecimal valueToMultiplyBy) { 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) { if (valueToMultiplyBy == 1) { return this; @@ -289,6 +382,14 @@ public Money multipliedBy(final long valueToMultiplyBy) { 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) { if (valueToMultiplyBy.compareTo(BigDecimal.ONE) == 0) { return this; @@ -309,17 +410,20 @@ public Money percentageOf(BigDecimal percentage, final RoundingMode roundingMode @Override public int compareTo(final Money other) { - final Money otherMoney = other; - if (this.currencyCode.equals(otherMoney.currencyCode) == false) { + if (!this.currencyCode.equals(other.currencyCode)) { throw new UnsupportedOperationException("currencies arent different"); } - return this.amount.compareTo(otherMoney.amount); + return this.amount.compareTo(other.amount); } public boolean isZero() { return isEqualTo(Money.zero(getCurrency())); } + public boolean isZero(final MathContext mc) { + return isEqualTo(Money.zero(getCurrency(), mc)); + } + public boolean isEqualTo(final Money other) { return compareTo(other) == 0; } @@ -340,6 +444,10 @@ public boolean isGreaterThanZero() { return isGreaterThan(Money.zero(getCurrency())); } + public boolean isGreaterThanZero(MathContext mc) { + return isGreaterThan(Money.zero(getCurrency(), mc)); + } + public boolean isLessThan(final Money other) { return compareTo(other) < 0; } @@ -348,22 +456,14 @@ public boolean isLessThanZero() { return isLessThan(Money.zero(getCurrency())); } - public String getCurrencyCode() { - return this.currencyCode; - } - - public int getCurrencyDigitsAfterDecimal() { - return this.currencyDigitsAfterDecimal; + public boolean isLessThanZero(final MathContext mc) { + return isLessThan(Money.zero(getCurrency(), mc)); } public Integer getCurrencyInMultiplesOf() { return this.inMultiplesOf; } - public BigDecimal getAmount() { - return this.amount; - } - public BigDecimal getAmountDefaultedToNullIfZero() { return defaultToNullIfZero(this.amount); } @@ -388,10 +488,21 @@ public Money negated() { 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() { return isLessThanZero() ? negated() : this; } + public Money abs(MathContext mc) { + return isLessThanZero(mc) ? negated(mc) : this; + } + public MonetaryCurrency getCurrency() { return monetaryCurrency(); } @@ -403,4 +514,12 @@ private MonetaryCurrency monetaryCurrency() { public Money zero() { return Money.zero(getCurrency()); } + + public Money zero(MathContext mc) { + return Money.zero(getCurrency(), mc); + } + + public MathContext getMc() { + return mc != null ? mc : MoneyHelper.getMathContext(); + } } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java index d3cefb27d2c..1a99e73117e 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MoneyHelper.java @@ -65,6 +65,7 @@ public static MathContext getMathContext() { public static void fetchRoundingModeFromGlobalConfig() { roundingMode = RoundingMode.valueOf(staticConfigurationDomainService.getRoundingMode()); log.info("Fetch Rounding Mode from Global Config {}", roundingMode.name()); + mathContext = null; } } diff --git a/fineract-loan/dependencies.gradle b/fineract-loan/dependencies.gradle index 2122febb58a..5d439e7088c 100644 --- a/fineract-loan/dependencies.gradle +++ b/fineract-loan/dependencies.gradle @@ -59,6 +59,11 @@ dependencies { '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' annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.mapstruct:mapstruct-processor' diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java index f73f67b19b9..635b62bd704 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.data; +import java.math.MathContext; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; @@ -98,6 +99,7 @@ public void setCompoundedInLastInstallment(Money compoundedInLastInstallment) { private int loanTermInDays; private final MonetaryCurrency currency; private final boolean applyInterestRecalculation; + private final MathContext mc; private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, @@ -108,7 +110,7 @@ private LoanScheduleParams(final int periodNumber, final int instalmentNumber, i final Money outstandingBalanceAsPerRest, final List installments, final Collection recalculationDetails, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, - final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) { + final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) { this.periodNumber = periodNumber; this.instalmentNumber = instalmentNumber; this.loanTermInDays = loanTermInDays; @@ -136,8 +138,9 @@ private LoanScheduleParams(final int periodNumber, final int instalmentNumber, i this.partialUpdate = partialUpdate; this.currency = currency; this.applyInterestRecalculation = applyInterestRecalculation; + this.mc = mc; if (this.currency != null) { - this.compoundedInLastInstallment = Money.zero(this.currency); + this.compoundedInLastInstallment = Money.zero(this.currency, mc); } } @@ -150,19 +153,19 @@ public static LoanScheduleParams createLoanScheduleParamsForPartialUpdate(final final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, final List installments, final Collection recalculationDetails, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, - final MonetaryCurrency currency, final boolean applyInterestRecalculation) { + final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) { final boolean partialUpdate = true; return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, - scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); } public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection recalculationDetails, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, - final boolean applyInterestRecalculation) { + final boolean applyInterestRecalculation, MathContext mc) { final int periodNumber = 1; final int instalmentNumber = 1; final LocalDate periodStartDate = null; @@ -191,22 +194,22 @@ public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, - scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); } public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, - final LocalDate periodStartDate, final Money principalToBeScheduled) { + final LocalDate periodStartDate, final Money principalToBeScheduled, final MathContext mc) { final int loanTermInDays = 0; final int periodNumber = 1; final int instalmentNumber = 1; - final Money totalCumulativePrincipal = Money.zero(currency); - final Money totalCumulativeInterest = Money.zero(currency); - final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); + final Money totalCumulativePrincipal = Money.zero(currency, mc); + final Money totalCumulativeInterest = Money.zero(currency, mc); + final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency, mc); final LocalDate actualRepaymentDate = periodStartDate; final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement; - final Money totalPenaltyChargesCharged = Money.zero(currency); + final Money totalPenaltyChargesCharged = Money.zero(currency, mc); final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement; - final Money reducePrincipal = Money.zero(currency); + final Money reducePrincipal = Money.zero(currency, mc); final Map principalPortionMap = new HashMap<>(); final Map latePaymentMap = new HashMap<>(); final Map compoundingMap = new TreeMap<>(); @@ -218,27 +221,28 @@ public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency final Collection recalculationDetails = null; final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null; final LocalDate scheduleTillDate = null; - final Money unCompoundedAmount = Money.zero(currency); + final Money unCompoundedAmount = Money.zero(currency, mc); final boolean applyInterestRecalculation = false; return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, - scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); } public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, - final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams) { + final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams, + MathContext mc) { final int loanTermInDays = 0; final int periodNumber = 1; final int instalmentNumber = 1; - final Money totalCumulativePrincipal = Money.zero(currency); - final Money totalCumulativeInterest = Money.zero(currency); - final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); + final Money totalCumulativePrincipal = Money.zero(currency, mc); + final Money totalCumulativeInterest = Money.zero(currency, mc); + final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency, mc); final LocalDate actualRepaymentDate = periodStartDate; final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement; - final Money totalPenaltyChargesCharged = Money.zero(currency); + final Money totalPenaltyChargesCharged = Money.zero(currency, mc); final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement; final Money reducePrincipal = Money.zero(currency); final Map principalPortionMap = new HashMap<>(); @@ -253,13 +257,13 @@ public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor; final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate; final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation; - final Money unCompoundedAmount = Money.zero(currency); + final Money unCompoundedAmount = Money.zero(currency, mc); return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, - scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); } public int getPeriodNumber() { @@ -288,7 +292,7 @@ public Money getTotalCumulativePrincipal() { public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) { if (this.totalCumulativePrincipal != null) { - this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal); + this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal, mc); } else { this.totalCumulativePrincipal = totalCumulativePrincipal; } @@ -300,7 +304,7 @@ public Money getTotalCumulativeInterest() { public void addTotalCumulativeInterest(final Money totalCumulativeInterest) { if (this.totalCumulativeInterest != null) { - this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest); + this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest, mc); } else { this.totalCumulativeInterest = totalCumulativeInterest; } @@ -312,7 +316,7 @@ public Money getTotalFeeChargesCharged() { public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) { if (this.totalFeeChargesCharged != null) { - this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged); + this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged, mc); } else { this.totalFeeChargesCharged = totalFeeChargesCharged; } @@ -324,7 +328,7 @@ public Money getTotalPenaltyChargesCharged() { public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) { if (this.totalPenaltyChargesCharged != null) { - this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged); + this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged, mc); } else { this.totalPenaltyChargesCharged = totalPenaltyChargesCharged; } @@ -336,7 +340,7 @@ public Money getTotalRepaymentExpected() { public void addTotalRepaymentExpected(final Money totalRepaymentExpected) { if (this.totalRepaymentExpected != null) { - this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected); + this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected, mc); } else { this.totalRepaymentExpected = totalRepaymentExpected; } @@ -428,7 +432,7 @@ public void incrementInstalmentNumber() { public void addReducePrincipal(Money reducePrincipal) { if (this.reducePrincipal != null) { - this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal); + this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal, mc); } else { this.reducePrincipal = reducePrincipal; } @@ -436,13 +440,13 @@ public void addReducePrincipal(Money reducePrincipal) { public void reduceReducePrincipal(Money reducePrincipal) { if (this.reducePrincipal != null) { - this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal); + this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal, mc); } } public void addPrincipalToBeScheduled(Money principalToBeScheduled) { if (this.principalToBeScheduled != null) { - this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled); + this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled, mc); } else { this.principalToBeScheduled = principalToBeScheduled; } @@ -450,7 +454,7 @@ public void addPrincipalToBeScheduled(Money principalToBeScheduled) { public void addOutstandingBalance(Money outstandingBalance) { if (this.outstandingBalance != null) { - this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance); + this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance, mc); } else { this.outstandingBalance = outstandingBalance; } @@ -458,13 +462,13 @@ public void addOutstandingBalance(Money outstandingBalance) { public void reduceOutstandingBalance(Money outstandingBalance) { if (this.outstandingBalance != null) { - this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance); + this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance, mc); } } public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { if (this.outstandingBalanceAsPerRest != null) { - this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest); + this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest, mc); } else { this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; } @@ -472,7 +476,7 @@ public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) { if (this.outstandingBalanceAsPerRest != null) { - this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest); + this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest, mc); } } @@ -522,7 +526,7 @@ public void addUnCompoundedAmount(Money unCompoundedAmount) { public void minusUnCompoundedAmount(Money unCompoundedAmount) { if (this.unCompoundedAmount != null) { - this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount); + this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount, mc); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java index b891ace31bf..2d6382b55de 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java @@ -94,10 +94,10 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe : loanApplicationTerms.getSubmittedOnDate(); if (loanScheduleParams == null) { scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), - periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms)); + periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), mc); } else if (!loanScheduleParams.isPartialUpdate()) { scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), - periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams); + periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams, mc); } else { scheduleParams = loanScheduleParams; } @@ -345,7 +345,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(), scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(), currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), - totalInstallmentDue, !isCompletePeriod); + totalInstallmentDue, !isCompletePeriod, mc); if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) { installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount()); } @@ -651,7 +651,7 @@ private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, fin scheduleParams.getPeriodStartDate(), transactionDate, currentPeriodParams.getPrincipalForThisPeriod(), scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(), currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), - currentPeriodParams.fetchTotalAmountForPeriod(), false); + currentPeriodParams.fetchTotalAmountForPeriod(), false, mc); scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(interestTillDate.interestPaymentDueToGrace()); } } @@ -789,7 +789,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(), scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod, scheduleParams.getOutstandingBalance(), interestForCurrentInstallment, feeChargesForInstallment, - penaltyChargesForInstallment, totalInstallmentDue, true); + penaltyChargesForInstallment, totalInstallmentDue, true, mc); periods.add(installment); addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); @@ -1416,7 +1416,7 @@ periodNumberTemp, mc, mergeVariationsToMap(loanApplicationTerms, params), params LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(), startDate, transactionDate, totalInterest.zero(), totalInterest.zero(), totalInterest, totalInterest.zero(), - totalInterest.zero(), totalInterest, true); + totalInterest.zero(), totalInterest, true, mc); params.incrementInstalmentNumber(); periods.add(installment); totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); @@ -1489,7 +1489,7 @@ periodNumberTemp, mc, mergeVariationsToMap(loanApplicationTerms, params), params if (totalInterest.isGreaterThanZero()) { LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(), startDate, params.getActualRepaymentDate(), totalInterest.zero(), totalInterest.zero(), totalInterest, - totalInterest.zero(), totalInterest.zero(), totalInterest, true); + totalInterest.zero(), totalInterest.zero(), totalInterest, true, mc); params.incrementInstalmentNumber(); periods.add(installment); params.getCompoundingDateVariations().put(startDate, new TreeMap<>(params.getCompoundingMap())); @@ -2189,7 +2189,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L // for complete schedule generation LoanScheduleParams loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, - loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); + loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation, mc); List periods = new ArrayList<>(); final List retainedInstallments = new ArrayList<>(); @@ -2402,7 +2402,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L installment.resetDerivedComponents(); newRepaymentScheduleInstallments.add(installment); outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency)); - final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance); + final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance, mc); periods.add(loanScheduleModelPeriod); totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency)); totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency)); @@ -2493,7 +2493,7 @@ private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final L totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, - currency, applyInterestRecalculation); + currency, applyInterestRecalculation, mc); retainedInstallments.addAll(newRepaymentScheduleInstallments); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged())); @@ -2729,13 +2729,13 @@ private LoanScheduleModelPeriod createLoanScheduleModelDownPaymentPeriod(final L } private LoanScheduleModelPeriod createLoanScheduleModelPeriod(final LoanRepaymentScheduleInstallment installment, - final Money outstandingPrincipal) { + final Money outstandingPrincipal, final MathContext mc) { final MonetaryCurrency currency = outstandingPrincipal.getCurrency(); return LoanScheduleModelRepaymentPeriod.repayment(installment.getInstallmentNumber(), installment.getFromDate(), installment.getDueDate(), installment.getPrincipal(currency), outstandingPrincipal, installment.getInterestCharged(currency), installment.getFeeChargesCharged(currency), - installment.getPenaltyChargesCharged(currency), installment.getDue(currency), - installment.isRecalculatedInterestComponent()); + installment.getPenaltyChargesCharged(currency), installment.getDue(currency), installment.isRecalculatedInterestComponent(), + mc); } private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java similarity index 90% rename from fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java rename to fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index eb077751e8a..c0e9f4d9bba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import java.math.MathContext; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -45,9 +46,9 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override - public List generateRepaymentPeriods(final LocalDate scheduleStartDate, + public List generateRepaymentPeriods(final MathContext mc, final LocalDate scheduleStartDate, 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 ArrayList repaymentPeriods = new ArrayList<>(numberOfRepayments); LocalDate lastRepaymentDate = scheduleStartDate; @@ -65,7 +66,7 @@ public List generateRepaymentPeriods(final Loc } nextRepaymentDate = applyLoanTermVariations(loanApplicationTerms, 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; isFirstRepayment = false; } @@ -75,11 +76,13 @@ public List generateRepaymentPeriods(final Loc private LocalDate applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms, final LocalDate scheduledDueDate) { LocalDate modifiedScheduledDueDate = scheduledDueDate; // due date changes should be applied only for that dueDate - if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) { - LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); - if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) { - modifiedScheduledDueDate = loanTermVariationsData.getDateValue(); - loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate)); + if (loanApplicationTerms.getLoanTermVariations() != null) { + if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) { + LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation(); + if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) { + modifiedScheduledDueDate = loanTermVariationsData.getDateValue(); + loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate)); + } } } return modifiedScheduledDueDate; @@ -197,28 +200,30 @@ private AdjustedDateDetailsDTO getAdjustedDateDetailsDTO(final LocalDate dueRepa private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate( final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) { - final Recur recur = CalendarUtils.getICalRecur(holidayDetailDTO.getWorkingDays().getRecurrence()); - final boolean isSevenDaysWeek = (recur.getDayList().size() == 7); // 7 Seven days in the week - // If Workings days are not seven day week - if (!isSevenDaysWeek) { - checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, + if (holidayDetailDTO != null) { + final Recur recur = CalendarUtils.getICalRecur(holidayDetailDTO.getWorkingDays().getRecurrence()); + final boolean isSevenDaysWeek = (recur.getDayList().size() == 7); // 7 Seven days in the week + // If Workings days are not seven day week + if (!isSevenDaysWeek) { + checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, + isFirstRepayment); + } + // Check Holidays If applied + checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, isFirstRepayment); - } - // Check Holidays If applied - checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, - isFirstRepayment); - - /** - * Check Changed Schedule Date is holiday or is not a working day Then re-call this method to get the non - * holiday and working day - */ - if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(), - holidayDetailDTO.getHolidays()) != null) - || WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) { - recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO, - loanApplicationTerms, holidayDetailDTO, isFirstRepayment); - } + /** + * Check Changed Schedule Date is holiday or is not a working day Then re-call this method to get the non + * holiday and working day + */ + if ((holidayDetailDTO.isHolidayEnabled() && HolidayUtil.getApplicableHoliday(adjustedDateDetailsDTO.getChangedScheduleDate(), + holidayDetailDTO.getHolidays()) != null) + || WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), + adjustedDateDetailsDTO.getChangedScheduleDate())) { + recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO, + loanApplicationTerms, holidayDetailDTO, isFirstRepayment); + } + } return adjustedDateDetailsDTO; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 8f13670bc63..e4fb8fd2bdb 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -249,6 +249,8 @@ private LoanApplicationTerms(Builder builder) { this.inArrearsTolerance = builder.inArrearsTolerance; this.disbursementDatas = builder.disbursementDatas; this.downPaymentAmount = builder.downPaymentAmount; + this.submittedOnDate = builder.submittedOnDate; + this.seedDate = builder.seedDate; } public static class Builder { @@ -272,6 +274,8 @@ public static class Builder { private Money inArrearsTolerance; private List disbursementDatas; private Money downPaymentAmount; + private LocalDate submittedOnDate; + private LocalDate seedDate; public Builder currency(ApplicationCurrency currency) { this.currency = currency; @@ -343,11 +347,6 @@ public Builder daysInYearType(DaysInYearType daysInYearType) { return this; } - public Builder variationsDataWrapper(LoanTermVariationsDataWrapper variationsDataWrapper) { - this.variationsDataWrapper = variationsDataWrapper; - return this; - } - public Builder fixedLength(Integer fixedLength) { this.fixedLength = fixedLength; return this; @@ -368,23 +367,41 @@ public Builder downPaymentAmount(Money downPaymentAmount) { return this; } + public Builder submittedOnDate(LocalDate submittedOnDate) { + this.submittedOnDate = submittedOnDate; + return this; + } + + public Builder seedDate(LocalDate seedDate) { + this.seedDate = seedDate; + return this; + } + public LoanApplicationTerms build() { return new LoanApplicationTerms(this); } } - public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData) { - Money principal = Money.of(modelData.currency().toData(), modelData.disbursementAmount()); - Money downPaymentAmount = Money.zero(modelData.currency().toData()); + public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData, MathContext mc) { + Money principal = Money.of(modelData.currency().toData(), modelData.disbursementAmount(), mc); + Money downPaymentAmount = Money.zero(modelData.currency().toData(), mc); if (modelData.downPaymentEnabled()) { downPaymentAmount = Money.of(modelData.currency().toData(), - MathUtil.percentageOf(principal.getAmount(), modelData.disbursementAmount(), 19)); + MathUtil.percentageOf(principal.getAmount(), modelData.disbursementAmount(), mc), mc); if (modelData.installmentAmountInMultiplesOf() != null) { - downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, modelData.installmentAmountInMultiplesOf()); + downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, modelData.installmentAmountInMultiplesOf(), mc); } } + LocalDate seedDate; + + if (modelData.disbursementDate() != null) { + seedDate = modelData.disbursementDate(); + } else { + seedDate = modelData.scheduleGenerationStartDate(); + } + return new Builder().currency(modelData.currency()).loanTermFrequency(modelData.numberOfRepayments()) .loanTermPeriodFrequencyType(PeriodFrequencyType.valueOf(modelData.repaymentFrequencyType())) .numberOfRepayments(modelData.numberOfRepayments()).repaymentEvery(modelData.repaymentFrequency()) @@ -394,8 +411,8 @@ public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData m .annualNominalInterestRate(modelData.annualNominalInterestRate()).principal(principal) .expectedDisbursementDate(modelData.disbursementDate()).repaymentsStartingFromDate(modelData.scheduleGenerationStartDate()) .daysInMonthType(modelData.daysInMonth()).daysInYearType(modelData.daysInYear()).fixedLength(modelData.fixedLength()) - .inArrearsTolerance(Money.zero(modelData.currency().toData())).disbursementDatas(new ArrayList<>()) - .downPaymentAmount(downPaymentAmount).build(); + .inArrearsTolerance(Money.zero(modelData.currency().toData(), mc)).disbursementDatas(new ArrayList<>()) + .downPaymentAmount(downPaymentAmount).submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate).build(); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java index fd848751e50..4460cfe8ddc 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.util.HashSet; import java.util.Set; @@ -46,18 +47,20 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel private final Set loanCompoundingDetails = new HashSet<>(); private boolean isEMIFixedSpecificToInstallment = false; BigDecimal rescheduleInterestPortion; + private final MathContext mc; public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate, final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, - final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent) { + final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent, + final MathContext mc) { return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance, - interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent); + interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent, mc); } public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue, - final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent) { + final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent, final MathContext mc) { this.periodNumber = periodNumber; this.fromDate = fromDate; this.dueDate = dueDate; @@ -68,6 +71,7 @@ public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate this.penaltyChargesDue = penaltyChargesDue; this.totalDue = totalDue; this.recalculatedInterestComponent = recalculatedInterestComponent; + this.mc = mc; } @Override @@ -144,15 +148,15 @@ public BigDecimal penaltyChargesDue() { @Override public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) { - this.feeChargesDue = this.feeChargesDue.plus(feeCharge); - this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge); - this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge); + this.feeChargesDue = this.feeChargesDue.plus(feeCharge, mc); + this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge, mc); + this.totalDue = this.totalDue.plus(feeCharge, mc).plus(penaltyCharge, mc); } @Override public void addPrincipalAmount(final Money principalDue) { - this.principalDue = this.principalDue.plus(principalDue); - this.totalDue = this.totalDue.plus(principalDue); + this.principalDue = this.principalDue.plus(principalDue, mc); + this.totalDue = this.totalDue.plus(principalDue, mc); } @Override @@ -162,8 +166,8 @@ public boolean isRecalculatedInterestComponent() { @Override public void addInterestAmount(Money interestDue) { - this.interestDue = this.interestDue.plus(interestDue); - this.totalDue = this.totalDue.plus(interestDue); + this.interestDue = this.interestDue.plus(interestDue, mc); + this.totalDue = this.totalDue.plus(interestDue, mc); } public void setPeriodNumber(int periodNumber) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java index fd5e824e8b0..16ceae37159 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import java.math.MathContext; import java.time.LocalDate; import java.util.List; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; @@ -27,8 +28,8 @@ public interface ScheduledDateGenerator { - List generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms, - HolidayDetailDTO holidayDetailDTO); + List generateRepaymentPeriods(MathContext mc, LocalDate scheduledDueDate, + LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO); LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO); diff --git a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java index ada2be619ff..c1eb1ad8be0 100644 --- a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java +++ b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.util.HashMap; @@ -64,6 +65,7 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest { @BeforeAll public static void init() { MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); + MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN)); } @Test @@ -142,11 +144,12 @@ private static LoanCharge createCharge(boolean penalty) { @NotNull private LoanRepaymentScheduleInstallment createPeriod(int periodId, LocalDate start, LocalDate end) { LoanRepaymentScheduleInstallment period = Mockito.mock(LoanRepaymentScheduleInstallment.class); + MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN); Mockito.when(period.getInstallmentNumber()).thenReturn(periodId); Mockito.when(period.getFromDate()).thenReturn(start); Mockito.when(period.getDueDate()).thenReturn(end); - Money principal = Money.of(currency, new BigDecimal("1000.0")); - Money interest = Money.of(currency, BigDecimal.ZERO); + Money principal = Money.of(currency, new BigDecimal("1000.0"), mc); + Money interest = Money.of(currency, BigDecimal.ZERO, mc); Mockito.when(period.getPrincipal(eq(currency))).thenReturn(principal); Mockito.when(period.getInterestCharged(eq(currency))).thenReturn(interest); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 7499fccaa0c..6375353af8d 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -176,7 +176,7 @@ public Pair repr final Integer installmentAmountInMultiplesOf = loan.getLoanProduct().getInstallmentAmountInMultiplesOf(); final LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateModel(loanProductRelatedDetail, - installmentAmountInMultiplesOf, installments); + installmentAmountInMultiplesOf, installments, overpaymentHolder.getMoneyObject().getMc()); ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder, changedTransactionDetail, scheduleModel); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java index 85366e602f9..62c79443d3b 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/InterestPeriod.java @@ -20,6 +20,7 @@ import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.util.Optional; import lombok.EqualsAndHashCode; @@ -44,9 +45,10 @@ public class InterestPeriod implements Comparable { private Money disbursementAmount; private Money balanceCorrectionAmount; private Money outstandingLoanBalance; + private final MathContext mc; 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.fromDate = fromDate; this.dueDate = dueDate; @@ -54,12 +56,13 @@ public InterestPeriod(RepaymentPeriod repaymentPeriod, LocalDate fromDate, Local this.disbursementAmount = disbursementAmount; this.balanceCorrectionAmount = balanceCorrectionAmount; 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(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(), - interestPeriod.getOutstandingLoanBalance()); + interestPeriod.getOutstandingLoanBalance(), mc); } @Override @@ -72,11 +75,11 @@ public void addBalanceCorrectionAmount(final Money balanceCorrectionAmount) { } public void addDisbursementAmount(final Money disbursementAmount) { - this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount); + this.disbursementAmount = MathUtil.plus(this.disbursementAmount, disbursementAmount, mc); } public Money getCalculatedDueInterest() { - return getOutstandingLoanBalance().multipliedBy(getRateFactor()); + return getOutstandingLoanBalance().multipliedBy(getRateFactor(), mc); } public void updateOutstandingLoanBalance() { @@ -86,17 +89,17 @@ public void updateOutstandingLoanBalance() { InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getInterestPeriods() .get(previousRepaymentPeriod.get().getInterestPeriods().size() - 1); this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance()// - .plus(previousInterestPeriod.getDisbursementAmount())// - .plus(previousInterestPeriod.getBalanceCorrectionAmount())// - .minus(previousRepaymentPeriod.get().getDuePrincipal())// - .plus(previousRepaymentPeriod.get().getPaidPrincipal());// + .plus(previousInterestPeriod.getDisbursementAmount(), mc)// + .plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc)// + .minus(previousRepaymentPeriod.get().getDuePrincipal(), mc)// + .plus(previousRepaymentPeriod.get().getPaidPrincipal(), mc);// } } else { int index = getRepaymentPeriod().getInterestPeriods().indexOf(this); InterestPeriod previousInterestPeriod = getRepaymentPeriod().getInterestPeriods().get(index - 1); this.outstandingLoanBalance = previousInterestPeriod.getOutstandingLoanBalance() // - .plus(previousInterestPeriod.getBalanceCorrectionAmount()) // - .plus(previousInterestPeriod.getDisbursementAmount()); // + .plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc) // + .plus(previousInterestPeriod.getDisbursementAmount(), mc); // } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java index 45f61c02f46..7f51b9db4ca 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/ProgressiveLoanInterestScheduleModel.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.data; import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -38,33 +39,36 @@ public class ProgressiveLoanInterestScheduleModel { private final List interestRates; private final LoanProductRelatedDetail loanProductRelatedDetail; private final Integer installmentAmountInMultiplesOf; + private MathContext mc; public ProgressiveLoanInterestScheduleModel(List repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail, - Integer installmentAmountInMultiplesOf) { + Integer installmentAmountInMultiplesOf, MathContext mc) { this.repaymentPeriods = repaymentPeriods; this.interestRates = new ArrayList<>(); this.loanProductRelatedDetail = loanProductRelatedDetail; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; + this.mc = mc; } private ProgressiveLoanInterestScheduleModel(List repaymentPeriods, final List interestRates, - LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf) { + LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) { + this.mc = mc; this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods); this.interestRates = new ArrayList<>(interestRates); this.loanProductRelatedDetail = loanProductRelatedDetail; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; } - public ProgressiveLoanInterestScheduleModel deepCopy() { + public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) { return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail, - installmentAmountInMultiplesOf); + installmentAmountInMultiplesOf, mc); } private List copyRepaymentPeriods(final List repaymentPeriods) { final List repaymentCopies = new ArrayList<>(repaymentPeriods.size()); RepaymentPeriod previousPeriod = null; for (RepaymentPeriod repaymentPeriod : repaymentPeriods) { - RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentPeriod); + RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentPeriod, mc); previousPeriod = currentPeriod; repaymentCopies.add(currentPeriod); } @@ -175,11 +179,11 @@ void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate previousInterestPeriod.addDisbursementAmount(disbursedAmount); previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount); 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); } - private Money getZero() { - return Money.zero(loanProductRelatedDetail.getCurrency()); + private Money getZero(MathContext mc) { + return Money.zero(loanProductRelatedDetail.getCurrency(), mc); } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java index d9ff4e89f71..12943352786 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/RepaymentPeriod.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.data; import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -54,20 +55,23 @@ public class RepaymentPeriod { private Memo calculatedDueInterestCalculation; private Memo dueInterestCalculation; private Memo 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.fromDate = fromDate; this.dueDate = dueDate; this.emi = emi; + this.mc = mc; this.interestPeriods = new ArrayList<>(); // 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())); - this.paidInterest = getZero(); - this.paidPrincipal = getZero(); + getInterestPeriods() + .add(new InterestPeriod(this, getFromDate(), getDueDate(), BigDecimal.ZERO, getZero(mc), getZero(mc), getZero(mc), mc)); + 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.fromDate = repaymentPeriod.fromDate; this.dueDate = repaymentPeriod.dueDate; @@ -75,9 +79,10 @@ public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod this.interestPeriods = new ArrayList<>(); this.paidPrincipal = repaymentPeriod.paidPrincipal; 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 for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) { - interestPeriods.add(new InterestPeriod(this, interestPeriod)); + interestPeriods.add(new InterestPeriod(this, interestPeriod, mc)); } } @@ -105,21 +110,21 @@ public Money getCalculatedDueInterest() { } private Money calculateCalculatedDueInterest() { - Money calculatedDueInterest = getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(getZero(), - Money::plus); + Money calculatedDueInterest = getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest).reduce(getZero(mc), + (m1, m2) -> m1.plus(m2, mc)); if (getPrevious().isPresent()) { - calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest()); + calculatedDueInterest = calculatedDueInterest.add(getPrevious().get().getUnrecognizedInterest(), mc); } return calculatedDueInterest; } - private Money getZero() { + private Money getZero(MathContext mc) { // EMI is always initiated - return this.emi.zero(); + return this.emi.zero(mc); } public Money getCalculatedDuePrincipal() { - return getEmi().minus(getCalculatedDueInterest()); + return getEmi().minus(getCalculatedDueInterest(), mc); } public boolean isFullyPaid() { @@ -138,11 +143,11 @@ public Money getDueInterest() { public Money getDuePrincipal() { // 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() { - return getCalculatedDueInterest().minus(getDueInterest()); + return getCalculatedDueInterest().minus(getDueInterest(), mc); } public Money getOutstandingLoanBalance() { @@ -150,22 +155,22 @@ public Money getOutstandingLoanBalance() { outstandingBalanceCalculation = Memo.of(() -> { InterestPeriod lastInstallmentPeriod = getInterestPeriods().get(getInterestPeriods().size() - 1); Money calculatedOutStandingLoanBalance = lastInstallmentPeriod.getOutstandingLoanBalance() // - .plus(lastInstallmentPeriod.getBalanceCorrectionAmount()) // - .plus(lastInstallmentPeriod.getDisbursementAmount()) // - .minus(getDuePrincipal())// - .plus(getPaidPrincipal());// - return MathUtil.negativeToZero(calculatedOutStandingLoanBalance); + .plus(lastInstallmentPeriod.getBalanceCorrectionAmount(), mc) // + .plus(lastInstallmentPeriod.getDisbursementAmount(), mc) // + .minus(getDuePrincipal(), mc)// + .plus(getPaidPrincipal(), mc);// + return MathUtil.negativeToZero(calculatedOutStandingLoanBalance, mc); }, () -> new Object[] { paidPrincipal, paidInterest, interestPeriods }); } return outstandingBalanceCalculation.get(); } 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) { - this.paidInterest = MathUtil.plus(this.paidInterest, paidInterest); + this.paidInterest = MathUtil.plus(this.paidInterest, paidInterest, mc); } public Money getInitialBalanceForEmiRecalculation() { @@ -173,10 +178,10 @@ public Money getInitialBalanceForEmiRecalculation() { if (getPrevious().isPresent()) { initialBalance = getPrevious().get().getOutstandingLoanBalance(); } else { - initialBalance = getZero(); + initialBalance = getZero(mc); } - Money totalDisbursedAmount = getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount).reduce(getZero(), - Money::plus); - return initialBalance.add(totalDisbursedAmount); + Money totalDisbursedAmount = getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount).reduce(getZero(mc), + (m1, m2) -> m1.plus(m2, mc)); + return initialBalance.add(totalDisbursedAmount, mc); } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index 2de29d2c969..e4382e54dad 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -87,17 +87,17 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer : loanApplicationTerms.getSubmittedOnDate(); 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 // set and handled separately after all installments generated final Set nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges); - final List expectedRepaymentPeriods = scheduledDateGenerator - .generateRepaymentPeriods(periodStartDate, loanApplicationTerms, holidayDetailDTO); + final List expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc, + periodStartDate, loanApplicationTerms, holidayDetailDTO); final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generateInterestScheduleModel( expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(), - loanApplicationTerms.getInstallmentAmountInMultiplesOf()); + loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); final List periods = new ArrayList<>(expectedRepaymentPeriods.size()); prepareDisbursementsOnLoanApplicationTerms(loanApplicationTerms); @@ -110,7 +110,7 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer scheduleParams.setActualRepaymentDate(repaymentPeriod.getDueDate()); processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods, - chargesDueAtTimeOfDisbursement, false); + chargesDueAtTimeOfDisbursement, false, mc); repaymentPeriod.setPeriodNumber(scheduleParams.getInstalmentNumber()); if (loanApplicationTerms.getLoanTermVariations() != null) { @@ -135,7 +135,7 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer scheduleParams.addTotalCumulativePrincipal(principalDue); scheduleParams.addTotalCumulativeInterest(interestDue); // add everything - scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue)); + scheduleParams.addTotalRepaymentExpected(principalDue.plus(interestDue, mc)); }); applyChargesForCurrentPeriod(repaymentPeriod, loanCharges, scheduleParams, currency, mc); @@ -147,7 +147,7 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer if (loanApplicationTerms.isMultiDisburseLoan()) { processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods, - chargesDueAtTimeOfDisbursement, true); + chargesDueAtTimeOfDisbursement, true, mc); } // determine fees and penalties for charges which depends on total @@ -158,8 +158,8 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer final BigDecimal totalOutstanding = BigDecimal.ZERO; return LoanScheduleModel.from(periods, applicationCurrency, interestScheduleModel.getLoanTermInDays(), - scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()), - scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(), + scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount(), mc), + scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount(), mc).getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding); @@ -167,7 +167,7 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer 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); } @@ -184,7 +184,7 @@ private void prepareDisbursementsOnLoanApplicationTerms(final LoanApplicationTer private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final ArrayList disbursementDataList, final LoanScheduleParams scheduleParams, final ProgressiveLoanInterestScheduleModel interestScheduleModel, final List periods, - final BigDecimal chargesDueAtTimeOfDisbursement, final boolean includeDisbursementsAfterMaturityDate) { + final BigDecimal chargesDueAtTimeOfDisbursement, final boolean includeDisbursementsAfterMaturityDate, final MathContext mc) { for (DisbursementData disbursementData : disbursementDataList) { final LocalDate disbursementDate = disbursementData.disbursementDate(); @@ -202,7 +202,7 @@ private void processDisbursements(final LoanApplicationTerms loanApplicationTerm 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 .disbursement(disbursementData.disbursementDate(), disbursedAmount, chargesDueAtTimeOfDisbursement); periods.add(disbursementPeriod); @@ -218,7 +218,7 @@ private void processDisbursements(final LoanApplicationTerms loanApplicationTerm } } - Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency()); + Money downPaymentAmount = Money.zero(loanApplicationTerms.getCurrency(), mc); if (loanApplicationTerms.isDownPaymentEnabled()) { downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(), MathUtil.percentageOf(disbursedAmount.getAmount(), loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19)); @@ -321,7 +321,7 @@ private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final L final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, final MathContext mc) { - Money cumulative = Money.zero(monetaryCurrency); + Money cumulative = Money.zero(monetaryCurrency, mc); if (loanCharges != null) { for (final LoanCharge loanCharge : loanCharges) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { @@ -356,7 +356,7 @@ private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, fin final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, final MathContext mc) { - Money cumulative = Money.zero(monetaryCurrency); + Money cumulative = Money.zero(monetaryCurrency, mc); if (loanCharges != null) { for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { @@ -407,8 +407,8 @@ private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanSched final Collection periods, final Set nonCompoundingCharges, MathContext mc) { for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { - PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()), - Money.of(currency, loanScheduleModelPeriod.interestDue()), null); + PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue(), mc), + Money.of(currency, loanScheduleModelPeriod.interestDue(), mc), null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java index 157989912ac..ac10aeab3e4 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanproduct.calc; import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.util.List; import java.util.Optional; @@ -33,10 +34,10 @@ public interface EMICalculator { ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(List periods, - LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf); + LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc); ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail, - Integer installmentAmountInMultiplesOf, List repaymentPeriods); + Integer installmentAmountInMultiplesOf, List repaymentPeriods, MathContext mc); Optional findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index c2ab25fa872..c97ef747fc0 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.loanproduct.calc; import java.math.BigDecimal; +import java.math.MathContext; import java.time.LocalDate; import java.time.Year; import java.util.ArrayList; @@ -29,7 +30,6 @@ import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.MathUtil; 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.DaysInYearType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; @@ -40,12 +40,10 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; -import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor -@DependsOn("moneyHelper") public final class ProgressiveEMICalculator implements EMICalculator { private static final BigDecimal DIVISOR_100 = new BigDecimal("100"); @@ -53,19 +51,19 @@ public final class ProgressiveEMICalculator implements EMICalculator { @Override public ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(final List periods, - final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf) { - final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency()); + final LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) { + final Money zeroAmount = Money.zero(loanProductRelatedDetail.getCurrency(), mc); final ArrayList interestRepaymentModelList = new ArrayList<>(periods.size()); RepaymentPeriod previousPeriod = null; for (final LoanScheduleModelRepaymentPeriod period : periods) { - RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, period.periodFromDate(), period.periodDueDate(), - zeroAmount); + RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, period.periodFromDate(), period.periodDueDate(), zeroAmount, + mc); previousPeriod = currentPeriod; interestRepaymentModelList.add(currentPeriod); } return new ProgressiveLoanInterestScheduleModel(interestRepaymentModelList, loanProductRelatedDetail, - installmentAmountInMultiplesOf); + installmentAmountInMultiplesOf, mc); } @Override @@ -85,7 +83,7 @@ public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleM final Money disbursedAmount) { scheduleModel .changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, - Money.zero(disbursedAmount.getCurrency())) + Money.zero(disbursedAmount.getCurrency(), scheduleModel.mc())) .ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(repaymentPeriod.getDueDate(), scheduleModel)); } @@ -130,7 +128,8 @@ public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, Loc @Override public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel, 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(); LocalDate adjustedTargetDate = targetDate; InterestPeriod interestPeriod; @@ -151,19 +150,19 @@ public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleMod // TODO: gather all the unrecognized interest from previous periods based on target date Money payableInterest = targetDate.isBefore(repaymentPeriod.getFromDate()) - ? Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency()) + ? Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc) : repaymentPeriod.getDueInterest(); Money outstandingLoanBalance = interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount()); - Money calculatedEmi = outstandingLoanBalance.plus(payableInterest); + Money calculatedEmi = outstandingLoanBalance.plus(payableInterest, mc); if (calculatedEmi.isLessThan(repaymentPeriod.getEmi())) { // Review this logic - repaymentPeriod.setEmi(outstandingLoanBalance.plus(payableInterest).plus(repaymentPeriod.getPaidInterest()) - .plus(repaymentPeriod.getPaidPrincipal())); + repaymentPeriod.setEmi(outstandingLoanBalance.plus(payableInterest).plus(repaymentPeriod.getPaidInterest(), mc) + .plus(repaymentPeriod.getPaidPrincipal(), mc)); } - Money payablePrincipal = repaymentPeriod.getEmi().minus(payableInterest); + Money payablePrincipal = repaymentPeriod.getEmi().minus(payableInterest, mc); return new PayableDetails(repaymentPeriod.getEmi(), payablePrincipal, payableInterest, - interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount())); + interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc)); } @Override @@ -189,18 +188,19 @@ void calculateEMIValueAndRateFactors(final LocalDate calculateFromRepaymentPerio } private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) { + MathContext mc = scheduleModel.mc(); 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) - .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() .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 findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()) .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) { @@ -209,24 +209,26 @@ private void calculateOutstandingBalance(ProgressiveLoanInterestScheduleModel sc private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final ProgressiveLoanInterestScheduleModel scheduleModel, final List relatedRepaymentPeriods) { + MathContext mc = scheduleModel.mc(); final Money emiDifference = getDifferenceBetweenLastTwoPeriod(relatedRepaymentPeriods, scheduleModel); final int numberOfRelatedPeriods = relatedRepaymentPeriods.size(); double lowerHalfOfRelatedPeriods = Math.floor(numberOfRelatedPeriods / 2.0); - if (emiDifference.isZero() || lowerHalfOfRelatedPeriods == 0.0) { + if (emiDifference.isZero(mc) || lowerHalfOfRelatedPeriods == 0.0) { return; } final Money originalEmi = relatedRepaymentPeriods.get(numberOfRelatedPeriods - 2).getEmi(); - boolean shouldBeAdjusted = emiDifference.abs().multipliedBy(100) - .isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods))); + boolean shouldBeAdjusted = emiDifference.abs(mc).multipliedBy(100, mc) + .isGreaterThan(Money.of(originalEmi.getCurrency(), BigDecimal.valueOf(lowerHalfOfRelatedPeriods), mc)); if (shouldBeAdjusted) { - Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, MoneyHelper.getMathContext().getRoundingMode()); - Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel, originalEmi.plus(adjustment)); + Money adjustment = emiDifference.dividedBy(numberOfRelatedPeriods, mc.getRoundingMode(), mc); + Money adjustedEqualMonthlyInstallmentValue = applyInstallmentAmountInMultiplesOf(scheduleModel, + originalEmi.plus(adjustment, mc)); if (adjustedEqualMonthlyInstallmentValue.isEqualTo(originalEmi)) { return; } final LocalDate relatedPeriodsFirstDueDate = relatedRepaymentPeriods.get(0).getDueDate(); - final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy(); + final ProgressiveLoanInterestScheduleModel newScheduleModel = scheduleModel.deepCopy(mc); newScheduleModel.repaymentPeriods().forEach(period -> { if (!period.getDueDate().isBefore(relatedPeriodsFirstDueDate)) { period.setEmi(adjustedEqualMonthlyInstallmentValue); @@ -235,7 +237,7 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv calculateOutstandingBalance(newScheduleModel); calculateLastUnpaidRepaymentPeriodEMI(newScheduleModel); 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) { return; } @@ -263,8 +265,8 @@ private void checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods(final Progressiv * * @return Rate Interest Rate in fraction format */ - BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate) { - return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, MoneyHelper.getMathContext()); + BigDecimal calcNominalInterestRatePercentage(final BigDecimal interestRate, MathContext mc) { + return MathUtil.nullToZero(interestRate).divide(DIVISOR_100, mc); } /** @@ -286,8 +288,10 @@ void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod */ BigDecimal calculateRateFactorPerPeriod(final RepaymentPeriod repaymentPeriod, final InterestPeriod interestPeriod, final ProgressiveLoanInterestScheduleModel scheduleModel) { + final MathContext mc = scheduleModel.mc(); 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 DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(loanProductRelatedDetail.getDaysInMonthType()); final PeriodFrequencyType repaymentFrequency = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); @@ -306,13 +310,13 @@ BigDecimal calculateRateFactorPerPeriod(final RepaymentPeriod repaymentPeriod, f // TODO review: (repayment frequency: days, weeks, years; validation day is month fix 30) // TODO refactor this logic to represent in interest period if (partialPeriodCalculationNeeded) { - final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriod); + final BigDecimal cumulatedPeriodFractions = calculatePeriodFractions(interestPeriod, mc); return rateFactorByRepaymentPartialPeriod(interestRate, repaymentEvery, cumulatedPeriodFractions, BigDecimal.ONE, - BigDecimal.ONE); + BigDecimal.ONE, mc); } return calculateRateFactorPerPeriodBasedOnRepaymentFrequency(interestRate, repaymentFrequency, repaymentEvery, daysInMonth, - daysInYear, actualDaysInPeriod, calculatedDaysInPeriod); + daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc); } /** @@ -321,7 +325,7 @@ BigDecimal calculateRateFactorPerPeriod(final RepaymentPeriod repaymentPeriod, f * @param interestPeriod * @return */ - BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod) { + BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod, MathContext mc) { BigDecimal cumulatedRateFactor = BigDecimal.ZERO; int actualYear = interestPeriod.getFromDate().getYear(); int endYear = interestPeriod.getDueDate().getYear(); @@ -332,8 +336,7 @@ BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod) { endOfActualYear = actualYear == endYear ? interestPeriod.getDueDate() : LocalDate.of(actualYear, 12, 31); BigDecimal numberOfDaysInYear = BigDecimal.valueOf(Year.of(actualYear).length()); BigDecimal calculatedDaysInActualYear = BigDecimal.valueOf(DateUtils.getDifferenceInDays(actualDate, endOfActualYear)); - cumulatedRateFactor = cumulatedRateFactor - .add(calculatedDaysInActualYear.divide(numberOfDaysInYear, MoneyHelper.getMathContext()), MoneyHelper.getMathContext()); + cumulatedRateFactor = cumulatedRateFactor.add(calculatedDaysInActualYear.divide(numberOfDaysInYear, mc), mc); actualDate = endOfActualYear; actualYear++; } @@ -354,14 +357,15 @@ BigDecimal calculatePeriodFractions(InterestPeriod interestPeriod) { */ BigDecimal calculateRateFactorPerPeriodBasedOnRepaymentFrequency(final BigDecimal interestRate, 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) { case DAYS -> - rateFactorByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod); + rateFactorByRepaymentEveryDay(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc); case WEEKS -> - rateFactorByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod); + rateFactorByRepaymentEveryWeek(interestRate, repaymentEvery, daysInYear, actualDaysInPeriod, calculatedDaysInPeriod, mc); case MONTHS -> rateFactorByRepaymentEveryMonth(interestRate, repaymentEvery, daysInMonth, daysInYear, actualDaysInPeriod, - calculatedDaysInPeriod); + calculatedDaysInPeriod, mc); default -> throw new UnsupportedOperationException("Invalid repayment frequency"); // not supported yet }; } @@ -370,14 +374,15 @@ void calculateEMIOnPeriods(final List repaymentPeriods, final P if (repaymentPeriods.isEmpty()) { return; } - final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods)); - final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods)); + final MathContext mc = scheduleModel.mc(); + final BigDecimal rateFactorN = MathUtil.stripTrailingZeros(calculateRateFactorPlus1N(repaymentPeriods, mc)); + final BigDecimal fnResult = MathUtil.stripTrailingZeros(calculateFnResult(repaymentPeriods, mc)); final RepaymentPeriod startPeriod = repaymentPeriods.get(0); // TODO: double check final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation(); 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); repaymentPeriods.forEach(period -> period.setEmi(finalEqualMonthlyInstallment)); @@ -392,39 +397,40 @@ Money applyInstallmentAmountInMultiplesOf(final ProgressiveLoanInterestScheduleM Money getDifferenceBetweenLastTwoPeriod(final List repaymentPeriods, final ProgressiveLoanInterestScheduleModel scheduleModel) { + MathContext mc = scheduleModel.mc(); int numberOfUpcomingPeriods = repaymentPeriods.size(); 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 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 */ - BigDecimal calculateRateFactorPlus1N(final List periods) { + BigDecimal calculateRateFactorPlus1N(final List periods, MathContext mc) { 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 */ - BigDecimal calculateFnResult(final List periods) { + BigDecimal calculateFnResult(final List periods, final MathContext mc) { return periods.stream()// .skip(1)// .map(RepaymentPeriod::getRateFactorPlus1)// - .reduce(BigDecimal.ONE, this::fnValue);// + .reduce(BigDecimal.ONE, (previousFnValue, currentRateFactor) -> fnValue(previousFnValue, currentRateFactor, mc));// } /** * Calculate the EMI (Equal Monthly Installment) value */ - BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult) { - return rateFactorPlus1N.multiply(outstandingBalanceForRest, MoneyHelper.getMathContext()).divide(fnResult, - MoneyHelper.getMathContext()); + BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal outstandingBalanceForRest, final BigDecimal fnResult, + MathContext mc) { + return rateFactorPlus1N.multiply(outstandingBalanceForRest, mc).divide(fnResult, mc); } /** @@ -452,9 +458,9 @@ BigDecimal calculateEMIValue(final BigDecimal rateFactorPlus1N, final BigDecimal * @return Rate Factor for period */ 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, - calculatedDaysInPeriod); + calculatedDaysInPeriod, mc); } /** @@ -482,9 +488,9 @@ BigDecimal rateFactorByRepaymentEveryDay(final BigDecimal interestRate, final Bi * @return Rate Factor for period */ 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, - calculatedDaysInPeriod); + calculatedDaysInPeriod, mc); } /** @@ -515,9 +521,10 @@ BigDecimal rateFactorByRepaymentEveryWeek(final BigDecimal interestRate, final B * @return Rate Factor for period */ 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, - calculatedDaysInPeriod); + calculatedDaysInPeriod, mc); } /** @@ -549,14 +556,14 @@ BigDecimal rateFactorByRepaymentEveryMonth(final BigDecimal interestRate, final */ BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigDecimal repaymentPeriodMultiplierInDays, final BigDecimal repaymentEvery, final BigDecimal daysInYear, final BigDecimal actualDaysInPeriod, - final BigDecimal calculatedDaysInPeriod) { + final BigDecimal calculatedDaysInPeriod, final MathContext mc) { final BigDecimal interestFractionPerPeriod = repaymentPeriodMultiplierInDays// - .multiply(repaymentEvery, MoneyHelper.getMathContext())// - .divide(daysInYear, MoneyHelper.getMathContext());// + .multiply(repaymentEvery, mc)// + .divide(daysInYear, mc);// return interestRate// - .multiply(interestFractionPerPeriod, MoneyHelper.getMathContext())// - .multiply(actualDaysInPeriod, MoneyHelper.getMathContext())// - .divide(calculatedDaysInPeriod, MoneyHelper.getMathContext());// + .multiply(interestFractionPerPeriod, mc)// + .multiply(actualDaysInPeriod, mc)// + .divide(calculatedDaysInPeriod, mc);// } /** @@ -564,12 +571,13 @@ BigDecimal rateFactorByRepaymentPeriod(final BigDecimal interestRate, final BigD * */ 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); return interestRate// - .multiply(interestFractionPerPeriod, MoneyHelper.getMathContext())// - .multiply(actualDaysInPeriod, MoneyHelper.getMathContext())// - .divide(calculatedDaysInPeriod, MoneyHelper.getMathContext());// + .multiply(interestFractionPerPeriod, mc)// + .multiply(actualDaysInPeriod, mc)// + .divide(calculatedDaysInPeriod, mc);// } /** @@ -582,13 +590,13 @@ BigDecimal rateFactorByRepaymentPartialPeriod(final BigDecimal interestRate, fin * @param currentRateFactor * */ - BigDecimal fnValue(final BigDecimal previousFnValue, final BigDecimal currentRateFactor) { - return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, MoneyHelper.getMathContext()), MoneyHelper.getMathContext()); + BigDecimal fnValue(final BigDecimal previousFnValue, final BigDecimal currentRateFactor, final MathContext mc) { + return BigDecimal.ONE.add(previousFnValue.multiply(currentRateFactor, mc), mc); } @Override public ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDetail loanProductRelatedDetail, - Integer installmentAmountInMultiplesOf, List repaymentPeriods) { + Integer installmentAmountInMultiplesOf, List repaymentPeriods, MathContext mc) { List repaymentModelsWithoutDownPayment = repaymentPeriods.stream() .filter(period -> !period.isDownPayment() && !period.isAdditional()).toList(); @@ -596,11 +604,11 @@ public ProgressiveLoanInterestScheduleModel generateModel(LoanProductRelatedDeta RepaymentPeriod previousPeriod = null; for (LoanRepaymentScheduleInstallment repaymentModel : repaymentModelsWithoutDownPayment) { RepaymentPeriod currentPeriod = new RepaymentPeriod(previousPeriod, repaymentModel.getFromDate(), repaymentModel.getDueDate(), - Money.zero(repaymentModel.getLoan().getCurrency())); + Money.zero(repaymentModel.getLoan().getCurrency(), mc), mc); previousPeriod = currentPeriod; repaymentModels.add(currentPeriod); } - return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf); + return new ProgressiveLoanInterestScheduleModel(repaymentModels, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); } } diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java index ffef3502a36..d1f9de0e0de 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.util.ArrayList; @@ -90,11 +91,12 @@ class AdvancedPaymentScheduleTransactionProcessorTest { private static final MonetaryCurrency MONETARY_CURRENCY = new MonetaryCurrency("USD", 2, 1); private static final MockedStatic MONEY_HELPER = mockStatic(MoneyHelper.class); private AdvancedPaymentScheduleTransactionProcessor underTest; - private final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class); + private static final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class); @BeforeAll public static void init() { MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN); + MONEY_HELPER.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.HALF_EVEN)); } @AfterAll diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java index b0015c29c94..c5b7f65d1c8 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleGeneratorTest.java @@ -20,77 +20,36 @@ import static org.junit.jupiter.api.Assertions.assertEquals; 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.MathContext; import java.math.RoundingMode; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; 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.DaysInYearType; 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.extension.ExtendWith; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class LoanScheduleGeneratorTest { private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(); - private static MockedStatic moneyHelper = Mockito.mockStatic(MoneyHelper.class); 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 NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99); private static final int NUMBER_OF_REPAYMENTS = 6; private static final int REPAYMENT_FREQUENCY = 1; private static final String REPAYMENT_FREQUENCY_TYPE = "MONTHS"; private static final LocalDate DISBURSEMENT_DATE = LocalDate.of(2024, 1, 15); - - @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(); - } + private static final MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN); @Test void testGenerateLoanSchedule() { - 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); - - final MathContext mc = MoneyHelper.getMathContext(); - final List 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 periods = loanSchedule.getPeriods(); + List periods = getLoanScheduleModelPeriods(); assertEquals(7, periods.size(), "Expected 7 periods including the downpayment period."); @@ -111,6 +70,18 @@ void testGenerateLoanSchedule() { BigDecimal.valueOf(0.27), BigDecimal.valueOf(32.87), BigDecimal.ZERO); } + private static List getLoanScheduleModelPeriods() { + LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY, + DISBURSEMENT_AMOUNT, DISBURSEMENT_DATE, NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE, + NOMINAL_INTEREST_RATE, true, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null); + + 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, LocalDate expectedDueDate, BigDecimal expectedPrincipalDue, BigDecimal expectedInterestDue, BigDecimal expectedTotalDue, BigDecimal expectedOutstandingLoanBalance) { @@ -123,10 +94,4 @@ private void checkPeriod(LoanScheduleModelPeriod period, int expectedPeriodNumbe assertEquals(0, expectedTotalDue.compareTo(repaymentPeriod.getTotalDue().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); - } } diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java index 1f7b6cb8732..1137b2e681c 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java @@ -59,6 +59,7 @@ class ProgressiveEMICalculatorTest { private static MockedStatic threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class); private static MockedStatic 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 final MonetaryCurrency monetaryCurrency = MonetaryCurrency @@ -95,7 +96,7 @@ private BigDecimal getRateFactorsByMonth(final DaysInYearType daysInYearType, fi final BigDecimal daysInYear = BigDecimal.valueOf(daysInYearType.getNumberOfDays(period.getFromDate())); final BigDecimal daysInMonth = BigDecimal.valueOf(daysInMonthType.getNumberOfDays(period.getFromDate())); final BigDecimal rateFactor = emiCalculator.rateFactorByRepaymentEveryMonth(interestRate, BigDecimal.ONE, daysInMonth, daysInYear, - daysInPeriod, daysInPeriod); + daysInPeriod, daysInPeriod, mc); return rateFactor.setScale(12, MoneyHelper.getRoundingMode()); } @@ -145,7 +146,7 @@ public void testFnValueFunction_RepayEvery1Month_DayInYear365_DaysInMonthActual( BigDecimal rateFactorPlus1 = getRateFactorsByMonth(daysInYearType, daysInMonthType, interestRate, period).add(BigDecimal.ONE, MoneyHelper.getMathContext()); - final BigDecimal currentFnValue = emiCalculator.fnValue(previousFnValue, rateFactorPlus1); + final BigDecimal currentFnValue = emiCalculator.fnValue(previousFnValue, rateFactorPlus1, mc); fnValuesCalculated.add(currentFnValue); previousFnValue = currentFnValue; @@ -171,7 +172,7 @@ public void testEMICalculator_generateInterestScheduleModel() { Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator - .generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf); + .generateInterestScheduleModel(expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); Assertions.assertTrue(interestScheduleModel != null); Assertions.assertTrue(interestScheduleModel.loanProductRelatedDetail() != null); @@ -211,7 +212,7 @@ public void testEMICalculation_performance() { Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -256,7 +257,7 @@ public void testEMICalculation_CheckEmiButNewEmiNotBetterThanOriginal() { Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -293,7 +294,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -330,7 +331,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -370,7 +371,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -414,7 +415,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -460,7 +461,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -577,7 +578,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -661,7 +662,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonth30_repay threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = toMoney(100.0); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -739,7 +740,7 @@ public void testEMICalculation_multiDisbursedAmt300InSamePeriod_dayInYears360_da Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -788,7 +789,7 @@ public void testEMICalculation_multiDisbursedAmt200InDifferentPeriod_dayInYears3 Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -837,7 +838,7 @@ public void testEMICalculation_multiDisbursedAmt150InSamePeriod_dayInYears360_da Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 5), disbursedAmount); @@ -881,7 +882,7 @@ public void testEMICalculation_disbursedAmt100_dayInYearsActual_daysInMonthActua Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2023, 12, 12), disbursedAmount); @@ -915,7 +916,7 @@ public void testEMICalculation_disbursedAmt1000_NoInterest_repayEvery1Month() { Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(1000)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -948,7 +949,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears364_daysInMonthActual_r Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -981,7 +982,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears364_daysInMonthActual_r Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -1014,7 +1015,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonthDoesntMa Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generateInterestScheduleModel(expectedRepaymentPeriods, - loanProductRelatedDetail, installmentAmountInMultiplesOf); + loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); final Money disbursedAmount = Money.of(monetaryCurrency, BigDecimal.valueOf(100)); emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), disbursedAmount); @@ -1031,7 +1032,7 @@ public void testEMICalculation_disbursedAmt100_dayInYears360_daysInMonthDoesntMa private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) { final Money zeroAmount = Money.zero(monetaryCurrency); return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount, - zeroAmount, zeroAmount, false); + zeroAmount, zeroAmount, false, mc); } @NotNull diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java index 345e23c3350..b3aad08fb32 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/CheckLoanRepaymentOverdueBusinessStepTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.when; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.time.ZoneId; @@ -88,6 +89,7 @@ public void tearDown() { @BeforeAll 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); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java index 3abad3fe7d2..6299535e959 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.time.ZoneId; @@ -116,8 +117,12 @@ public class LoanAccountDelinquencyRangeEventSerializerTest { @Mock private AvroDateTimeMapper mapper; + private MockedStatic moneyHelper = Mockito.mockStatic(MoneyHelper.class); + @BeforeEach 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.setActionContext(ActionContext.DEFAULT); ThreadLocalContextUtil @@ -127,6 +132,7 @@ public void setUp() { @AfterEach public void tearDown() { ThreadLocalContextUtil.reset(); + moneyHelper.close(); } @Test @@ -141,7 +147,6 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), LoanAccountData loanAccountData = mock(LoanAccountData.class); CollectionData delinquentData = mock(CollectionData.class); MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1); - MockedStatic moneyHelper = Mockito.mockStatic(MoneyHelper.class); String delinquentDateAsStr = "2022-12-01"; LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr); @@ -168,8 +173,6 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), when(loanChargeReadPlatformService.retrieveLoanCharges(anyLong())).thenAnswer(a -> repaymentScheduleInstallments.get(0) .getInstallmentCharges().stream().map(c -> c.getLoanCharge().toData()).collect(Collectors.toList())); - moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); - // when LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event); @@ -190,8 +193,6 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), assertEquals(0, data.getAmount().getFeeAmount().compareTo(new BigDecimal("5.0"))); assertEquals(0, data.getAmount().getPenaltyAmount().compareTo(new BigDecimal("50.0"))); assertEquals(delinquentDateAsStr, data.getDelinquentDate()); - - moneyHelper.close(); } @Test @@ -206,7 +207,6 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), LoanAccountData loanAccountData = mock(LoanAccountData.class); CollectionData delinquentData = mock(CollectionData.class); MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1); - MockedStatic moneyHelper = Mockito.mockStatic(MoneyHelper.class); String delinquentDateAsStr = "2022-12-01"; LocalDate delinquentDate = LocalDate.parse(delinquentDateAsStr); when(loanForProcessing.getId()).thenReturn(1L); @@ -263,8 +263,6 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), when(loanForProcessing.getLoanCharges()).thenAnswer(a -> repaymentScheduleInstallments.get(0).getInstallmentCharges().stream() .map(c -> c.getLoanCharge()).collect(Collectors.toList())); - moneyHelper.when(() -> MoneyHelper.getRoundingMode()).thenReturn(RoundingMode.UP); - // when LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event); @@ -314,7 +312,6 @@ delinquencyReadPlatformService, new LoanChargeDataMapperImpl(null, null, null), 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.getCharges().size()); - moneyHelper.close(); } @Test diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java index 3728a6bd94c..3ea0da970b4 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanRepaymentBusinessEventSerializerTest.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.time.ZoneId; @@ -85,7 +86,8 @@ public void setUp() { ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT); ThreadLocalContextUtil .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 diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java index e4da2691a70..037120297f4 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/LoanDelinquencyDomainServiceTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.time.ZoneId; @@ -104,7 +105,8 @@ public void setUp() { currency = new MonetaryCurrency("USD", 2, null); 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 diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java index 98034716ff4..a93177cde90 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent; @@ -55,7 +56,8 @@ class DefaultLoanLifecycleStateMachineTest { public void setUp() { 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); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java index 19213fdf149..38f5369efc7 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.refEq; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.util.HashMap; @@ -87,6 +88,7 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio @BeforeAll 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); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java index b566bb29d7f..6567ae6c5ae 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.refEq; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.util.HashMap; @@ -88,6 +89,7 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio @BeforeAll 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); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java index 3dc81175d65..4a212eee558 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java @@ -37,6 +37,8 @@ import static org.mockito.BDDMockito.given; import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; import java.time.LocalDate; import java.util.List; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -82,6 +84,7 @@ public void setUp() { public void test_generateRepaymentPeriods() { // given HolidayDetailDTO holidayDetailDTO = createHolidayDTO(); + MathContext mathContext = new MathContext(12, RoundingMode.HALF_EVEN); ApplicationCurrency dollarCurrency = new ApplicationCurrency("USD", "US Dollar", 2, 0, "currency.USD", "$"); Money principalAmount = Money.of(fromApplicationCurrency(dollarCurrency), BigDecimal.valueOf(100)); @@ -98,8 +101,8 @@ public void test_generateRepaymentPeriods() { submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null); // when - List result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms, - holidayDetailDTO); + List result = underTest.generateRepaymentPeriods(mathContext, expectedDisbursementDate, + loanApplicationTerms, holidayDetailDTO); // then assertThat(result.size()).isEqualTo(4); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java index def517ca903..599382e15a4 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanCalculateRepaymentPastDueServiceTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.when; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.time.ZoneId; @@ -64,7 +65,8 @@ public void setUp() { ThreadLocalContextUtil .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault())))); 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 diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java index 0bfa16ad207..5670c7869bf 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.math.MathContext; import java.math.RoundingMode; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; @@ -75,7 +76,8 @@ public class LoanDownPaymentHandlerServiceImplTest { @BeforeEach public void setUp() { 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