From bb8480a03dc18b4b1bfc5abc78bfebcf157b8910 Mon Sep 17 00:00:00 2001 From: Janos Meszaros Date: Tue, 10 Dec 2024 12:21:24 +0100 Subject: [PATCH] FINERACT-1981: Embeddable Progressive Loan Schedule generator --- .../build-embeddable-progressive-loan-jar.yml | 51 +++++ build.gradle | 9 +- .../infrastructure/core/service/MathUtil.java | 4 +- .../monetary/domain/ApplicationCurrency.java | 4 - .../monetary/domain/MonetaryCurrency.java | 18 +- .../organisation/monetary/domain/Money.java | 139 +++++++------ .../monetary/domain/OrganisationCurrency.java | 5 + .../data/ScheduleGeneratorDTO.java | 12 +- .../portfolio/loanaccount/domain/Loan.java | 6 +- .../loanschedule/data/LoanScheduleParams.java | 16 +- ...stractCumulativeLoanScheduleGenerator.java | 59 +++--- .../domain/DefaultScheduledDateGenerator.java | 28 ++- .../domain/LoanApplicationTerms.java | 83 ++++---- .../LoanRepaymentScheduleModelData.java | 4 +- .../domain/LoanScheduleModel.java | 35 ++-- .../domain/ScheduleCurrentPeriodParams.java | 6 +- .../domain/LoanRescheduleModel.java | 118 ----------- .../LoanProductRelatedDetailMinimumData.java | 186 ++++++++++++++++++ ...MinimumRepaymentScheduleRelatedDetail.java | 8 +- .../domain/LoanProductRelatedDetail.java | 14 +- .../README.md | 63 ++++++ .../build.gradle | 41 ++++ .../dependencies.gradle | 26 +++ .../misc/Main.java | 84 ++++++++ ...dableProgressiveLoanScheduleGenerator.java | 42 ++++ .../impl/ProgressiveTransactionCtx.java | 2 +- .../loanschedule/data/InterestPeriod.java | 2 +- .../loanschedule/data/LoanSchedulePlan.java | 2 +- .../ProgressiveLoanInterestScheduleModel.java | 33 ++-- .../ProgressiveLoanScheduleGenerator.java | 28 ++- .../loanproduct/calc/EMICalculator.java | 10 +- .../calc/ProgressiveEMICalculator.java | 38 ++-- ...ymentScheduleTransactionProcessorTest.java | 2 +- .../domain/LoanScheduleGeneratorTest.java | 6 +- .../calc/ProgressiveEMICalculatorTest.java | 63 +++--- .../service/LoanScheduleAssembler.java | 4 +- .../loanaccount/service/LoanUtilService.java | 2 +- ...nRepaymentBusinessEventSerializerTest.java | 10 +- .../DefaultScheduledDateGeneratorTest.java | 17 +- ...nCalculateRepaymentPastDueServiceTest.java | 5 +- settings.gradle | 1 + 41 files changed, 848 insertions(+), 438 deletions(-) create mode 100644 .github/workflows/build-embeddable-progressive-loan-jar.yml delete mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java create mode 100644 fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java create mode 100644 fineract-progressive-loan-embeddable-schedule-generator/README.md create mode 100644 fineract-progressive-loan-embeddable-schedule-generator/build.gradle create mode 100644 fineract-progressive-loan-embeddable-schedule-generator/dependencies.gradle create mode 100644 fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java create mode 100644 fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java diff --git a/.github/workflows/build-embeddable-progressive-loan-jar.yml b/.github/workflows/build-embeddable-progressive-loan-jar.yml new file mode 100644 index 00000000000..0cf91e92553 --- /dev/null +++ b/.github/workflows/build-embeddable-progressive-loan-jar.yml @@ -0,0 +1,51 @@ +name: Fineract Build Progressive Loan Embeddable Jar & Test with a Sample Application +on: [push, pull_request] + +permissions: + contents: read + + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 0 + - name: Set up JDK 17 + uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4 + with: + java-version: '17' + distribution: 'zulu' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1 + - name: Build Embeddable Jar + run: ./gradlew --no-daemon --console=plain :fineract-progressive-loan-embeddable-schedule-generator:shadowJar + - name: Pick up the JAR filename + run: | + EMBEDDABLE_JAR_FILE=(`ls fineract-progressive-loan-embeddable-schedule-generator/build/libs/*-all.jar | head -n 1`) + echo "EMBEDDABLE_JAR_FILE=$EMBEDDABLE_JAR_FILE" >> $GITHUB_ENV + - name: Build Sample Application + run: | + mkdir sample-app + javac -cp $EMBEDDABLE_JAR_FILE -d sample-app fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java + env: + EMBEDDABLE_JAR_FILE: ${{ env.EMBEDDABLE_JAR_FILE }} + + - name: Run Schedule Generator Sample Application + run: | + java -cp $EMBEDDABLE_JAR_FILE:sample-app Main + java -cp $EMBEDDABLE_JAR_FILE:sample-app Main 25 + env: + EMBEDDABLE_JAR_FILE: ${{ env.EMBEDDABLE_JAR_FILE }} + + - name: Archive test results + if: always() + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4 + with: + name: test-results + path: | + build/reports/ + fineract-progressive-loan-embeddable-schedule-generator/build/reports/ diff --git a/build.gradle b/build.gradle index 152e9d3d3c6..b2a2166162d 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,8 @@ buildscript { 'fineract-avro-schemas', 'fineract-e2e-tests-core', 'fineract-e2e-tests-runner', - 'fineract-progressive-loan' + 'fineract-progressive-loan', + 'fineract-progressive-loan-embeddable-schedule-generator' ].contains(it.name) } fineractPublishProjects = subprojects.findAll{ @@ -66,7 +67,8 @@ buildscript { 'fineract-report', 'fineract-branch', 'fineract-document', - 'fineract-progressive-loan' + 'fineract-progressive-loan', + 'fineract-progressive-loan-embeddable-schedule-generator' ].contains(it.name) } npmRepository = 'https://npm.pkg.github.com' @@ -109,6 +111,7 @@ plugins { id 'se.thinkcode.cucumber-runner' version '0.0.11' apply false id "com.github.davidmc24.gradle.plugin.avro-base" version "1.9.1" apply false id 'org.openapi.generator' version '7.8.0' apply false + id 'com.gradleup.shadow' version '8.3.5' apply false } apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.release.gradle" @@ -169,7 +172,7 @@ allprojects { groovyGradle { target '*.gradle', '**/*.gradle' - targetExclude '**/build/**' + targetExclude '**/build/**', '/**/build.gradle' // TODO: temporary disabled on build.gradle until someone fix this issue: https://github.com/diffplug/spotless/issues/1807 greclipse() indentWithSpaces(4) endWithNewline() 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 99365b6c235..a543a3f1dad 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 @@ -365,12 +365,12 @@ public static Money zeroToNull(Money value) { /** @return parameter value or ZERO if it is negative */ public static Money negativeToZero(Money value) { - return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrency()); + return value == null || isGreaterThanZero(value) ? value : Money.zero(value.getCurrencyData()); } /** @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); + return value == null || isGreaterThanZero(value, mc) ? value : Money.zero(value.getCurrencyData(), mc); } public static boolean isEmpty(Money value) { diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrency.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrency.java index 4eb1f59af85..d79e2562e5c 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrency.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/ApplicationCurrency.java @@ -98,10 +98,6 @@ public CurrencyData toData() { return new CurrencyData(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.displaySymbol, this.nameCode); } - public CurrencyData toData(final int digitsAfterDecimalSupported, final Integer inMultiplesOf) { - return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, inMultiplesOf, this.displaySymbol, this.nameCode); - } - public OrganisationCurrency toOrganisationCurrency() { return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.nameCode, this.displaySymbol); } diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java index cbf4f2f8d87..dd50f7bdeca 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/MonetaryCurrency.java @@ -34,6 +34,8 @@ public class MonetaryCurrency { @Column(name = "currency_multiplesof") private Integer inMultiplesOf; + private transient CurrencyData currencyData; + protected MonetaryCurrency() { this.code = null; this.digitsAfterDecimal = 0; @@ -46,6 +48,13 @@ public MonetaryCurrency(final String code, final int digitsAfterDecimal, final I this.inMultiplesOf = inMultiplesOf; } + public MonetaryCurrency(final CurrencyData currencyData) { + this.currencyData = currencyData; + this.code = currencyData.getCode(); + this.digitsAfterDecimal = currencyData.getDecimalPlaces(); + this.inMultiplesOf = currencyData.getInMultiplesOf(); + } + public MonetaryCurrency copy() { return new MonetaryCurrency(this.code, this.digitsAfterDecimal, this.inMultiplesOf); } @@ -56,7 +65,14 @@ public static MonetaryCurrency fromApplicationCurrency(ApplicationCurrency appli } public static MonetaryCurrency fromCurrencyData(final CurrencyData currencyData) { - return new MonetaryCurrency(currencyData.getCode(), currencyData.getDecimalPlaces(), currencyData.getInMultiplesOf()); + return new MonetaryCurrency(currencyData); + } + + public CurrencyData toData() { + if (currencyData == null) { + currencyData = new CurrencyData(code, digitsAfterDecimal, inMultiplesOf); + } + return currencyData; } public String getCode() { 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 e70974cc6ad..ecab17b3c9c 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,43 +21,59 @@ import java.math.BigDecimal; import java.math.MathContext; import java.util.Iterator; -import lombok.Getter; import org.apache.fineract.organisation.monetary.data.CurrencyData; -@Getter public class Money implements Comparable { - private final String currencyCode; - private final int currencyDigitsAfterDecimal; - private final Integer inMultiplesOf; private final BigDecimal amount; + private final CurrencyData currency; private final transient MathContext mc; protected Money() { - this.currencyCode = null; - this.currencyDigitsAfterDecimal = 0; - this.inMultiplesOf = 0; - this.amount = null; + this.currency = CurrencyData.blank(); + this.amount = BigDecimal.ZERO; this.mc = getMc(); } - private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf, - final MathContext mc) { - this.currencyCode = currencyCode; - this.currencyDigitsAfterDecimal = digitsAfterDecimal; - this.inMultiplesOf = inMultiplesOf; + private Money(final CurrencyData currency, final BigDecimal amount, final MathContext mc) { + this.currency = currency; this.mc = mc; final BigDecimal amountZeroed = defaultToZeroIfNull(amount); BigDecimal amountScaled = amountZeroed.stripTrailingZeros(); // round monetary amounts into multiples of say 20/50. - if (inMultiplesOf != null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf > 0 && amountScaled.doubleValue() > 0) { + if (currency.getInMultiplesOf() != null && currency.getDecimalPlaces() == 0 && currency.getInMultiplesOf() > 0 + && amountScaled.doubleValue() > 0) { final double existingVal = amountScaled.doubleValue(); - amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf)); + amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, currency.getInMultiplesOf())); } - this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, getMc().getRoundingMode()); + this.amount = amountScaled.setScale(currency.getDecimalPlaces(), getMc().getRoundingMode()); + } + + public MonetaryCurrency getCurrency() { + return MonetaryCurrency.fromCurrencyData(currency); + } + + public CurrencyData getCurrencyData() { + return currency; + } + + public String getCurrencyCode() { + return currency.getCode(); + } + + public Integer getInMultiplesOf() { + return currency.getInMultiplesOf(); + } + + public BigDecimal getAmount() { + return amount; + } + + public BigDecimal getAmountDefaultedToNullIfZero() { + return defaultToNullIfZero(this.amount); } public static Money total(final Money... monies) { @@ -83,21 +99,20 @@ 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 CurrencyData currency, final BigDecimal newAmount) { + return of(currency, newAmount, MoneyHelper.getMathContext()); } - public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) { - return of(currency, newAmount, MoneyHelper.getMathContext()); + public static Money of(final CurrencyData currency, final BigDecimal newAmount, final MathContext mc) { + return new Money(currency, newAmount, mc); } - public static Money of(final CurrencyData currency, final BigDecimal newAmount) { - return of(currency, newAmount, MoneyHelper.getMathContext()); + public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount, final MathContext mc) { + return new Money(currency.toData(), newAmount, mc); } - 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 of(final MonetaryCurrency currency, final BigDecimal newAmount) { + return of(currency, newAmount, MoneyHelper.getMathContext()); } public static Money zero(final MonetaryCurrency currency) { @@ -105,11 +120,11 @@ public static Money zero(final MonetaryCurrency currency) { } public static Money zero(final MonetaryCurrency currency, MathContext mc) { - return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), BigDecimal.ZERO, currency.getCurrencyInMultiplesOf(), mc); + return new Money(currency.toData(), BigDecimal.ZERO, mc); } public static Money zero(final CurrencyData currency, MathContext mc) { - return new Money(currency.getCode(), currency.getDecimalPlaces(), BigDecimal.ZERO, currency.getInMultiplesOf(), mc); + return new Money(currency, BigDecimal.ZERO, mc); } public static Money zero(final CurrencyData currency) { @@ -151,7 +166,7 @@ public static Money roundToMultiplesOf(final Money existingVal, final Integer in if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) { amountScaled = amountScaled.divide(inMultiplesOfValue, 0, mc.getRoundingMode()).multiply(inMultiplesOfValue); } - return Money.of(existingVal.getCurrency(), amountScaled); + return Money.of(existingVal.getCurrencyData(), amountScaled); } public static double ceiling(final double n, final double s) { @@ -195,11 +210,11 @@ private static BigDecimal defaultToNullIfZero(final BigDecimal value) { } public Money copy() { - return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros(), this.inMultiplesOf, this.mc); + return new Money(this.currency, this.amount, this.mc); } public Money copy(final BigDecimal amount) { - return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, amount.stripTrailingZeros(), this.inMultiplesOf, this.mc); + return new Money(this.currency, amount, this.mc); } public Money copy(final double amount) { @@ -212,7 +227,7 @@ public Money plus(final Iterable moniesToAdd) { final Money money = checkCurrencyEqual(moneyProvider); total = total.add(money.amount); } - return Money.of(monetaryCurrency(), total); + return Money.of(getCurrencyData(), total); } public Money plus(final Money moneyToAdd) { @@ -233,7 +248,7 @@ public Money plus(final BigDecimal amountToAdd, MathContext mc) { return this; } final BigDecimal newAmount = this.amount.add(amountToAdd); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } public Money plus(final double amountToAdd) { @@ -241,7 +256,7 @@ public Money plus(final double amountToAdd) { return this; } final BigDecimal newAmount = this.amount.add(BigDecimal.valueOf(amountToAdd)); - return Money.of(monetaryCurrency(), newAmount); + return Money.of(getCurrencyData(), newAmount); } public Money minus(final Money moneyToSubtract) { @@ -271,7 +286,7 @@ public Money add(final BigDecimal amountToAdd, final MathContext mc) { return this; } final BigDecimal newAmount = this.amount.add(amountToAdd); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } public Money minus(final BigDecimal amountToSubtract) { @@ -283,7 +298,7 @@ public Money minus(final BigDecimal amountToSubtract, final MathContext mc) { return this; } final BigDecimal newAmount = this.amount.subtract(amountToSubtract); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } private Money checkCurrencyEqual(final Money money) { @@ -294,7 +309,7 @@ private Money checkCurrencyEqual(final Money money) { } public boolean isSameCurrency(final Money money) { - return this.currencyCode.equals(money.getCurrencyCode()); + return getCurrencyCode().equals(money.getCurrencyCode()); } public Money dividedBy(final BigDecimal valueToDivideBy, final MathContext mc) { @@ -302,7 +317,7 @@ public Money dividedBy(final BigDecimal valueToDivideBy, final MathContext mc) { return this; } final BigDecimal newAmount = this.amount.divide(valueToDivideBy, mc); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } public Money dividedBy(final double valueToDivideBy, final MathContext mc) { @@ -310,7 +325,7 @@ public Money dividedBy(final double valueToDivideBy, final MathContext mc) { return this; } final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), mc); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } public Money dividedBy(final long valueToDivideBy, final MathContext mc) { @@ -318,7 +333,7 @@ public Money dividedBy(final long valueToDivideBy, final MathContext mc) { return this; } final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), mc); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } public Money dividedBy(final long valueToDivideBy) { @@ -326,7 +341,7 @@ public Money dividedBy(final long valueToDivideBy) { return this; } final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), getMc()); - return Money.of(monetaryCurrency(), newAmount, getMc()); + return Money.of(getCurrencyData(), newAmount, getMc()); } public Money multipliedBy(final BigDecimal valueToMultiplyBy) { @@ -338,7 +353,7 @@ public Money multipliedBy(final BigDecimal valueToMultiplyBy, final MathContext return this; } final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy, mc); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } public Money multipliedBy(final double valueToMultiplyBy) { @@ -346,7 +361,7 @@ public Money multipliedBy(final double valueToMultiplyBy) { return this; } final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy)); - return Money.of(monetaryCurrency(), newAmount); + return Money.of(getCurrencyData(), newAmount); } public Money multipliedBy(final long valueToMultiplyBy) { @@ -358,7 +373,7 @@ public Money multipliedBy(final long valueToMultiplyBy, final MathContext mc) { return this; } final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy), mc); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final MathContext mc) { @@ -366,8 +381,8 @@ public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final MathC return this; } BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy, mc); - newAmount = newAmount.setScale(this.currencyDigitsAfterDecimal, mc.getRoundingMode()); - return Money.of(monetaryCurrency(), newAmount, mc); + newAmount = newAmount.setScale(this.currency.getDecimalPlaces(), mc.getRoundingMode()); + return Money.of(getCurrencyData(), newAmount, mc); } public Money multiplyRetainScale(final double valueToMultiplyBy, final MathContext mc) { @@ -376,12 +391,12 @@ public Money multiplyRetainScale(final double valueToMultiplyBy, final MathConte public Money percentageOf(BigDecimal percentage, final MathContext mc) { final BigDecimal newAmount = this.amount.multiply(percentage).divide(BigDecimal.valueOf(100), mc); - return Money.of(monetaryCurrency(), newAmount, mc); + return Money.of(getCurrencyData(), newAmount, mc); } @Override public int compareTo(final Money other) { - if (!this.currencyCode.equals(other.currencyCode)) { + if (!this.getCurrencyCode().equals(other.getCurrencyCode())) { throw new UnsupportedOperationException("currencies arent different"); } return this.amount.compareTo(other.amount); @@ -392,7 +407,7 @@ public boolean isZero() { } public boolean isZero(final MathContext mc) { - return isEqualTo(Money.zero(getCurrency(), mc)); + return isEqualTo(Money.zero(getCurrencyData(), mc)); } public boolean isEqualTo(final Money other) { @@ -416,7 +431,7 @@ public boolean isGreaterThanZero() { } public boolean isGreaterThanZero(MathContext mc) { - return isGreaterThan(Money.zero(getCurrency(), mc)); + return isGreaterThan(Money.zero(getCurrencyData(), mc)); } public boolean isLessThan(final Money other) { @@ -428,20 +443,12 @@ public boolean isLessThanZero() { } public boolean isLessThanZero(final MathContext mc) { - return isLessThan(Money.zero(getCurrency(), mc)); - } - - public Integer getCurrencyInMultiplesOf() { - return this.inMultiplesOf; - } - - public BigDecimal getAmountDefaultedToNullIfZero() { - return defaultToNullIfZero(this.amount); + return isLessThan(Money.zero(getCurrencyData(), mc)); } @Override public String toString() { - return new StringBuilder().append(this.currencyCode).append(' ').append(this.amount.toPlainString()).toString(); + return this.getCurrencyCode() + ' ' + this.amount.toPlainString(); } public Money negated() { @@ -452,7 +459,7 @@ public Money negated(final MathContext mc) { if (isZero(mc)) { return this; } - return Money.of(monetaryCurrency(), this.amount.negate(), mc); + return Money.of(getCurrencyData(), this.amount.negate(), mc); } public Money abs() { @@ -463,20 +470,12 @@ public Money abs(MathContext mc) { return isLessThanZero(mc) ? negated(mc) : this; } - public MonetaryCurrency getCurrency() { - return monetaryCurrency(); - } - - private MonetaryCurrency monetaryCurrency() { - return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal, this.inMultiplesOf); - } - public Money zero() { return zero(getMc()); } public Money zero(MathContext mc) { - return Money.zero(getCurrency(), mc); + return Money.zero(getCurrencyData(), mc); } public MathContext getMc() { diff --git a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/OrganisationCurrency.java b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/OrganisationCurrency.java index 11bf2e387cf..c5b750df5c9 100644 --- a/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/OrganisationCurrency.java +++ b/fineract-core/src/main/java/org/apache/fineract/organisation/monetary/domain/OrganisationCurrency.java @@ -22,6 +22,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Table; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.organisation.monetary.data.CurrencyData; /** * Represents currencies allowed for this MFI/organisation. @@ -74,4 +75,8 @@ public final String getCode() { public final MonetaryCurrency toMonetaryCurrency() { return new MonetaryCurrency(this.code, this.decimalPlaces, this.inMultiplesOf); } + + public final CurrencyData toData() { + return new CurrencyData(this.code, this.decimalPlaces, this.inMultiplesOf); + } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java index 8fc33719e66..6d85063bc93 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java @@ -19,7 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.data; import java.time.LocalDate; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; @@ -29,7 +29,7 @@ public class ScheduleGeneratorDTO { final LoanScheduleGeneratorFactory loanScheduleFactory; - final ApplicationCurrency applicationCurrency; + final CurrencyData currency; final LocalDate calculatedRepaymentsStartingFromDate; final HolidayDetailDTO holidayDetailDTO; final CalendarInstance calendarInstanceForInterestRecalculation; @@ -47,7 +47,7 @@ public class ScheduleGeneratorDTO { final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI; final boolean isPrincipalCompoundingDisabledForOverdueLoans; - public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, + public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final CurrencyData currency, final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance, final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO, @@ -58,7 +58,7 @@ public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFacto final boolean isPrincipalCompoundingDisabledForOverdueLoans) { this.loanScheduleFactory = loanScheduleFactory; - this.applicationCurrency = applicationCurrency; + this.currency = currency; this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation; this.compoundingCalendarInstance = compoundingCalendarInstance; @@ -81,8 +81,8 @@ public LoanScheduleGeneratorFactory getLoanScheduleFactory() { return this.loanScheduleFactory; } - public ApplicationCurrency getApplicationCurrency() { - return this.applicationCurrency; + public CurrencyData getCurrency() { + return this.currency; } public LocalDate getCalculatedRepaymentsStartingFromDate() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 7107b8d8b71..280693da5fe 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -2781,8 +2781,8 @@ public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGenerato interestChargedFromDate = getDisbursementDate(); } - return LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(), loanTermFrequency, - loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), + return LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getCurrency(), loanTermFrequency, loanTermPeriodFrequencyType, + nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), @@ -3053,7 +3053,7 @@ public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency ap List loanTermVariations = new ArrayList<>(); annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations); - return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, + return LoanApplicationTerms.assembleFrom(applicationCurrency.toData(), loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, maxOutstandingBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), 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 635b62bd704..2c4e95993bf 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 @@ -26,7 +26,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; @@ -97,7 +97,7 @@ public void setCompoundedInLastInstallment(Money compoundedInLastInstallment) { private final LocalDate scheduleTillDate; private final boolean partialUpdate; private int loanTermInDays; - private final MonetaryCurrency currency; + private final CurrencyData currency; private final boolean applyInterestRecalculation; private final MathContext mc; @@ -110,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 MathContext mc) { + final boolean partialUpdate, final CurrencyData currency, final boolean applyInterestRecalculation, final MathContext mc) { this.periodNumber = periodNumber; this.instalmentNumber = instalmentNumber; this.loanTermInDays = loanTermInDays; @@ -153,7 +153,7 @@ 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 MathContext mc) { + final CurrencyData currency, final boolean applyInterestRecalculation, final MathContext mc) { final boolean partialUpdate = true; return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, @@ -187,7 +187,7 @@ public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final final boolean partialUpdate = false; final int loanTermInDays = 0; final Money totalOutstandingInterestPaymentDueToGrace = null; - final MonetaryCurrency currency = null; + final CurrencyData currency = null; final Money unCompoundedAmount = null; return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, @@ -197,7 +197,7 @@ public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); } - public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, + public static LoanScheduleParams createLoanScheduleParams(final CurrencyData currency, final Money chargesDueAtTimeOfDisbursement, final LocalDate periodStartDate, final Money principalToBeScheduled, final MathContext mc) { final int loanTermInDays = 0; final int periodNumber = 1; @@ -231,7 +231,7 @@ public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); } - public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, + public static LoanScheduleParams createLoanScheduleParams(final CurrencyData currency, final Money chargesDueAtTimeOfDisbursement, final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams, MathContext mc) { final int loanTermInDays = 0; @@ -500,7 +500,7 @@ public Map> getCompoundingDateVariations() { return this.compoundingDateVariations; } - public MonetaryCurrency getCurrency() { + public CurrencyData getCurrency() { return this.currency; } 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 3f2b4907ef4..149654f7409 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 @@ -37,7 +37,7 @@ import java.util.TreeMap; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.MathUtil; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; @@ -74,7 +74,6 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams loanScheduleParams) { - final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // generate list of proposed schedule due dates LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations() @@ -90,7 +89,8 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // setup variables for tracking important facts required for loan // schedule generation. - final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); + final CurrencyData currency = loanApplicationTerms.getCurrency(); + final MonetaryCurrency monetaryCurrency = MonetaryCurrency.fromCurrencyData(currency); LoanScheduleParams scheduleParams; LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) ? loanApplicationTerms.getExpectedDisbursementDate() @@ -252,7 +252,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe if (loanApplicationTerms.isInterestRecalculationEnabled()) { populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, - holidayDetailDTO, scheduleParams, loanCharges, currency, mc); + holidayDetailDTO, scheduleParams, loanCharges, monetaryCurrency, mc); } // populates the collection with transactions till the due date of @@ -330,7 +330,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe } // applies charges for the period - applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams, mc); + applyChargesForCurrentPeriod(loanCharges, monetaryCurrency, scheduleParams, scheduledDueDate, currentPeriodParams, mc); // sum up real totalInstallmentDue from components final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod(); @@ -354,7 +354,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); // apply loan transactions on installments to identify early/late // payments for interest recalculation - installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, + installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, monetaryCurrency, scheduleParams, loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams, lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges); @@ -398,7 +398,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe // determine fees and penalties for charges which depends on total // loan interest - updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc); + updatePeriodsWithCharges(monetaryCurrency, scheduleParams, periods, nonCompoundingCharges, mc); // this block is to add extra re-payment schedules with interest portion // if the loan not paid with in loan term @@ -408,8 +408,9 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe } if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0 && DateUtils.isAfter(currentDate, scheduleParams.getPeriodStartDate())) { - Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO, currency, - periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges, scheduleParams); + Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO, + monetaryCurrency, periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges, + scheduleParams); scheduleParams.addTotalCumulativeInterest(totalInterest); } @@ -418,7 +419,7 @@ private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTe final BigDecimal totalOutstanding = BigDecimal.ZERO; updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms); - return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), + return LoanScheduleModel.from(periods, currency, scheduleParams.getLoanTermInDays(), scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()), scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), @@ -534,7 +535,7 @@ private LoanScheduleModelPeriod handleRecalculationForTransactions(final MathCon modifiedInstallment = handlePrepaymentOfLoan(mc, loanApplicationTerms, holidayDetailDTO, scheduleParams, totalInterestChargedForFullLoanTerm, scheduledDueDate, periodStartDateApplicableForInterest, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), currentPeriodParams, - lastTotalOutstandingInterestPaymentDueToGrace, transactionDate, modifiedInstallment, loanCharges); + lastTotalOutstandingInterestPaymentDueToGrace, transactionDate, modifiedInstallment, loanCharges, currency); Money addToPrincipal = Money.zero(currency); if (scheduleParams.getOutstandingBalance().isLessThanZero()) { @@ -575,17 +576,18 @@ private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, fin final Money totalInterestChargedForFullLoanTerm, final LocalDate scheduledDueDate, LocalDate periodStartDateApplicableForInterest, final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final ScheduleCurrentPeriodParams currentPeriodParams, final Money lastTotalOutstandingInterestPaymentDueToGrace, - final LocalDate transactionDate, final LoanScheduleModelPeriod installment, Set loanCharges) { + final LocalDate transactionDate, final LoanScheduleModelPeriod installment, Set loanCharges, + MonetaryCurrency currency) { LoanScheduleModelPeriod modifiedInstallment = installment; Money outstanding = scheduleParams.getOutstandingBalance(); PrincipalInterest tempPrincipalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(), currentPeriodParams.getInterestForThisPeriod(), null); - outstanding = outstanding.minus(cumulativeFeeChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, - totalInterestChargedForFullLoanTerm.getCurrency(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); - outstanding = outstanding.minus(cumulativePenaltyChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, - totalInterestChargedForFullLoanTerm.getCurrency(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), - scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); + outstanding = outstanding.minus(cumulativeFeeChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, currency, + tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), true, + scheduleParams.isFirstPeriod(), mc)); + outstanding = outstanding.minus(cumulativePenaltyChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, currency, + tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), true, + scheduleParams.isFirstPeriod(), mc)); if (!outstanding.isGreaterThan(currentPeriodParams.getInterestForThisPeriod()) && !scheduledDueDate.equals(transactionDate)) { final Collection interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); @@ -607,7 +609,7 @@ private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, fin new TreeMap<>(scheduleParams.getCompoundingMap())); scheduleParams.getCompoundingMap().clear(); populateCompoundingDatesInPeriod(periodStartDateApplicableForInterest, calculateTill, loanApplicationTerms, holidayDetailDTO, - scheduleParams, loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), mc); + scheduleParams, loanCharges, currency, mc); // this is to make sure we are recalculating using correct interest // rate @@ -628,11 +630,10 @@ private LoanScheduleModelPeriod handlePrepaymentOfLoan(final MathContext mc, fin loanApplicationTerms.updateAnnualNominalInterestRate(currentInterestRate); // applies charges for the period - final ScheduleCurrentPeriodParams tempPeriod = new ScheduleCurrentPeriodParams( - totalInterestChargedForFullLoanTerm.getCurrency(), interestCalculationGraceOnRepaymentPeriodFraction); + final ScheduleCurrentPeriodParams tempPeriod = new ScheduleCurrentPeriodParams(loanApplicationTerms.getCurrency(), + interestCalculationGraceOnRepaymentPeriodFraction); tempPeriod.setInterestForThisPeriod(interestTillDate.interest()); - applyChargesForCurrentPeriod(loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), scheduleParams, calculateTill, - tempPeriod, mc); + applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, calculateTill, tempPeriod, mc); Money interestDiff = currentPeriodParams.getInterestForThisPeriod().minus(tempPeriod.getInterestForThisPeriod()); Money chargeDiff = currentPeriodParams.getFeeChargesForInstallment().minus(tempPeriod.getFeeChargesForInstallment()); Money penaltyDiff = currentPeriodParams.getPenaltyChargesForInstallment().minus(tempPeriod.getPenaltyChargesForInstallment()); @@ -684,8 +685,8 @@ private void updateAmountsBasedOnCurrentEarlyPayments(final MathContext mc, fina .reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance())); } - private void updatePrincipalPortionBasedOnPreviousEarlyPayments(final MonetaryCurrency currency, - final LoanScheduleParams scheduleParams, final ScheduleCurrentPeriodParams currentPeriodParams) { + private void updatePrincipalPortionBasedOnPreviousEarlyPayments(final CurrencyData currency, final LoanScheduleParams scheduleParams, + final ScheduleCurrentPeriodParams currentPeriodParams) { if (currentPeriodParams.getPrincipalForThisPeriod().isGreaterThan(scheduleParams.getReducePrincipal())) { currentPeriodParams.minusPrincipalForThisPeriod(scheduleParams.getReducePrincipal()); scheduleParams.setReducePrincipal(Money.zero(currency)); @@ -711,7 +712,7 @@ private void handleRecalculationForNonDueDateTransactions(final MathContext mc, final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest, final Collection applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) { if (scheduleParams.applyInterestRecalculation()) { - final MonetaryCurrency currency = scheduleParams.getCurrency(); + final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(scheduleParams.getCurrency()); final Collection interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); boolean checkForOutstanding = true; List unprocessedTransactions = new ArrayList<>(); @@ -1682,7 +1683,7 @@ private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerm final LoanScheduleParams params, final LocalDate lastRestDate, final LocalDate scheduledDueDate) { if (loanApplicationTerms.isInterestRecalculationEnabled() && loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { - final MonetaryCurrency currency = params.getCurrency(); + final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(params.getCurrency()); Money totalCompoundedAmount = Money.zero(currency); boolean lastInstallmentIsPastDate = false; for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : params.getInstallments()) { @@ -1802,7 +1803,7 @@ private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams param private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate, final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms) { adjustCompoundedAmountWithPaidDetail(params.getPrincipalPortionMap(), lastRestDate, amountApplicableDate, transaction, - loanApplicationTerms, params.getCurrency()); + loanApplicationTerms, MonetaryCurrency.fromCurrencyData(params.getCurrency())); } private void adjustCompoundedAmountWithPaidDetail(final Map principalPortionMap, final LocalDate lastRestDate, @@ -2493,7 +2494,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, mc); + currency.toData(), applyInterestRecalculation, mc); retainedInstallments.addAll(newRepaymentScheduleInstallments); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged())); diff --git a/fineract-loan/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 index 2b9e1b77cb9..ad25cee25e1 100644 --- a/fineract-loan/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 @@ -20,10 +20,12 @@ import java.math.MathContext; import java.time.LocalDate; +import java.time.YearMonth; +import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; import java.util.ArrayList; import java.util.List; -import lombok.extern.slf4j.Slf4j; import net.fortuna.ical4j.model.Recur; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.holiday.domain.Holiday; @@ -42,7 +44,6 @@ import org.springframework.stereotype.Component; @Component -@Slf4j public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override @@ -121,7 +122,7 @@ public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, fi Calendar currentCalendar = loanApplicationTerms.getLoanCalendar(); dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate); - dueRepaymentPeriodDate = (LocalDate) CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(), + dueRepaymentPeriodDate = (LocalDate) adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(), loanApplicationTerms.getRepaymentPeriodFrequencyType()); if (currentCalendar != null) { // If we have currentCalendar object, this means there is a @@ -154,6 +155,21 @@ public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, fi return dueRepaymentPeriodDate; } + /* + * NOTE: This method a copy of CalendarUtils.adjustDate() method. Purpose of this method copy here is to eliminate + * the whole "ical4j" dependency for the small stateless embeddable progressive loan jar. Registered exception + * brings the whole ical4j deps. + */ + private Temporal adjustDate(final Temporal date, final Temporal seedDate, final PeriodFrequencyType frequencyType) { + if (frequencyType.isMonthly() && seedDate.get(ChronoField.DAY_OF_MONTH) > 28 && date.get(ChronoField.DAY_OF_MONTH) >= 28) { + int noOfDaysInCurrentMonth = YearMonth.from(date).lengthOfMonth(); + int seedDay = seedDate.get(ChronoField.DAY_OF_MONTH); + int adjustedDay = Math.min(noOfDaysInCurrentMonth, seedDay); + return date.with(ChronoField.DAY_OF_MONTH, adjustedDay); + } + return date; + } + @Override public LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment, final Integer periodNumber) { @@ -305,7 +321,7 @@ public LocalDate getRepaymentPeriodDate(final PeriodFrequencyType frequency, fin case INVALID: break; case WHOLE_TERM: - log.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM"); + // TODO: Implement getRepaymentPeriodDate for WHOLE_TERM break; } return dueRepaymentPeriodDate; @@ -347,7 +363,7 @@ public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final case INVALID: break; case WHOLE_TERM: - log.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM"); + // TODO: Implement getRepaymentPeriodDate for WHOLE_TERM break; } return isScheduledDate; @@ -384,7 +400,7 @@ public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFreq case INVALID: break; case WHOLE_TERM: - log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); + // TODO: Implement getRepaymentPeriodDate for WHOLE_TERM break; } 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 f9a3373e125..aff82208350 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 @@ -27,11 +27,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.MathUtil; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; @@ -48,21 +46,22 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour; +import org.apache.fineract.portfolio.loanproduct.data.LoanProductRelatedDetailMinimumData; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; -@Slf4j public final class LoanApplicationTerms { - private ApplicationCurrency currency; + private CurrencyData currency; private Calendar loanCalendar; private Integer loanTermFrequency; @@ -268,7 +267,7 @@ private LoanApplicationTerms(Builder builder) { public static class Builder { - private ApplicationCurrency currency; + private CurrencyData currency; private Integer loanTermFrequency; private PeriodFrequencyType loanTermPeriodFrequencyType; private Integer numberOfRepayments; @@ -292,7 +291,7 @@ public static class Builder { private LocalDate seedDate; private MathContext mc; - public Builder currency(ApplicationCurrency currency) { + public Builder currency(CurrencyData currency) { this.currency = currency; return this; } @@ -408,7 +407,7 @@ public LoanApplicationTerms build() { } public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData, MathContext mc) { - Money principal = Money.of(modelData.currency().toData(), modelData.disbursementAmount(), mc); + Money principal = Money.of(modelData.currency(), modelData.disbursementAmount(), mc); BigDecimal downPaymentPercentage = modelData.downPaymentPercentage(); LocalDate seedDate; @@ -428,12 +427,12 @@ 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(), mc)).disbursementDatas(new ArrayList<>()) + .inArrearsTolerance(Money.zero(modelData.currency(), mc)).disbursementDatas(new ArrayList<>()) .isDownPaymentEnabled(modelData.downPaymentEnabled()).downPaymentPercentage(downPaymentPercentage) .submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate).mc(mc).build(); } - public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, + public static LoanApplicationTerms assembleFrom(final CurrencyData currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, @@ -484,7 +483,7 @@ public static LoanApplicationTerms assembleFrom(final ApplicationCurrency curren } - public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, + public static LoanApplicationTerms assembleFrom(final CurrencyData currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance, @@ -537,27 +536,26 @@ public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applic LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType(); LoanScheduleProcessingType loanScheduleProcessingType = loanProductRelatedDetail.getLoanScheduleProcessingType(); final Integer fixedLength = loanProductRelatedDetail.getFixedLength(); - return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, - interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, - interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, - repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, - inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, - loanProductRelatedDetail.getGraceOnArrearsAgeing(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, - compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, - isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, - allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday, - isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, - isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, - isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType, - fixedLength, loanProductRelatedDetail.isEnableAccrualActivityPosting(), + return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, + repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, interestMethod, + interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, + allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, + calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, + graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, + disbursementDatas, maxOutstandingBalance, loanProductRelatedDetail.getGraceOnArrearsAgeing(), daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, + recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, + calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, + isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, + isFirstRepaymentDateAllowedOnHoliday, isInterestToBeRecoveredFirstWhenGreaterThanEMI, + fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, + disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, + loanScheduleType, loanScheduleProcessingType, fixedLength, loanProductRelatedDetail.isEnableAccrualActivityPosting(), loanProductRelatedDetail.getSupportedInterestRefundTypes(), loanProductRelatedDetail.getChargeOffBehaviour()); } - private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, + private LoanApplicationTerms(final CurrencyData currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer nthDay, final DayOfWeekType weekDayType, final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, @@ -830,7 +828,7 @@ private LocalDate getPeriodEndDate(final LocalDate startDate) { case INVALID: break; case WHOLE_TERM: - log.error("TODO Implement getPeriodEndDate for WHOLE_TERM"); + // TODO: Implement getPeriodEndDate for WHOLE_TERM break; } return dueRepaymentPeriodDate; @@ -1249,7 +1247,7 @@ private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); break; case WHOLE_TERM: - log.error("TODO Implement periodicInterestRate for WHOLE_TERM"); + // TODO: Implement getPeriodEndDate for WHOLE_TERM break; } break; @@ -1533,8 +1531,8 @@ private Money calculateTotalDueForEqualInstallmentRepaymentPeriod(final BigDecim } public LoanProductRelatedDetail toLoanProductRelatedDetail() { - final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces(), - this.currency.getCurrencyInMultiplesOf()); + final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(), + this.currency.getInMultiplesOf()); return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod, this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod, @@ -1548,6 +1546,15 @@ public LoanProductRelatedDetail toLoanProductRelatedDetail() { this.chargeOffBehaviour); } + public LoanProductMinimumRepaymentScheduleRelatedDetail toLoanProductRelatedDetailMinimumData() { + final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(), + this.currency.getInMultiplesOf()); + return new LoanProductRelatedDetailMinimumData(currency, principal, inArrearsTolerance, interestRatePerPeriod, + annualNominalInterestRate, interestChargingGrace, interestPaymentGrace, principalGrace, + recurringMoratoriumOnPrincipalPeriods, interestMethod, interestCalculationPeriodMethod, daysInYearType, daysInMonthType, + amortizationMethod, repaymentPeriodFrequencyType, repaymentEvery, numberOfRepayments); + } + public Integer getLoanTermFrequency() { return this.loanTermFrequency; } @@ -1592,8 +1599,8 @@ public AmortizationMethod getAmortizationMethod() { return this.amortizationMethod; } - public MonetaryCurrency getCurrency() { - return this.principal.getCurrency(); + public CurrencyData getCurrency() { + return currency; } public Integer getNumberOfRepayments() { @@ -1787,10 +1794,6 @@ public void updateTotalInterestDue(Money totalInterestDue) { this.totalInterestDue = totalInterestDue; } - public ApplicationCurrency getApplicationCurrency() { - return this.currency; - } - public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { return this.interestCalculationPeriodMethod; } @@ -2074,7 +2077,7 @@ public LocalDate calculateMaxDateForFixedLength() { case INVALID: break; case WHOLE_TERM: - log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); + // TODO: Implement getPeriodEndDate for WHOLE_TERM break; } return maxDateForFixedLength; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleModelData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleModelData.java index 9695fe97967..3a1894d300b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleModelData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanRepaymentScheduleModelData.java @@ -22,11 +22,11 @@ import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDate; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; -public record LoanRepaymentScheduleModelData(@NotNull LocalDate scheduleGenerationStartDate, @NotNull ApplicationCurrency currency, +public record LoanRepaymentScheduleModelData(@NotNull LocalDate scheduleGenerationStartDate, @NotNull CurrencyData currency, @NotNull BigDecimal disbursementAmount, @NotNull LocalDate disbursementDate, @NotNull int numberOfRepayments, @NotNull int repaymentFrequency, @NotBlank String repaymentFrequencyType, @NotNull BigDecimal annualNominalInterestRate, @NotNull boolean downPaymentEnabled, @NotNull DaysInMonthType daysInMonth, @NotNull DaysInYearType daysInYear, diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java index ee484bfccd0..6f28c5376bb 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java @@ -23,7 +23,6 @@ import java.util.List; import lombok.Getter; import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; @@ -36,7 +35,7 @@ public final class LoanScheduleModel { @Getter private final List periods; - private final ApplicationCurrency applicationCurrency; + private final CurrencyData currency; private final int loanTermInDays; private final Money totalPrincipalDisbursed; private final BigDecimal totalPrincipalExpected; @@ -49,20 +48,19 @@ public final class LoanScheduleModel { private final BigDecimal totalRepaymentExpected; private final BigDecimal totalOutstanding; - public static LoanScheduleModel from(final List periods, final ApplicationCurrency applicationCurrency, - final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, - final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, - final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { + public static LoanScheduleModel from(final List periods, final CurrencyData currency, final int loanTermInDays, + final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, + final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, + final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { - return new LoanScheduleModel(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected, - totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, - totalOutstanding); + return new LoanScheduleModel(periods, currency, loanTermInDays, principalDisbursed, totalPrincipalExpected, totalPrincipalPaid, + totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding); } public static LoanScheduleModel withOverdueChargeUpdation(final List periods, final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) { - return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays, + return new LoanScheduleModel(periods, loanScheduleModel.currency, loanScheduleModel.loanTermInDays, loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid, loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding); @@ -71,18 +69,18 @@ public static LoanScheduleModel withOverdueChargeUpdation(final List periods, final LoanScheduleModel loanScheduleModel) { - return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays, + return new LoanScheduleModel(periods, loanScheduleModel.currency, loanScheduleModel.loanTermInDays, loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid, loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding); } - private LoanScheduleModel(final List periods, final ApplicationCurrency applicationCurrency, - final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, - final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, - final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { + private LoanScheduleModel(final List periods, final CurrencyData currency, final int loanTermInDays, + final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, + final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged, + final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { this.periods = periods; - this.applicationCurrency = applicationCurrency; + this.currency = currency; this.loanTermInDays = loanTermInDays; this.totalPrincipalDisbursed = principalDisbursed; this.totalPrincipalExpected = totalPrincipalExpected; @@ -95,11 +93,6 @@ private LoanScheduleModel(final List periods, final App } public LoanScheduleData toData() { - - final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal(); - final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf(); - final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf); - final BigDecimal totalCredits = BigDecimal.ZERO; final List periodsData = new ArrayList<>(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduleCurrentPeriodParams.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduleCurrentPeriodParams.java index 58ac9a9b1c1..5d6822b85fa 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduleCurrentPeriodParams.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduleCurrentPeriodParams.java @@ -19,7 +19,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.Money; public class ScheduleCurrentPeriodParams { @@ -36,11 +36,11 @@ public class ScheduleCurrentPeriodParams { boolean isEmiAmountChanged; BigDecimal interestCalculationGraceOnRepaymentPeriodFraction; - ScheduleCurrentPeriodParams(final MonetaryCurrency currency) { + ScheduleCurrentPeriodParams(final CurrencyData currency) { this(currency, BigDecimal.ZERO); } - ScheduleCurrentPeriodParams(final MonetaryCurrency currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) { + ScheduleCurrentPeriodParams(final CurrencyData currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) { this.earlyPaidAmount = Money.zero(currency); this.lastInstallment = null; this.skipCurrentLoop = false; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java deleted file mode 100644 index f9ec64f971f..00000000000 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/domain/LoanRescheduleModel.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collection; -import org.apache.fineract.organisation.monetary.data.CurrencyData; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; -import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; -import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; -import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; - -public final class LoanRescheduleModel { - - private final Collection periods; - private final Collection oldPeriods; - private final ApplicationCurrency applicationCurrency; - private final int loanTermInDays; - private final Money totalPrincipalDisbursed; - private final BigDecimal totalPrincipalExpected; - private final BigDecimal totalPrincipalPaid; - private final BigDecimal totalInterestCharged; - private final BigDecimal totalFeeChargesCharged; - private final BigDecimal totalPenaltyChargesCharged; - private final BigDecimal totalRepaymentExpected; - private final BigDecimal totalOutstanding; - - private LoanRescheduleModel(final Collection periods, - final Collection oldPeriods, final ApplicationCurrency applicationCurrency, - final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, - final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, - final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { - this.periods = periods; - this.oldPeriods = oldPeriods; - this.applicationCurrency = applicationCurrency; - this.loanTermInDays = loanTermInDays; - this.totalPrincipalDisbursed = principalDisbursed; - this.totalPrincipalExpected = totalPrincipalExpected; - this.totalPrincipalPaid = totalPrincipalPaid; - this.totalInterestCharged = totalInterestCharged; - this.totalFeeChargesCharged = totalFeeChargesCharged; - this.totalPenaltyChargesCharged = totalPenaltyChargesCharged; - this.totalRepaymentExpected = totalRepaymentExpected; - this.totalOutstanding = totalOutstanding; - } - - public static LoanRescheduleModel instance(final Collection periods, - final Collection oldPeriods, final ApplicationCurrency applicationCurrency, - final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, - final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, - final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { - - return new LoanRescheduleModel(periods, oldPeriods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected, - totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, - totalOutstanding); - } - - public static LoanRescheduleModel createWithSchedulehistory(LoanRescheduleModel loanRescheduleModel, - final Collection oldPeriods) { - - return new LoanRescheduleModel(loanRescheduleModel.periods, oldPeriods, loanRescheduleModel.applicationCurrency, - loanRescheduleModel.loanTermInDays, loanRescheduleModel.totalPrincipalDisbursed, loanRescheduleModel.totalPrincipalExpected, - loanRescheduleModel.totalPrincipalPaid, loanRescheduleModel.totalInterestCharged, - loanRescheduleModel.totalFeeChargesCharged, loanRescheduleModel.totalPenaltyChargesCharged, - loanRescheduleModel.totalRepaymentExpected, loanRescheduleModel.totalOutstanding); - } - - public LoanScheduleData toData() { - - final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal(); - final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf(); - final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf); - - final BigDecimal totalCredits = BigDecimal.ZERO; - - final Collection periodsData = new ArrayList<>(); - for (final LoanRescheduleModalPeriod modelPeriod : this.periods) { - periodsData.add(modelPeriod.toData()); - } - - final BigDecimal totalWaived = null; - final BigDecimal totalWrittenOff = null; - final BigDecimal totalRepayment = null; - final BigDecimal totalPaidInAdvance = null; - final BigDecimal totalPaidLate = null; - - return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(), - this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged, - this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment, - totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits); - } - - public Collection getPeriods() { - return this.periods; - } - - public Collection getOldPeriods() { - return this.oldPeriods; - } -} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java new file mode 100644 index 00000000000..175d7d60543 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductRelatedDetailMinimumData.java @@ -0,0 +1,186 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanproduct.data; + +import java.math.BigDecimal; +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.common.domain.DaysInMonthType; +import org.apache.fineract.portfolio.common.domain.DaysInYearType; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; +import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; +import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; +import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; + +public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRepaymentScheduleRelatedDetail { + + private final CurrencyData currency; + + private final Money principal; + private final Money inArrearsTolerance; + + private final BigDecimal interestRatePerPeriod; + private final BigDecimal annualNominalInterestRate; + + private final Integer interestChargingGrace; + private final Integer interestPaymentGrace; + private final Integer principalGrace; + private final Integer recurringMoratoriumOnPrincipalPeriods; + + private final InterestMethod interestMethod; + private final InterestCalculationPeriodMethod interestCalculationPeriodMethod; + + private final DaysInYearType daysInYearType; + private final DaysInMonthType daysInMonthType; + + private final AmortizationMethod amortizationMethod; + + private final PeriodFrequencyType repaymentPeriodFrequencyType; + private final Integer repaymentEvery; + private final Integer numberOfRepayments; + + public LoanProductRelatedDetailMinimumData(CurrencyData currency, Money principal, Money inArrearsTolerance, + BigDecimal interestRatePerPeriod, BigDecimal annualNominalInterestRate, Integer interestChargingGrace, + Integer interestPaymentGrace, Integer principalGrace, Integer recurringMoratoriumOnPrincipalPeriods, + InterestMethod interestMethod, InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType, + DaysInMonthType daysInMonthType, AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType, + Integer repaymentEvery, Integer numberOfRepayments) { + this.currency = currency; + this.principal = principal; + this.inArrearsTolerance = inArrearsTolerance; + this.interestRatePerPeriod = interestRatePerPeriod; + this.annualNominalInterestRate = annualNominalInterestRate; + this.interestChargingGrace = defaultToNullIfZero(interestChargingGrace); + this.interestPaymentGrace = defaultToNullIfZero(interestPaymentGrace); + this.principalGrace = defaultToNullIfZero(principalGrace); + this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; + this.interestMethod = interestMethod; + this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; + this.daysInYearType = daysInYearType; + this.daysInMonthType = daysInMonthType; + this.amortizationMethod = amortizationMethod; + this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; + this.repaymentEvery = repaymentEvery; + this.numberOfRepayments = numberOfRepayments; + } + + private Integer defaultToNullIfZero(final Integer value) { + Integer defaultTo = value; + if (Integer.valueOf(0).equals(value)) { + defaultTo = null; + } + return defaultTo; + } + + @Override + public CurrencyData getCurrencyData() { + return currency; + } + + @Override + public Money getPrincipal() { + return principal; + } + + @Override + public Integer getGraceOnInterestCharged() { + return interestChargingGrace; + } + + @Override + public Integer getGraceOnInterestPayment() { + return interestPaymentGrace; + } + + @Override + public Integer getGraceOnPrincipalPayment() { + return principalGrace; + } + + @Override + public Integer getRecurringMoratoriumOnPrincipalPeriods() { + return recurringMoratoriumOnPrincipalPeriods; + } + + @Override + public Money getInArrearsTolerance() { + return inArrearsTolerance; + } + + @Override + public BigDecimal getNominalInterestRatePerPeriod() { + return interestRatePerPeriod; + } + + @Override + public PeriodFrequencyType getInterestPeriodFrequencyType() { + return repaymentPeriodFrequencyType; + } + + @Override + public BigDecimal getAnnualNominalInterestRate() { + return annualNominalInterestRate; + } + + @Override + public InterestMethod getInterestMethod() { + return interestMethod; + } + + @Override + public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { + return interestCalculationPeriodMethod; + } + + @Override + public Integer getRepayEvery() { + return repaymentEvery; + } + + @Override + public PeriodFrequencyType getRepaymentPeriodFrequencyType() { + return repaymentPeriodFrequencyType; + } + + @Override + public Integer getNumberOfRepayments() { + return numberOfRepayments; + } + + @Override + public AmortizationMethod getAmortizationMethod() { + return amortizationMethod; + } + + @Override + public Integer getGraceOnArrearsAgeing() { + return 0; + } + + @Override + public Integer getDaysInMonthType() { + return daysInMonthType.getValue(); + } + + @Override + public Integer getDaysInYearType() { + return daysInYearType.getValue(); + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java index 5ec2acdaac5..2e3f8408983 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java @@ -19,7 +19,7 @@ package org.apache.fineract.portfolio.loanproduct.domain; import java.math.BigDecimal; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; @@ -28,7 +28,7 @@ */ public interface LoanProductMinimumRepaymentScheduleRelatedDetail { - MonetaryCurrency getCurrency(); + CurrencyData getCurrencyData(); Money getPrincipal(); @@ -61,4 +61,8 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail { AmortizationMethod getAmortizationMethod(); Integer getGraceOnArrearsAgeing(); + + Integer getDaysInMonthType(); + + Integer getDaysInYearType(); } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index e65a78fca17..5dc7ecafd9b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -31,6 +31,7 @@ import lombok.Getter; import lombok.Setter; import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; @@ -164,7 +165,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Enumerated(EnumType.STRING) private LoanChargeOffBehaviour chargeOffBehaviour; - public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal, + public static LoanProductRelatedDetail createFrom(final CurrencyData currencyData, final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, @@ -179,6 +180,7 @@ public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currenc final boolean enableAccrualActivityPosting, final List supportedInterestRefundTypes, final LoanChargeOffBehaviour chargeOffBehaviour) { + final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(currencyData); return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, @@ -252,19 +254,23 @@ private Integer defaultToNullIfZero(final Integer value) { return defaultTo; } - @Override public MonetaryCurrency getCurrency() { return this.currency.copy(); } + @Override + public CurrencyData getCurrencyData() { + return currency.toData(); + } + @Override public Money getPrincipal() { - return Money.of(this.currency, this.principal); + return Money.of(getCurrencyData(), this.principal); } @Override public Money getInArrearsTolerance() { - return Money.of(this.currency, this.inArrearsTolerance); + return Money.of(getCurrencyData(), this.inArrearsTolerance); } // TODO: REVIEW diff --git a/fineract-progressive-loan-embeddable-schedule-generator/README.md b/fineract-progressive-loan-embeddable-schedule-generator/README.md new file mode 100644 index 00000000000..81e67c4be4e --- /dev/null +++ b/fineract-progressive-loan-embeddable-schedule-generator/README.md @@ -0,0 +1,63 @@ +# Fineract Progressive Loan Embeddable Schedule Generator + +## Build + +- Generate Embeddable Progressive Schedule Generator Jar + + ```shell + ./gradlew :fineract-progressive-loan-embeddable-schedule-generator:shadowJar + ``` + +- Copy Jar from `fineract-progressive-loan-embeddable-schedule-generator/build/libs/fineract-progressive-loan-embeddable-schedule-generator-*-SNAPSHOT-all.jar` to Your class path. + +## Dependencies + +There is no extra dependency. + + +## Sample Application + +Copy a [Main.java](misc/Main.java) file from the `misc` directory into the working directory: + +The project directory structure: + + - `Main.java` + - `fineract-embeddable-calculator-1.11.0-SNAPSHOT-all.jar` + + +- Check Java minimum version + + ```shell + java -version # openjdk version "17.0.3" 2022-04-19 LTS + ``` + +- Compile the source + + ```shell + javac -cp fineract-progressive-loan-embeddable-schedule-generator-1.11.0-SNAPSHOT-all.jar Main.java + ``` + +- Run with dependencies: + + ```shell + java -cp fineract-progressive-loan-embeddable-schedule-generator-1.11.0-SNAPSHOT-all.jar:. Main + ``` + +This code has the following output: + +``` +#------ Loan Schedule -----------------# + Number of Periods: 6 + Loan Term in Days: 182 + Total Disbursed Amount: 100.00 + Total Interest Amount: 2.05 + Total Repayment Amount: 102.05 +#------ Repayment Schedule ------------# + Disbursement - Date: 2024-01-01, Amount: 100.00 + Repayment Period: #1, Due Date: 2024-02-01, Balance: 83.57, Principal: 16.43, Interest: 0.58, Total: 17.01 + Repayment Period: #2, Due Date: 2024-03-01, Balance: 67.05, Principal: 16.52, Interest: 0.49, Total: 17.01 + Repayment Period: #3, Due Date: 2024-04-01, Balance: 50.43, Principal: 16.62, Interest: 0.39, Total: 17.01 + Repayment Period: #4, Due Date: 2024-05-01, Balance: 33.71, Principal: 16.72, Interest: 0.29, Total: 17.01 + Repayment Period: #5, Due Date: 2024-06-01, Balance: 16.90, Principal: 16.81, Interest: 0.20, Total: 17.01 + Repayment Period: #6, Due Date: 2024-07-01, Balance: 0.00, Principal: 16.90, Interest: 0.10, Total: 17.00 +``` diff --git a/fineract-progressive-loan-embeddable-schedule-generator/build.gradle b/fineract-progressive-loan-embeddable-schedule-generator/build.gradle new file mode 100644 index 00000000000..b150208eb21 --- /dev/null +++ b/fineract-progressive-loan-embeddable-schedule-generator/build.gradle @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +description = 'Fineract Progressive Loan Embeddable Schedule Generator' + +apply plugin: 'com.gradleup.shadow' +apply plugin: 'java' +apply from: 'dependencies.gradle' + +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +var requiredModuleNames = [ + 'fineract-core', + 'fineract-loan', + 'fineract-progressive-loan', +] + +tasks.named('shadowJar', ShadowJar) { + dependencies { + exclude((dep) -> !requiredModuleNames.any( (reqModName) -> dep.moduleName.contains(reqModName))) + } + minimize() { + exclude("*.xml") + } +} diff --git a/fineract-progressive-loan-embeddable-schedule-generator/dependencies.gradle b/fineract-progressive-loan-embeddable-schedule-generator/dependencies.gradle new file mode 100644 index 00000000000..06646455426 --- /dev/null +++ b/fineract-progressive-loan-embeddable-schedule-generator/dependencies.gradle @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +dependencies { + implementation(project(path: ':fineract-progressive-loan')) + implementation(project(path: ':fineract-loan')) + + annotationProcessor 'org.projectlombok:lombok' + annotationProcessor 'org.mapstruct:mapstruct-processor' +} diff --git a/fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java b/fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java new file mode 100644 index 00000000000..eef822b2c3e --- /dev/null +++ b/fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import org.apache.fineract.organisation.monetary.data.CurrencyData; +import org.apache.fineract.portfolio.common.domain.DaysInMonthType; +import org.apache.fineract.portfolio.common.domain.DaysInYearType; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanDisbursementPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanDownPaymentPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanRepaymentPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.EmbeddableProgressiveLoanScheduleGenerator; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleModelData; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.time.LocalDate; + +/* + * NOTE: This file used by a CI job to test Progressive Loan Embedded Jar compiled successfully and runs smoothly. + */ +public class Main { + public static void main(String[] args) throws InterruptedException { + MathContext mc = new MathContext(12, RoundingMode.HALF_UP); + EmbeddableProgressiveLoanScheduleGenerator calculator = new EmbeddableProgressiveLoanScheduleGenerator(); + + final CurrencyData currency = new CurrencyData("usd", "US Dollar", 2, null, "usd", "$"); + final LocalDate startDate = LocalDate.of(2024, 1, 1); + final LocalDate disbursementDate = LocalDate.of(2024, 1, 1); + final BigDecimal disbursedAmount = BigDecimal.valueOf(100); + + final int noRepayments = 6; + final int repaymentFrequency = 1; + final String repaymentFrequencyType = "MONTHS"; + final BigDecimal downPaymentPercentage = args.length > 0 ? new BigDecimal(args[0]) : BigDecimal.ZERO; + final boolean isDownPaymentEnabled = BigDecimal.ZERO.compareTo(downPaymentPercentage) != 0; + final BigDecimal annualNominalInterestRate = BigDecimal.valueOf(7.0); + final DaysInMonthType daysInMonthType = DaysInMonthType.DAYS_30; + final DaysInYearType daysInYearType = DaysInYearType.DAYS_360; + final Integer installmentAmountInMultiplesOf = null; + final Integer fixedLength = null; + + var config = new LoanRepaymentScheduleModelData(startDate, currency, disbursedAmount, disbursementDate, noRepayments, repaymentFrequency, repaymentFrequencyType, annualNominalInterestRate, isDownPaymentEnabled, daysInMonthType, daysInYearType, downPaymentPercentage, installmentAmountInMultiplesOf, fixedLength); + + final LoanSchedulePlan plan = calculator.generate(mc, config); + printPlan(plan); + } + + static void printPlan(final LoanSchedulePlan plan) throws InterruptedException { + System.out.println("#------ Loan Schedule -----------------#"); + System.out.printf(" Number of Periods: %d%n", plan.getPeriods().stream().filter(period -> !(period instanceof LoanSchedulePlanDisbursementPeriod)).count()); + System.out.printf(" Loan Term in Days: %d%n", plan.getLoanTermInDays()); + System.out.printf(" Total Disbursed Amount: %s%n", plan.getTotalDisbursedAmount()); + System.out.printf(" Total Interest Amount: %s%n", plan.getTotalInterestAmount()); + System.out.printf(" Total Repayment Amount: %s%n", plan.getTotalRepaymentAmount()); + System.out.println("#------ Repayment Schedule ------------#"); + + for (LoanSchedulePlanPeriod period : plan.getPeriods()) { + if (period instanceof LoanSchedulePlanDisbursementPeriod dp) { + System.out.printf(" Disbursement - Date: %s, Amount: %s%n", dp.periodDueDate(), dp.getPrincipalAmount()); + } if (period instanceof LoanSchedulePlanDownPaymentPeriod rp) { + System.out.printf(" Down payment Period: #%d, Due Date: %s, Balance: %s, Principal: %s, Total: %s%n", rp.periodNumber(), rp.periodDueDate(), rp.getOutstandingLoanBalance(), rp.getPrincipalAmount(), rp.getTotalDueAmount()); + } if (period instanceof LoanSchedulePlanRepaymentPeriod rp) { + System.out.printf(" Repayment Period: #%d, Due Date: %s, Balance: %s, Principal: %s, Interest: %s, Total: %s%n", rp.periodNumber(), rp.periodDueDate(), rp.getOutstandingLoanBalance(), rp.getPrincipalAmount(), rp.getInterestAmount(), rp.getTotalDueAmount()); + } + } + } +} diff --git a/fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java new file mode 100644 index 00000000000..1b77749768e --- /dev/null +++ b/fineract-progressive-loan-embeddable-schedule-generator/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/EmbeddableProgressiveLoanScheduleGenerator.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + +import java.math.MathContext; +import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan; +import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator; +import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator; + +@SuppressWarnings("unused") +public class EmbeddableProgressiveLoanScheduleGenerator { + + private final ProgressiveLoanScheduleGenerator scheduleGenerator; + private final ScheduledDateGenerator scheduledDateGenerator; + private final EMICalculator emiCalculator; + + public EmbeddableProgressiveLoanScheduleGenerator() { + this.emiCalculator = new ProgressiveEMICalculator(); + this.scheduledDateGenerator = new DefaultScheduledDateGenerator(); + this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator); + } + + public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) { + return scheduleGenerator.generate(mc, modelData); + } +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java index 833311fd2a1..3ce52571573 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java @@ -48,7 +48,7 @@ public ProgressiveTransactionCtx(MonetaryCurrency currency, List charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail, ProgressiveLoanInterestScheduleModel model) { super(currency, installments, charges, overpaymentHolder, changedTransactionDetail); - sumOfInterestRefundAmount = model.getZero(); + sumOfInterestRefundAmount = model.zero(); this.model = model; } } 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 c950bc687b2..97bdeb54aa9 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 @@ -91,7 +91,7 @@ public Money getCalculatedDueInterest() { .multipliedBy(getRateFactorTillPeriodDueDate(), mc).getAmount() // .divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) // .multiply(BigDecimal.valueOf(getLength()), mc); // - return Money.of(outstandingLoanBalance.getCurrency(), interestDueTillRepaymentDueDate, mc); + return Money.of(outstandingLoanBalance.getCurrencyData(), interestDueTillRepaymentDueDate, mc); } public long getLength() { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlan.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlan.java index 8b82b91e967..cdffe2c7d65 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlan.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanSchedulePlan.java @@ -75,7 +75,7 @@ public static LoanSchedulePlan from(LoanScheduleModel model) { }); return new LoanSchedulePlan(periods, // - model.getApplicationCurrency().toData(), // + model.getCurrency(), // model.getLoanTermInDays(), // model.getTotalPrincipalDisbursed().getAmount(), // model.getTotalPrincipalExpected(), // 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 0dec32dcd4b..1bb00af6172 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 @@ -34,7 +34,7 @@ import lombok.experimental.Accessors; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.Money; -import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; @Data @Accessors(fluent = true) @@ -42,26 +42,31 @@ public class ProgressiveLoanInterestScheduleModel { private final List repaymentPeriods; private final TreeSet interestRates; - private final LoanProductRelatedDetail loanProductRelatedDetail; + private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail; private final Integer installmentAmountInMultiplesOf; - private MathContext mc; + private final MathContext mc; + private final Money zero; - public ProgressiveLoanInterestScheduleModel(List repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail, - Integer installmentAmountInMultiplesOf, MathContext mc) { + public ProgressiveLoanInterestScheduleModel(final List repaymentPeriods, + final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, + final MathContext mc) { this.repaymentPeriods = repaymentPeriods; this.interestRates = new TreeSet<>(Collections.reverseOrder()); this.loanProductRelatedDetail = loanProductRelatedDetail; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.mc = mc; + this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); } - private ProgressiveLoanInterestScheduleModel(List repaymentPeriods, final TreeSet interestRates, - LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) { + private ProgressiveLoanInterestScheduleModel(final List repaymentPeriods, final TreeSet interestRates, + final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, + final MathContext mc) { this.mc = mc; this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods); this.interestRates = new TreeSet<>(interestRates); this.loanProductRelatedDetail = loanProductRelatedDetail; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; + this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); } public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) { @@ -197,30 +202,26 @@ void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate previousInterestPeriod.addDisbursementAmount(disbursedAmount); previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount); final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate, - BigDecimal.ZERO, BigDecimal.ZERO, getZero(), getZero(), getZero(), mc); + BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, mc); repaymentPeriod.getInterestPeriods().add(interestPeriod); } - public Money getZero() { - return Money.zero(loanProductRelatedDetail.getCurrency(), mc); - } - public Money getTotalDueInterest() { return repaymentPeriods().stream().flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest)) - .reduce(getZero(), Money::plus); + .reduce(zero(), Money::plus); } public Money getTotalDuePrincipal() { return repaymentPeriods.stream().flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount)) - .reduce(getZero(), Money::plus); + .reduce(zero(), Money::plus); } public Money getTotalPaidInterest() { - return repaymentPeriods().stream().map(RepaymentPeriod::getPaidInterest).reduce(getZero(), Money::plus); + return repaymentPeriods().stream().map(RepaymentPeriod::getPaidInterest).reduce(zero, Money::plus); } public Money getTotalPaidPrincipal() { - return repaymentPeriods().stream().map(RepaymentPeriod::getPaidPrincipal).reduce(getZero(), Money::plus); + return repaymentPeriods().stream().map(RepaymentPeriod::getPaidPrincipal).reduce(zero, Money::plus); } public Optional findRepaymentPeriod(@NotNull LocalDate transactionDate) { 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 82bef12abff..ab58c423758 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 @@ -31,9 +31,8 @@ import java.util.List; import java.util.Set; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.service.MathUtil; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; @@ -55,7 +54,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.springframework.stereotype.Component; -@Slf4j @Component @RequiredArgsConstructor public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { @@ -72,11 +70,10 @@ public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentSchedu public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) { - final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // determine the total charges due at time of disbursement final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); - final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); + final CurrencyData currency = loanApplicationTerms.getCurrency(); LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) ? loanApplicationTerms.getExpectedDisbursementDate() : loanApplicationTerms.getSubmittedOnDate(); @@ -92,7 +89,7 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer final List expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc, periodStartDate, loanApplicationTerms, holidayDetailDTO); final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( - expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(), + expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(), loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); final List periods = new ArrayList<>(expectedRepaymentPeriods.size()); @@ -141,7 +138,7 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; final BigDecimal totalOutstanding = BigDecimal.ZERO; - return LoanScheduleModel.from(periods, applicationCurrency, interestScheduleModel.getLoanTermInDays(), + return LoanScheduleModel.from(periods, currency, interestScheduleModel.getLoanTermInDays(), scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount(), mc), scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount(), mc).getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), @@ -308,7 +305,7 @@ private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set loanCharges, - final LoanScheduleParams scheduleParams, final MonetaryCurrency currency, final MathContext mc) { + final LoanScheduleParams scheduleParams, final CurrencyData currency, final MathContext mc) { final PrincipalInterest principalInterest = new PrincipalInterest(repaymentPeriod.getPrincipalDue(), repaymentPeriod.getInterestDue(), null); @@ -325,10 +322,10 @@ private void applyChargesForCurrentPeriod(final LoanScheduleModelRepaymentPeriod } private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, - final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, + final CurrencyData currency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, final MathContext mc) { - Money cumulative = Money.zero(monetaryCurrency, mc); + Money cumulative = Money.zero(currency, mc); if (loanCharges != null) { for (final LoanCharge loanCharge : loanCharges) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { @@ -358,11 +355,10 @@ private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate perio } private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, - final Set loanCharges, 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, mc); + final Set loanCharges, final CurrencyData currency, final PrincipalInterest principalInterestForThisPeriod, + final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, + final boolean isFirstPeriod, final MathContext mc) { + Money cumulative = Money.zero(currency, mc); if (loanCharges != null) { for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isPenaltyCharge()) { @@ -409,7 +405,7 @@ private Money calculateSpecificDueDateChargeWithPercentage(final Money principal return cumulative; } - private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams, + private void updatePeriodsWithCharges(final CurrencyData currency, LoanScheduleParams scheduleParams, final Collection periods, final Set nonCompoundingCharges, MathContext mc) { for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { 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 10f971448ee..3f8641600f5 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 @@ -31,18 +31,20 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; 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.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; public interface EMICalculator { @NotNull ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List periods, - @NotNull LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc); + @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, + MathContext mc); @NotNull ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( - @NotNull List installments, @NotNull LoanProductRelatedDetail loanProductRelatedDetail, - Integer installmentAmountInMultiplesOf, MathContext mc); + @NotNull List installments, + @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, + 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 25205779c12..4e0383c31af 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 @@ -43,7 +43,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; 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.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.springframework.stereotype.Component; @Component @@ -56,8 +56,8 @@ public final class ProgressiveEMICalculator implements EMICalculator { @Override @NotNull public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List periods, - @NotNull LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, - final MathContext mc) { + @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, + final Integer installmentAmountInMultiplesOf, final MathContext mc) { return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate, LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); } @@ -65,7 +65,8 @@ public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel( @Override @NotNull public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( - @NotNull List installments, @NotNull LoanProductRelatedDetail loanProductRelatedDetail, + @NotNull List installments, + @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) { installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList(); return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate, @@ -74,9 +75,9 @@ public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleM @NotNull private ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List periods, Function from, - Function to, @NotNull LoanProductRelatedDetail loanProductRelatedDetail, + Function to, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, final MathContext mc) { - final Money zero = Money.zero(loanProductRelatedDetail.getCurrency(), mc); + final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc); final AtomicReference prev = new AtomicReference<>(); List repaymentPeriods = periods.stream().map(e -> { RepaymentPeriod rp = new RepaymentPeriod(prev.get(), from.apply(e), to.apply(e), zero, mc); @@ -101,7 +102,7 @@ public Optional findRepaymentPeriod(final ProgressiveLoanIntere @Override public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate, final Money disbursedAmount) { - scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, scheduleModel.getZero()) + scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, scheduleModel.zero()) .ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors( getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, disbursementDueDate), scheduleModel)); } @@ -127,8 +128,8 @@ public void changeInterestRate(final ProgressiveLoanInterestScheduleModel schedu final LocalDate interestRateChangeEffectiveDate = newInterestSubmittedOnDate.minusDays(1); scheduleModel.addInterestRate(interestRateChangeEffectiveDate, newInterestRate); scheduleModel - .changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.getZero(), - scheduleModel.getZero()) + .changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.zero(), + scheduleModel.zero()) .ifPresent(repaymentPeriod -> calculateEMIValueAndRateFactors( getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel)); } @@ -136,8 +137,7 @@ public void changeInterestRate(final ProgressiveLoanInterestScheduleModel schedu @Override public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate, Money balanceCorrectionAmount) { - scheduleModel - .changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, scheduleModel.getZero(), balanceCorrectionAmount) + scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, scheduleModel.zero(), balanceCorrectionAmount) .ifPresent(repaymentPeriod -> { calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel); calculateOutstandingBalance(scheduleModel); @@ -296,13 +296,13 @@ 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(), mc), (m1, m2) -> m1.plus(m2, mc)); // 1.46 - Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmi) - .reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 101.48 + Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(scheduleModel.zero(), + (m1, m2) -> m1.plus(m2, mc)); // 1.46 + Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmi).reduce(scheduleModel.zero(), + (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(), mc), (m1, m2) -> m1.plus(m2, mc)); // 100 + .reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 100 Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc); Optional findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()) @@ -400,7 +400,7 @@ void calculateRateFactorForRepaymentPeriod(final RepaymentPeriod repaymentPeriod BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, final RepaymentPeriod repaymentPeriod, final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) { final MathContext mc = scheduleModel.mc(); - final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); + final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate), scheduleModel.mc()); final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()); @@ -493,7 +493,7 @@ void calculateEMIOnPeriods(final List repaymentPeriods, final P // TODO: double check final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation(); - final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrency(), + final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrencyData(), calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc); final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment); @@ -732,7 +732,7 @@ public Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel sch return scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueDate) // .map(repaymentPeriodDueDate -> getDueAmounts(scheduleModel, repaymentPeriodDueDate, subjectDate) // .getDueInterest()) // - .reduce(scheduleModel.getZero(), Money::add); // + .reduce(scheduleModel.zero(), Money::add); // } private long getUncountablePeriods(final List relatedRepaymentPeriods, final Money originalEmi) { 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 2d23c01e0dd..4b7d1e98aa2 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 @@ -561,7 +561,7 @@ private LoanTransaction createChargebackTransaction(Loan loan, double transactio @Test public void calculateChargebackAllocationMap() { Map result; - MonetaryCurrency currency = mock(MonetaryCurrency.class); + MonetaryCurrency currency = new MonetaryCurrency("usd", 2, null); result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0), List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency); 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 3e42964e147..4a19f2fa828 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 @@ -24,6 +24,7 @@ import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; @@ -38,6 +39,7 @@ class LoanScheduleGeneratorTest { private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(); private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$"); + private static final CurrencyData CURRENCY = APPLICATION_CURRENCY.toData(); private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22); private static final BigDecimal DISBURSEMENT_AMOUNT_100 = BigDecimal.valueOf(100); private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99); @@ -50,7 +52,7 @@ class LoanScheduleGeneratorTest { @Test void testGenerateLoanSchedule() { - LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY, + LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), CURRENCY, DISBURSEMENT_AMOUNT, DISBURSEMENT_DATE, NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE, NOMINAL_INTEREST_RATE, false, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null); @@ -85,7 +87,7 @@ void testGenerateLoanSchedule() { @Test void testGenerateLoanScheduleWithDownPayment() { - LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), APPLICATION_CURRENCY, + LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), CURRENCY, DISBURSEMENT_AMOUNT_100, LocalDate.of(2024, 1, 1), NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE, NOMINAL_INTEREST_RATE, true, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, DOWN_PAYMENT_PORTION, null, null); 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 a49824b7b72..a60e7a5377a 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 @@ -26,8 +26,7 @@ import java.util.List; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; -import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; -import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; +import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; @@ -39,7 +38,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; 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.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -59,10 +58,10 @@ 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 LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = Mockito + .mock(LoanProductMinimumRepaymentScheduleRelatedDetail.class); - private static final MonetaryCurrency monetaryCurrency = MonetaryCurrency - .fromApplicationCurrency(new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$")); + private static final CurrencyData currency = new CurrencyData("USD", "USD", 2, 1, "$", "USD"); private static List periods; private final BigDecimal interestRate = BigDecimal.valueOf(0.094822); @@ -166,7 +165,7 @@ public void test_generateInterestScheduleModel() { 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))); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -205,7 +204,7 @@ public void test_emi_calculator_performance() { Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -249,7 +248,7 @@ public void test_emiAdjustment_newCalculatedEmiNotBetterThanOriginal() { Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -285,7 +284,7 @@ public void test_disbursedAmt100_dayInYears360_daysInMonth30_repayEvery1Month() Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -321,7 +320,7 @@ public void test_multi_disbursedAmt200_2ndOnDueDate_dayInYears360_daysInMonth30_ Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -367,7 +366,7 @@ public void test_reschedule_disbursedAmt100_dayInYears360_daysInMonth30_repayEve Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -403,7 +402,7 @@ public void test_reschedule_interest_on0201_4per_disbursedAmt100_dayInYears360_d Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); @@ -445,7 +444,7 @@ public void test_reschedule_interest_on0201_2nd_EMI_not_changeable_disbursedAmt1 Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); @@ -492,7 +491,7 @@ public void test_reschedule_interest_on0120_adjsLst_dsbAmt100_dayInYears360_days Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); @@ -538,7 +537,7 @@ public void test_reschedule_interest_on0215_4per_disbursedAmt100_dayInYears360_d Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14)); @@ -584,7 +583,7 @@ public void test_balance_correction_on0215_disbursedAmt100_dayInYears360_daysInM Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); @@ -663,7 +662,7 @@ public void test_balance_correction_on0215_disbursedAmt100_dayInYears360_daysInM Assertions.assertEquals(0.1, toDouble(repaymentDetails4th.getDueInterest())); // balance update on the last period, check the right interest interval split - emiCalculator.addBalanceCorrection(interestSchedule, LocalDate.of(2024, 6, 10), Money.of(monetaryCurrency, BigDecimal.ZERO)); + emiCalculator.addBalanceCorrection(interestSchedule, LocalDate.of(2024, 6, 10), Money.of(currency, BigDecimal.ZERO)); final RepaymentPeriod lastRepaymentPeriod = interestSchedule.repaymentPeriods().get(interestSchedule.repaymentPeriods().size() - 1); Assertions.assertEquals(2, lastRepaymentPeriod.getInterestPeriods().size()); Assertions.assertEquals(LocalDate.of(2024, 6, 1), lastRepaymentPeriod.getInterestPeriods().get(0).getFromDate()); @@ -691,7 +690,7 @@ public void test_payoff_on0215_disbursedAmt100_dayInYears360_daysInMonth30_repay Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); @@ -762,7 +761,7 @@ public void test_payoff_on0115_disbursedAmt100_dayInYears360_daysInMonth30_repay Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15)); @@ -841,7 +840,7 @@ public void test_multiDisbursedAmt300InSamePeriod_dayInYears360_daysInMonth30_re Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -890,7 +889,7 @@ public void test_multiDisbursedAmt200InDifferentPeriod_dayInYears360_daysInMonth Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -939,7 +938,7 @@ public void test_multiDisbursedAmt150InSamePeriod_dayInYears360_daysInMonth30_re Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -983,7 +982,7 @@ public void test_disbursedAmt100_dayInYearsActual_daysInMonthActual_repayEvery1M Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -1017,7 +1016,7 @@ public void test_disbursedAmt1000_NoInterest_repayEvery1Month() { Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -1050,7 +1049,7 @@ public void test_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery1Week Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -1083,7 +1082,7 @@ public void test_disbursedAmt100_dayInYears364_daysInMonthActual_repayEvery2Week Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -1116,7 +1115,7 @@ public void test_disbursedAmt100_dayInYears360_daysInMonthDoesntMatter_repayEver Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -1148,7 +1147,7 @@ public void test_dailyInterest_disbursedAmt1000_dayInYears360_daysInMonth30_repa Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -1210,7 +1209,7 @@ public void test_dailyInterest_disbursedAmt2000_dayInYears360_daysInMonth30_repa Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); - Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); + Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency); final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); @@ -1266,7 +1265,7 @@ public void test_dailyInterest_disbursedAmt2000_dayInYears360_daysInMonth30_repa } private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) { - final Money zeroAmount = Money.zero(monetaryCurrency); + final Money zeroAmount = Money.zero(currency); return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, zeroAmount, false, mc); } @@ -1326,6 +1325,6 @@ private static BigDecimal applyMathContext(final BigDecimal value) { } private static Money toMoney(final double value) { - return Money.of(monetaryCurrency, BigDecimal.valueOf(value)); + return Money.of(currency, BigDecimal.valueOf(value)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index b6ddf56c001..84ab2a06ada 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -518,8 +518,8 @@ private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement fixedLength = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.FIXED_LENGTH, element); } - return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, + return LoanApplicationTerms.assembleFrom(applicationCurrency.toData(), loanTermFrequency, loanTermPeriodFrequencyType, + numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java index b0b451f1d2b..482ad8398fb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java @@ -122,7 +122,7 @@ public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final Loc boolean isPrincipalCompoundingDisabledForOverdueLoans = this.configurationDomainService .isPrincipalCompoundingDisabledForOverdueLoans(); - ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency.toData(), calculatedRepaymentsStartingFromDate, holidayDetails, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, floatingRateDTO, calendar, calendarHistoryDataWrapper, isInterestChargedFromDateAsDisbursementDateEnabled, numberOfDays, isSkipRepaymentOnFirstMonth, 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 3ea0da970b4..66af9aa8a78 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 @@ -106,7 +106,7 @@ public void testLoanRepaymentEventPayloadSerialization() throws IOException { Loan loanForProcessing = Mockito.mock(Loan.class); LoanSummary loanSummary = Mockito.mock(LoanSummary.class); - MonetaryCurrency loanCurrency = Mockito.mock(MonetaryCurrency.class); + MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1); LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1, LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), @@ -122,9 +122,6 @@ public void testLoanRepaymentEventPayloadSerialization() throws IOException { when(loanForProcessing.getSummary()).thenReturn(loanSummary); when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0)); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); - when(loanCurrency.getCode()).thenReturn("CODE"); - when(loanCurrency.getCurrencyInMultiplesOf()).thenReturn(1); - when(loanCurrency.getDigitsAfterDecimal()).thenReturn(1); when(mapper.mapLocalDate(any())).thenReturn(loanInstallmentRepaymentDueDate.format(DateTimeFormatter.ISO_DATE)); when(pastDueDataMapper.map(any())).thenReturn(pastDueAmount); when(pastDueService.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing)).thenReturn(null); @@ -156,7 +153,7 @@ public void testLoanRepaymentEventLoanIdMandatoryFieldValidation() { Loan loanForProcessing = Mockito.mock(Loan.class); LoanSummary loanSummary = Mockito.mock(LoanSummary.class); - MonetaryCurrency loanCurrency = Mockito.mock(MonetaryCurrency.class); + MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1); LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1, LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), @@ -173,9 +170,6 @@ public void testLoanRepaymentEventLoanIdMandatoryFieldValidation() { when(loanForProcessing.getSummary()).thenReturn(loanSummary); when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0)); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); - when(loanCurrency.getCode()).thenReturn("CODE"); - when(loanCurrency.getCurrencyInMultiplesOf()).thenReturn(1); - when(loanCurrency.getDigitsAfterDecimal()).thenReturn(1); when(mapper.mapLocalDate(any())).thenReturn(loanInstallmentRepaymentDueDate.format(DateTimeFormatter.ISO_DATE)); when(pastDueDataMapper.map(any())).thenReturn(pastDueAmount); when(pastDueService.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing)).thenReturn(null); 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 3cdf6995cff..eaa1e85f33e 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 @@ -92,8 +92,8 @@ public void test_generateRepaymentPeriods() { LocalDate dueRepaymentPeriodDate = LocalDate.of(2024, 2, 1); LocalDate submittedOnDate = LocalDate.of(2024, 1, 1); - LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(dollarCurrency, 1, MONTHS, 4, 1, MONTHS, null, - INVALID, EQUAL_PRINCIPAL, FLAT, ZERO, MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount, + LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(dollarCurrency.toData(), 1, MONTHS, 4, 1, MONTHS, + null, INVALID, EQUAL_PRINCIPAL, FLAT, ZERO, MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount, expectedDisbursementDate, null, dueRepaymentPeriodDate, null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, null, NONE, null, ZERO, @@ -164,12 +164,13 @@ private LoanApplicationTerms createLoanApplicationTerms(LocalDate dueRepaymentPe LocalDate expectedDisbursementDate = LocalDate.of(2023, 10, 26); LocalDate submittedOnDate = LocalDate.of(2023, 10, 24); - return LoanApplicationTerms.assembleFrom(dollarCurrency, 1, MONTHS, 1, 1, MONTHS, null, INVALID, EQUAL_PRINCIPAL, FLAT, ZERO, - MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount, expectedDisbursementDate, null, dueRepaymentPeriodDate, - null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, - BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, - null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, null, false, - DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null); + return LoanApplicationTerms.assembleFrom(dollarCurrency.toData(), 1, MONTHS, 1, 1, MONTHS, null, INVALID, EQUAL_PRINCIPAL, FLAT, + ZERO, MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount, expectedDisbursementDate, null, + dueRepaymentPeriodDate, null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, + EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, + null, ZERO, null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, + null, false, DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, + null); } private HolidayDetailDTO createHolidayDTO() { 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 599382e15a4..9be22f28a71 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 @@ -80,7 +80,7 @@ public void testLoanRepaymentPastDueAmountCalculations() { // given LocalDate businessDate = DateUtils.getBusinessLocalDate(); Loan loanForProcessing = Mockito.mock(Loan.class); - MonetaryCurrency loanCurrency = Mockito.mock(MonetaryCurrency.class); + MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1); // repayments // closed repayment @@ -111,9 +111,6 @@ public void testLoanRepaymentPastDueAmountCalculations() { repaymentInstallment_2, repaymentInstallment_upcoming); when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepayments); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); - when(loanCurrency.getCode()).thenReturn("CODE"); - when(loanCurrency.getCurrencyInMultiplesOf()).thenReturn(1); - when(loanCurrency.getDigitsAfterDecimal()).thenReturn(1); // when LoanRepaymentPastDueData pastDueAmount = underTest.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing); diff --git a/settings.gradle b/settings.gradle index 6347968f221..04999ae3f45 100644 --- a/settings.gradle +++ b/settings.gradle @@ -69,6 +69,7 @@ include ':fineract-avro-schemas' include ':fineract-e2e-tests-core' include ':fineract-e2e-tests-runner' include 'fineract-progressive-loan' +include 'fineract-progressive-loan-embeddable-schedule-generator' // NOTE: custom Docker image with all custom modules included include ':custom:docker' // NOTE: dynamically load custom modules with pattern "custom -> company -> category -> module"