FINERACT-1981: Embeddable Progressive Loan Schedule generator

This commit is contained in:
Janos Meszaros
2024-12-10 15:04:16 +01:00
committed by Adam Saghy
parent d0006a2169
commit bb8480a03d
41 changed files with 849 additions and 439 deletions
@@ -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/
+6 -3
View File
@@ -46,7 +46,8 @@ buildscript {
'fineract-avro-schemas', 'fineract-avro-schemas',
'fineract-e2e-tests-core', 'fineract-e2e-tests-core',
'fineract-e2e-tests-runner', 'fineract-e2e-tests-runner',
'fineract-progressive-loan' 'fineract-progressive-loan',
'fineract-progressive-loan-embeddable-schedule-generator'
].contains(it.name) ].contains(it.name)
} }
fineractPublishProjects = subprojects.findAll{ fineractPublishProjects = subprojects.findAll{
@@ -66,7 +67,8 @@ buildscript {
'fineract-report', 'fineract-report',
'fineract-branch', 'fineract-branch',
'fineract-document', 'fineract-document',
'fineract-progressive-loan' 'fineract-progressive-loan',
'fineract-progressive-loan-embeddable-schedule-generator'
].contains(it.name) ].contains(it.name)
} }
npmRepository = 'https://npm.pkg.github.com' npmRepository = 'https://npm.pkg.github.com'
@@ -109,6 +111,7 @@ plugins {
id 'se.thinkcode.cucumber-runner' version '0.0.11' apply false 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 "com.github.davidmc24.gradle.plugin.avro-base" version "1.9.1" apply false
id 'org.openapi.generator' version '7.8.0' 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" apply from: "${rootDir}/buildSrc/src/main/groovy/org.apache.fineract.release.gradle"
@@ -169,7 +172,7 @@ allprojects {
groovyGradle { groovyGradle {
target '*.gradle', '**/*.gradle' 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() greclipse()
indentWithSpaces(4) indentWithSpaces(4)
endWithNewline() endWithNewline()
@@ -365,12 +365,12 @@ public final class MathUtil {
/** @return parameter value or ZERO if it is negative */ /** @return parameter value or ZERO if it is negative */
public static Money negativeToZero(Money value) { 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 */ /** @return parameter value or ZERO if it is negative */
public static Money negativeToZero(Money value, MathContext mc) { 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) { public static boolean isEmpty(Money value) {
@@ -98,10 +98,6 @@ public class ApplicationCurrency extends AbstractPersistableCustom<Long> {
return new CurrencyData(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.displaySymbol, this.nameCode); 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() { public OrganisationCurrency toOrganisationCurrency() {
return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.nameCode, this.displaySymbol); return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.inMultiplesOf, this.nameCode, this.displaySymbol);
} }
@@ -34,6 +34,8 @@ public class MonetaryCurrency {
@Column(name = "currency_multiplesof") @Column(name = "currency_multiplesof")
private Integer inMultiplesOf; private Integer inMultiplesOf;
private transient CurrencyData currencyData;
protected MonetaryCurrency() { protected MonetaryCurrency() {
this.code = null; this.code = null;
this.digitsAfterDecimal = 0; this.digitsAfterDecimal = 0;
@@ -46,6 +48,13 @@ public class MonetaryCurrency {
this.inMultiplesOf = inMultiplesOf; 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() { public MonetaryCurrency copy() {
return new MonetaryCurrency(this.code, this.digitsAfterDecimal, this.inMultiplesOf); return new MonetaryCurrency(this.code, this.digitsAfterDecimal, this.inMultiplesOf);
} }
@@ -56,7 +65,14 @@ public class MonetaryCurrency {
} }
public static MonetaryCurrency fromCurrencyData(final CurrencyData currencyData) { 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() { public String getCode() {
@@ -21,43 +21,59 @@ package org.apache.fineract.organisation.monetary.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.Iterator; import java.util.Iterator;
import lombok.Getter;
import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.data.CurrencyData;
@Getter
public class Money implements Comparable<Money> { public class Money implements Comparable<Money> {
private final String currencyCode;
private final int currencyDigitsAfterDecimal;
private final Integer inMultiplesOf;
private final BigDecimal amount; private final BigDecimal amount;
private final CurrencyData currency;
private final transient MathContext mc; private final transient MathContext mc;
protected Money() { protected Money() {
this.currencyCode = null; this.currency = CurrencyData.blank();
this.currencyDigitsAfterDecimal = 0; this.amount = BigDecimal.ZERO;
this.inMultiplesOf = 0;
this.amount = null;
this.mc = getMc(); this.mc = getMc();
} }
private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount, final Integer inMultiplesOf, private Money(final CurrencyData currency, final BigDecimal amount, final MathContext mc) {
final MathContext mc) { this.currency = currency;
this.currencyCode = currencyCode;
this.currencyDigitsAfterDecimal = digitsAfterDecimal;
this.inMultiplesOf = inMultiplesOf;
this.mc = mc; this.mc = mc;
final BigDecimal amountZeroed = defaultToZeroIfNull(amount); final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
BigDecimal amountScaled = amountZeroed.stripTrailingZeros(); BigDecimal amountScaled = amountZeroed.stripTrailingZeros();
// round monetary amounts into multiples of say 20/50. // 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(); 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) { public static Money total(final Money... monies) {
@@ -83,21 +99,20 @@ public class Money implements Comparable<Money> {
return total; return total;
} }
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount, final MathContext mc) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount),
currency.getCurrencyInMultiplesOf(), mc);
}
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) {
return of(currency, newAmount, MoneyHelper.getMathContext());
}
public static Money of(final CurrencyData currency, final BigDecimal newAmount) { public static Money of(final CurrencyData currency, final BigDecimal newAmount) {
return of(currency, newAmount, MoneyHelper.getMathContext()); return of(currency, newAmount, MoneyHelper.getMathContext());
} }
public static Money of(final CurrencyData currency, final BigDecimal newAmount, final MathContext 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); return new Money(currency, newAmount, mc);
}
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 MonetaryCurrency currency, final BigDecimal newAmount) {
return of(currency, newAmount, MoneyHelper.getMathContext());
} }
public static Money zero(final MonetaryCurrency currency) { public static Money zero(final MonetaryCurrency currency) {
@@ -105,11 +120,11 @@ public class Money implements Comparable<Money> {
} }
public static Money zero(final MonetaryCurrency currency, MathContext mc) { 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) { 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) { public static Money zero(final CurrencyData currency) {
@@ -151,7 +166,7 @@ public class Money implements Comparable<Money> {
if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) { if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) {
amountScaled = amountScaled.divide(inMultiplesOfValue, 0, mc.getRoundingMode()).multiply(inMultiplesOfValue); 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) { public static double ceiling(final double n, final double s) {
@@ -195,11 +210,11 @@ public class Money implements Comparable<Money> {
} }
public Money copy() { 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) { 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) { public Money copy(final double amount) {
@@ -212,7 +227,7 @@ public class Money implements Comparable<Money> {
final Money money = checkCurrencyEqual(moneyProvider); final Money money = checkCurrencyEqual(moneyProvider);
total = total.add(money.amount); total = total.add(money.amount);
} }
return Money.of(monetaryCurrency(), total); return Money.of(getCurrencyData(), total);
} }
public Money plus(final Money moneyToAdd) { public Money plus(final Money moneyToAdd) {
@@ -233,7 +248,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.add(amountToAdd); 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) { public Money plus(final double amountToAdd) {
@@ -241,7 +256,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.add(BigDecimal.valueOf(amountToAdd)); 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) { public Money minus(final Money moneyToSubtract) {
@@ -271,7 +286,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.add(amountToAdd); 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) { public Money minus(final BigDecimal amountToSubtract) {
@@ -283,7 +298,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.subtract(amountToSubtract); 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) { private Money checkCurrencyEqual(final Money money) {
@@ -294,7 +309,7 @@ public class Money implements Comparable<Money> {
} }
public boolean isSameCurrency(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) { public Money dividedBy(final BigDecimal valueToDivideBy, final MathContext mc) {
@@ -302,7 +317,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.divide(valueToDivideBy, mc); 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) { public Money dividedBy(final double valueToDivideBy, final MathContext mc) {
@@ -310,7 +325,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), mc); 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) { public Money dividedBy(final long valueToDivideBy, final MathContext mc) {
@@ -318,7 +333,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), mc); 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) { public Money dividedBy(final long valueToDivideBy) {
@@ -326,7 +341,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.divide(BigDecimal.valueOf(valueToDivideBy), getMc()); 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) { public Money multipliedBy(final BigDecimal valueToMultiplyBy) {
@@ -338,7 +353,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy, mc); 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) { public Money multipliedBy(final double valueToMultiplyBy) {
@@ -346,7 +361,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy)); 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) { public Money multipliedBy(final long valueToMultiplyBy) {
@@ -358,7 +373,7 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
final BigDecimal newAmount = this.amount.multiply(BigDecimal.valueOf(valueToMultiplyBy), mc); 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) { public Money multiplyRetainScale(final BigDecimal valueToMultiplyBy, final MathContext mc) {
@@ -366,8 +381,8 @@ public class Money implements Comparable<Money> {
return this; return this;
} }
BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy, mc); BigDecimal newAmount = this.amount.multiply(valueToMultiplyBy, mc);
newAmount = newAmount.setScale(this.currencyDigitsAfterDecimal, mc.getRoundingMode()); newAmount = newAmount.setScale(this.currency.getDecimalPlaces(), mc.getRoundingMode());
return Money.of(monetaryCurrency(), newAmount, mc); return Money.of(getCurrencyData(), newAmount, mc);
} }
public Money multiplyRetainScale(final double valueToMultiplyBy, final MathContext mc) { public Money multiplyRetainScale(final double valueToMultiplyBy, final MathContext mc) {
@@ -376,12 +391,12 @@ public class Money implements Comparable<Money> {
public Money percentageOf(BigDecimal percentage, final MathContext mc) { public Money percentageOf(BigDecimal percentage, final MathContext mc) {
final BigDecimal newAmount = this.amount.multiply(percentage).divide(BigDecimal.valueOf(100), 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 @Override
public int compareTo(final Money other) { public int compareTo(final Money other) {
if (!this.currencyCode.equals(other.currencyCode)) { if (!this.getCurrencyCode().equals(other.getCurrencyCode())) {
throw new UnsupportedOperationException("currencies arent different"); throw new UnsupportedOperationException("currencies arent different");
} }
return this.amount.compareTo(other.amount); return this.amount.compareTo(other.amount);
@@ -392,7 +407,7 @@ public class Money implements Comparable<Money> {
} }
public boolean isZero(final MathContext mc) { public boolean isZero(final MathContext mc) {
return isEqualTo(Money.zero(getCurrency(), mc)); return isEqualTo(Money.zero(getCurrencyData(), mc));
} }
public boolean isEqualTo(final Money other) { public boolean isEqualTo(final Money other) {
@@ -416,7 +431,7 @@ public class Money implements Comparable<Money> {
} }
public boolean isGreaterThanZero(MathContext mc) { public boolean isGreaterThanZero(MathContext mc) {
return isGreaterThan(Money.zero(getCurrency(), mc)); return isGreaterThan(Money.zero(getCurrencyData(), mc));
} }
public boolean isLessThan(final Money other) { public boolean isLessThan(final Money other) {
@@ -428,20 +443,12 @@ public class Money implements Comparable<Money> {
} }
public boolean isLessThanZero(final MathContext mc) { public boolean isLessThanZero(final MathContext mc) {
return isLessThan(Money.zero(getCurrency(), mc)); return isLessThan(Money.zero(getCurrencyData(), mc));
}
public Integer getCurrencyInMultiplesOf() {
return this.inMultiplesOf;
}
public BigDecimal getAmountDefaultedToNullIfZero() {
return defaultToNullIfZero(this.amount);
} }
@Override @Override
public String toString() { public String toString() {
return new StringBuilder().append(this.currencyCode).append(' ').append(this.amount.toPlainString()).toString(); return this.getCurrencyCode() + ' ' + this.amount.toPlainString();
} }
public Money negated() { public Money negated() {
@@ -452,7 +459,7 @@ public class Money implements Comparable<Money> {
if (isZero(mc)) { if (isZero(mc)) {
return this; return this;
} }
return Money.of(monetaryCurrency(), this.amount.negate(), mc); return Money.of(getCurrencyData(), this.amount.negate(), mc);
} }
public Money abs() { public Money abs() {
@@ -463,20 +470,12 @@ public class Money implements Comparable<Money> {
return isLessThanZero(mc) ? negated(mc) : this; 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() { public Money zero() {
return zero(getMc()); return zero(getMc());
} }
public Money zero(MathContext mc) { public Money zero(MathContext mc) {
return Money.zero(getCurrency(), mc); return Money.zero(getCurrencyData(), mc);
} }
public MathContext getMc() { public MathContext getMc() {
@@ -22,6 +22,7 @@ import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
/** /**
* Represents currencies allowed for this MFI/organisation. * Represents currencies allowed for this MFI/organisation.
@@ -74,4 +75,8 @@ public class OrganisationCurrency extends AbstractPersistableCustom<Long> {
public final MonetaryCurrency toMonetaryCurrency() { public final MonetaryCurrency toMonetaryCurrency() {
return new MonetaryCurrency(this.code, this.decimalPlaces, this.inMultiplesOf); return new MonetaryCurrency(this.code, this.decimalPlaces, this.inMultiplesOf);
} }
public final CurrencyData toData() {
return new CurrencyData(this.code, this.decimalPlaces, this.inMultiplesOf);
}
} }
@@ -19,7 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.data; package org.apache.fineract.portfolio.loanaccount.data;
import java.time.LocalDate; 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.data.CalendarHistoryDataWrapper;
import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
@@ -29,7 +29,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
public class ScheduleGeneratorDTO { public class ScheduleGeneratorDTO {
final LoanScheduleGeneratorFactory loanScheduleFactory; final LoanScheduleGeneratorFactory loanScheduleFactory;
final ApplicationCurrency applicationCurrency; final CurrencyData currency;
final LocalDate calculatedRepaymentsStartingFromDate; final LocalDate calculatedRepaymentsStartingFromDate;
final HolidayDetailDTO holidayDetailDTO; final HolidayDetailDTO holidayDetailDTO;
final CalendarInstance calendarInstanceForInterestRecalculation; final CalendarInstance calendarInstanceForInterestRecalculation;
@@ -47,7 +47,7 @@ public class ScheduleGeneratorDTO {
final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI; final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI;
final boolean isPrincipalCompoundingDisabledForOverdueLoans; 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 LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO,
final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance, final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance,
final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO, final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO,
@@ -58,7 +58,7 @@ public class ScheduleGeneratorDTO {
final boolean isPrincipalCompoundingDisabledForOverdueLoans) { final boolean isPrincipalCompoundingDisabledForOverdueLoans) {
this.loanScheduleFactory = loanScheduleFactory; this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency; this.currency = currency;
this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation; this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation;
this.compoundingCalendarInstance = compoundingCalendarInstance; this.compoundingCalendarInstance = compoundingCalendarInstance;
@@ -81,8 +81,8 @@ public class ScheduleGeneratorDTO {
return this.loanScheduleFactory; return this.loanScheduleFactory;
} }
public ApplicationCurrency getApplicationCurrency() { public CurrencyData getCurrency() {
return this.applicationCurrency; return this.currency;
} }
public LocalDate getCalculatedRepaymentsStartingFromDate() { public LocalDate getCalculatedRepaymentsStartingFromDate() {
@@ -2781,8 +2781,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
interestChargedFromDate = getDisbursementDate(); interestChargedFromDate = getDisbursementDate();
} }
return LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(), loanTermFrequency, return LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getCurrency(), loanTermFrequency, loanTermPeriodFrequencyType,
loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail, scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(),
@@ -3053,7 +3053,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>(); List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations); annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations);
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, return LoanApplicationTerms.assembleFrom(applicationCurrency.toData(), loanTermFrequency, loanTermPeriodFrequencyType, nthDayType,
dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData,
maxOutstandingBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), maxOutstandingBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(),
@@ -26,7 +26,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; 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.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
@@ -97,7 +97,7 @@ public final class LoanScheduleParams {
private final LocalDate scheduleTillDate; private final LocalDate scheduleTillDate;
private final boolean partialUpdate; private final boolean partialUpdate;
private int loanTermInDays; private int loanTermInDays;
private final MonetaryCurrency currency; private final CurrencyData currency;
private final boolean applyInterestRecalculation; private final boolean applyInterestRecalculation;
private final MathContext mc; private final MathContext mc;
@@ -110,7 +110,7 @@ public final class LoanScheduleParams {
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments, final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> recalculationDetails, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) { final boolean partialUpdate, final CurrencyData currency, final boolean applyInterestRecalculation, final MathContext mc) {
this.periodNumber = periodNumber; this.periodNumber = periodNumber;
this.instalmentNumber = instalmentNumber; this.instalmentNumber = instalmentNumber;
this.loanTermInDays = loanTermInDays; this.loanTermInDays = loanTermInDays;
@@ -153,7 +153,7 @@ public final class LoanScheduleParams {
final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails, final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final MonetaryCurrency currency, final boolean applyInterestRecalculation, final MathContext mc) { final CurrencyData currency, final boolean applyInterestRecalculation, final MathContext mc) {
final boolean partialUpdate = true; final boolean partialUpdate = true;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
@@ -187,7 +187,7 @@ public final class LoanScheduleParams {
final boolean partialUpdate = false; final boolean partialUpdate = false;
final int loanTermInDays = 0; final int loanTermInDays = 0;
final Money totalOutstandingInterestPaymentDueToGrace = null; final Money totalOutstandingInterestPaymentDueToGrace = null;
final MonetaryCurrency currency = null; final CurrencyData currency = null;
final Money unCompoundedAmount = null; final Money unCompoundedAmount = null;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
@@ -197,7 +197,7 @@ public final class LoanScheduleParams {
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); 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 LocalDate periodStartDate, final Money principalToBeScheduled, final MathContext mc) {
final int loanTermInDays = 0; final int loanTermInDays = 0;
final int periodNumber = 1; final int periodNumber = 1;
@@ -231,7 +231,7 @@ public final class LoanScheduleParams {
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation, mc); 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, final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams,
MathContext mc) { MathContext mc) {
final int loanTermInDays = 0; final int loanTermInDays = 0;
@@ -500,7 +500,7 @@ public final class LoanScheduleParams {
return this.compoundingDateVariations; return this.compoundingDateVariations;
} }
public MonetaryCurrency getCurrency() { public CurrencyData getCurrency() {
return this.currency; return this.currency;
} }
@@ -37,7 +37,7 @@ import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
@@ -74,7 +74,6 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams loanScheduleParams) { final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams loanScheduleParams) {
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
// generate list of proposed schedule due dates // generate list of proposed schedule due dates
LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations() LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations()
@@ -90,7 +89,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
// setup variables for tracking important facts required for loan // setup variables for tracking important facts required for loan
// schedule generation. // schedule generation.
final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); final CurrencyData currency = loanApplicationTerms.getCurrency();
final MonetaryCurrency monetaryCurrency = MonetaryCurrency.fromCurrencyData(currency);
LoanScheduleParams scheduleParams; LoanScheduleParams scheduleParams;
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
? loanApplicationTerms.getExpectedDisbursementDate() ? loanApplicationTerms.getExpectedDisbursementDate()
@@ -252,7 +252,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
if (loanApplicationTerms.isInterestRecalculationEnabled()) { if (loanApplicationTerms.isInterestRecalculationEnabled()) {
populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, 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 // populates the collection with transactions till the due date of
@@ -330,7 +330,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
} }
// applies charges for the period // 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 // sum up real totalInstallmentDue from components
final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod(); final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod();
@@ -354,7 +354,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
// apply loan transactions on installments to identify early/late // apply loan transactions on installments to identify early/late
// payments for interest recalculation // payments for interest recalculation
installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, monetaryCurrency, scheduleParams,
loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate, loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate,
periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams,
lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges); lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges);
@@ -398,7 +398,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
// determine fees and penalties for charges which depends on total // determine fees and penalties for charges which depends on total
// loan interest // 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 // this block is to add extra re-payment schedules with interest portion
// if the loan not paid with in loan term // if the loan not paid with in loan term
@@ -408,8 +408,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
} }
if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0 if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0
&& DateUtils.isAfter(currentDate, scheduleParams.getPeriodStartDate())) { && DateUtils.isAfter(currentDate, scheduleParams.getPeriodStartDate())) {
Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO, currency, Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentDate(mc, loanApplicationTerms, holidayDetailDTO,
periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges, scheduleParams); monetaryCurrency, periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges,
scheduleParams);
scheduleParams.addTotalCumulativeInterest(totalInterest); scheduleParams.addTotalCumulativeInterest(totalInterest);
} }
@@ -418,7 +419,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
final BigDecimal totalOutstanding = BigDecimal.ZERO; final BigDecimal totalOutstanding = BigDecimal.ZERO;
updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms); updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms);
return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), return LoanScheduleModel.from(periods, currency, scheduleParams.getLoanTermInDays(),
scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()), scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()),
scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(), scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(),
totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
@@ -534,7 +535,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
modifiedInstallment = handlePrepaymentOfLoan(mc, loanApplicationTerms, holidayDetailDTO, scheduleParams, modifiedInstallment = handlePrepaymentOfLoan(mc, loanApplicationTerms, holidayDetailDTO, scheduleParams,
totalInterestChargedForFullLoanTerm, scheduledDueDate, periodStartDateApplicableForInterest, totalInterestChargedForFullLoanTerm, scheduledDueDate, periodStartDateApplicableForInterest,
currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), currentPeriodParams, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), currentPeriodParams,
lastTotalOutstandingInterestPaymentDueToGrace, transactionDate, modifiedInstallment, loanCharges); lastTotalOutstandingInterestPaymentDueToGrace, transactionDate, modifiedInstallment, loanCharges, currency);
Money addToPrincipal = Money.zero(currency); Money addToPrincipal = Money.zero(currency);
if (scheduleParams.getOutstandingBalance().isLessThanZero()) { if (scheduleParams.getOutstandingBalance().isLessThanZero()) {
@@ -575,17 +576,18 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
final Money totalInterestChargedForFullLoanTerm, final LocalDate scheduledDueDate, final Money totalInterestChargedForFullLoanTerm, final LocalDate scheduledDueDate,
LocalDate periodStartDateApplicableForInterest, final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, LocalDate periodStartDateApplicableForInterest, final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction,
final ScheduleCurrentPeriodParams currentPeriodParams, final Money lastTotalOutstandingInterestPaymentDueToGrace, final ScheduleCurrentPeriodParams currentPeriodParams, final Money lastTotalOutstandingInterestPaymentDueToGrace,
final LocalDate transactionDate, final LoanScheduleModelPeriod installment, Set<LoanCharge> loanCharges) { final LocalDate transactionDate, final LoanScheduleModelPeriod installment, Set<LoanCharge> loanCharges,
MonetaryCurrency currency) {
LoanScheduleModelPeriod modifiedInstallment = installment; LoanScheduleModelPeriod modifiedInstallment = installment;
Money outstanding = scheduleParams.getOutstandingBalance(); Money outstanding = scheduleParams.getOutstandingBalance();
PrincipalInterest tempPrincipalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(), PrincipalInterest tempPrincipalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(),
currentPeriodParams.getInterestForThisPeriod(), null); currentPeriodParams.getInterestForThisPeriod(), null);
outstanding = outstanding.minus(cumulativeFeeChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, outstanding = outstanding.minus(cumulativeFeeChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, currency,
totalInterestChargedForFullLoanTerm.getCurrency(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), true,
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); scheduleParams.isFirstPeriod(), mc));
outstanding = outstanding.minus(cumulativePenaltyChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, outstanding = outstanding.minus(cumulativePenaltyChargesDueWithin(transactionDate, scheduledDueDate, loanCharges, currency,
totalInterestChargedForFullLoanTerm.getCurrency(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), tempPrincipalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), true,
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc)); scheduleParams.isFirstPeriod(), mc));
if (!outstanding.isGreaterThan(currentPeriodParams.getInterestForThisPeriod()) && !scheduledDueDate.equals(transactionDate)) { if (!outstanding.isGreaterThan(currentPeriodParams.getInterestForThisPeriod()) && !scheduledDueDate.equals(transactionDate)) {
final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
@@ -607,7 +609,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
new TreeMap<>(scheduleParams.getCompoundingMap())); new TreeMap<>(scheduleParams.getCompoundingMap()));
scheduleParams.getCompoundingMap().clear(); scheduleParams.getCompoundingMap().clear();
populateCompoundingDatesInPeriod(periodStartDateApplicableForInterest, calculateTill, loanApplicationTerms, holidayDetailDTO, 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 // this is to make sure we are recalculating using correct interest
// rate // rate
@@ -628,11 +630,10 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
loanApplicationTerms.updateAnnualNominalInterestRate(currentInterestRate); loanApplicationTerms.updateAnnualNominalInterestRate(currentInterestRate);
// applies charges for the period // applies charges for the period
final ScheduleCurrentPeriodParams tempPeriod = new ScheduleCurrentPeriodParams( final ScheduleCurrentPeriodParams tempPeriod = new ScheduleCurrentPeriodParams(loanApplicationTerms.getCurrency(),
totalInterestChargedForFullLoanTerm.getCurrency(), interestCalculationGraceOnRepaymentPeriodFraction); interestCalculationGraceOnRepaymentPeriodFraction);
tempPeriod.setInterestForThisPeriod(interestTillDate.interest()); tempPeriod.setInterestForThisPeriod(interestTillDate.interest());
applyChargesForCurrentPeriod(loanCharges, totalInterestChargedForFullLoanTerm.getCurrency(), scheduleParams, calculateTill, applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, calculateTill, tempPeriod, mc);
tempPeriod, mc);
Money interestDiff = currentPeriodParams.getInterestForThisPeriod().minus(tempPeriod.getInterestForThisPeriod()); Money interestDiff = currentPeriodParams.getInterestForThisPeriod().minus(tempPeriod.getInterestForThisPeriod());
Money chargeDiff = currentPeriodParams.getFeeChargesForInstallment().minus(tempPeriod.getFeeChargesForInstallment()); Money chargeDiff = currentPeriodParams.getFeeChargesForInstallment().minus(tempPeriod.getFeeChargesForInstallment());
Money penaltyDiff = currentPeriodParams.getPenaltyChargesForInstallment().minus(tempPeriod.getPenaltyChargesForInstallment()); Money penaltyDiff = currentPeriodParams.getPenaltyChargesForInstallment().minus(tempPeriod.getPenaltyChargesForInstallment());
@@ -684,8 +685,8 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
.reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance())); .reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()));
} }
private void updatePrincipalPortionBasedOnPreviousEarlyPayments(final MonetaryCurrency currency, private void updatePrincipalPortionBasedOnPreviousEarlyPayments(final CurrencyData currency, final LoanScheduleParams scheduleParams,
final LoanScheduleParams scheduleParams, final ScheduleCurrentPeriodParams currentPeriodParams) { final ScheduleCurrentPeriodParams currentPeriodParams) {
if (currentPeriodParams.getPrincipalForThisPeriod().isGreaterThan(scheduleParams.getReducePrincipal())) { if (currentPeriodParams.getPrincipalForThisPeriod().isGreaterThan(scheduleParams.getReducePrincipal())) {
currentPeriodParams.minusPrincipalForThisPeriod(scheduleParams.getReducePrincipal()); currentPeriodParams.minusPrincipalForThisPeriod(scheduleParams.getReducePrincipal());
scheduleParams.setReducePrincipal(Money.zero(currency)); scheduleParams.setReducePrincipal(Money.zero(currency));
@@ -711,7 +712,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest, final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest,
final Collection<RecalculationDetail> applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) { final Collection<RecalculationDetail> applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) {
if (scheduleParams.applyInterestRecalculation()) { if (scheduleParams.applyInterestRecalculation()) {
final MonetaryCurrency currency = scheduleParams.getCurrency(); final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(scheduleParams.getCurrency());
final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
boolean checkForOutstanding = true; boolean checkForOutstanding = true;
List<RecalculationDetail> unprocessedTransactions = new ArrayList<>(); List<RecalculationDetail> unprocessedTransactions = new ArrayList<>();
@@ -1682,7 +1683,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
final LoanScheduleParams params, final LocalDate lastRestDate, final LocalDate scheduledDueDate) { final LoanScheduleParams params, final LocalDate lastRestDate, final LocalDate scheduledDueDate) {
if (loanApplicationTerms.isInterestRecalculationEnabled() if (loanApplicationTerms.isInterestRecalculationEnabled()
&& loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { && loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
final MonetaryCurrency currency = params.getCurrency(); final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(params.getCurrency());
Money totalCompoundedAmount = Money.zero(currency); Money totalCompoundedAmount = Money.zero(currency);
boolean lastInstallmentIsPastDate = false; boolean lastInstallmentIsPastDate = false;
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : params.getInstallments()) { for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : params.getInstallments()) {
@@ -1802,7 +1803,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate, private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate,
final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms) { final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms) {
adjustCompoundedAmountWithPaidDetail(params.getPrincipalPortionMap(), lastRestDate, amountApplicableDate, transaction, adjustCompoundedAmountWithPaidDetail(params.getPrincipalPortionMap(), lastRestDate, amountApplicableDate, transaction,
loanApplicationTerms, params.getCurrency()); loanApplicationTerms, MonetaryCurrency.fromCurrencyData(params.getCurrency()));
} }
private void adjustCompoundedAmountWithPaidDetail(final Map<LocalDate, Money> principalPortionMap, final LocalDate lastRestDate, private void adjustCompoundedAmountWithPaidDetail(final Map<LocalDate, Money> principalPortionMap, final LocalDate lastRestDate,
@@ -2493,7 +2494,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap,
uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest,
newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate,
currency, applyInterestRecalculation, mc); currency.toData(), applyInterestRecalculation, mc);
retainedInstallments.addAll(newRepaymentScheduleInstallments); retainedInstallments.addAll(newRepaymentScheduleInstallments);
loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations);
loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged())); loanApplicationTerms.updateTotalInterestDue(Money.of(currency, loan.getSummary().getTotalInterestCharged()));
@@ -20,10 +20,12 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext; import java.math.MathContext;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.YearMonth;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.extern.slf4j.Slf4j;
import net.fortuna.ical4j.model.Recur; import net.fortuna.ical4j.model.Recur;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.domain.Holiday;
@@ -42,7 +44,6 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@Slf4j
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override @Override
@@ -121,7 +122,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
Calendar currentCalendar = loanApplicationTerms.getLoanCalendar(); Calendar currentCalendar = loanApplicationTerms.getLoanCalendar();
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate); loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate);
dueRepaymentPeriodDate = (LocalDate) CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(), dueRepaymentPeriodDate = (LocalDate) adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType()); loanApplicationTerms.getRepaymentPeriodFrequencyType());
if (currentCalendar != null) { if (currentCalendar != null) {
// If we have currentCalendar object, this means there is a // If we have currentCalendar object, this means there is a
@@ -154,6 +155,21 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
return dueRepaymentPeriodDate; 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 @Override
public LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, public LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
final boolean isFirstRepayment, final Integer periodNumber) { final boolean isFirstRepayment, final Integer periodNumber) {
@@ -305,7 +321,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
case INVALID: case INVALID:
break; break;
case WHOLE_TERM: case WHOLE_TERM:
log.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM"); // TODO: Implement getRepaymentPeriodDate for WHOLE_TERM
break; break;
} }
return dueRepaymentPeriodDate; return dueRepaymentPeriodDate;
@@ -347,7 +363,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
case INVALID: case INVALID:
break; break;
case WHOLE_TERM: case WHOLE_TERM:
log.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM"); // TODO: Implement getRepaymentPeriodDate for WHOLE_TERM
break; break;
} }
return isScheduledDate; return isScheduledDate;
@@ -384,7 +400,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
case INVALID: case INVALID:
break; break;
case WHOLE_TERM: case WHOLE_TERM:
log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); // TODO: Implement getRepaymentPeriodDate for WHOLE_TERM
break; break;
} }
@@ -27,11 +27,9 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.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.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper;
@@ -48,21 +46,22 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour; 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.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreCloseInterestCalculationStrategy; 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.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
@Slf4j
public final class LoanApplicationTerms { public final class LoanApplicationTerms {
private ApplicationCurrency currency; private CurrencyData currency;
private Calendar loanCalendar; private Calendar loanCalendar;
private Integer loanTermFrequency; private Integer loanTermFrequency;
@@ -268,7 +267,7 @@ public final class LoanApplicationTerms {
public static class Builder { public static class Builder {
private ApplicationCurrency currency; private CurrencyData currency;
private Integer loanTermFrequency; private Integer loanTermFrequency;
private PeriodFrequencyType loanTermPeriodFrequencyType; private PeriodFrequencyType loanTermPeriodFrequencyType;
private Integer numberOfRepayments; private Integer numberOfRepayments;
@@ -292,7 +291,7 @@ public final class LoanApplicationTerms {
private LocalDate seedDate; private LocalDate seedDate;
private MathContext mc; private MathContext mc;
public Builder currency(ApplicationCurrency currency) { public Builder currency(CurrencyData currency) {
this.currency = currency; this.currency = currency;
return this; return this;
} }
@@ -408,7 +407,7 @@ public final class LoanApplicationTerms {
} }
public static LoanApplicationTerms assembleFrom(LoanRepaymentScheduleModelData modelData, MathContext mc) { 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(); BigDecimal downPaymentPercentage = modelData.downPaymentPercentage();
LocalDate seedDate; LocalDate seedDate;
@@ -428,12 +427,12 @@ public final class LoanApplicationTerms {
.annualNominalInterestRate(modelData.annualNominalInterestRate()).principal(principal) .annualNominalInterestRate(modelData.annualNominalInterestRate()).principal(principal)
.expectedDisbursementDate(modelData.disbursementDate()).repaymentsStartingFromDate(modelData.scheduleGenerationStartDate()) .expectedDisbursementDate(modelData.disbursementDate()).repaymentsStartingFromDate(modelData.scheduleGenerationStartDate())
.daysInMonthType(modelData.daysInMonth()).daysInYearType(modelData.daysInYear()).fixedLength(modelData.fixedLength()) .daysInMonthType(modelData.daysInMonth()).daysInYearType(modelData.daysInYear()).fixedLength(modelData.fixedLength())
.inArrearsTolerance(Money.zero(modelData.currency().toData(), mc)).disbursementDatas(new ArrayList<>()) .inArrearsTolerance(Money.zero(modelData.currency(), mc)).disbursementDatas(new ArrayList<>())
.isDownPaymentEnabled(modelData.downPaymentEnabled()).downPaymentPercentage(downPaymentPercentage) .isDownPaymentEnabled(modelData.downPaymentEnabled()).downPaymentPercentage(downPaymentPercentage)
.submittedOnDate(modelData.scheduleGenerationStartDate()).seedDate(seedDate).mc(mc).build(); .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 loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod,
@@ -484,7 +483,7 @@ public final class LoanApplicationTerms {
} }
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 PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek,
final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate,
final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance, final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance,
@@ -537,27 +536,26 @@ public final class LoanApplicationTerms {
LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType(); LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType();
LoanScheduleProcessingType loanScheduleProcessingType = loanProductRelatedDetail.getLoanScheduleProcessingType(); LoanScheduleProcessingType loanScheduleProcessingType = loanProductRelatedDetail.getLoanScheduleProcessingType();
final Integer fixedLength = loanProductRelatedDetail.getFixedLength(); final Integer fixedLength = loanProductRelatedDetail.getFixedLength();
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, interestMethod,
interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods,
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, disbursementDatas, maxOutstandingBalance, loanProductRelatedDetail.getGraceOnArrearsAgeing(), daysInMonthType,
loanProductRelatedDetail.getGraceOnArrearsAgeing(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance,
rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays,
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization,
allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday, isFirstRepaymentDateAllowedOnHoliday, isInterestToBeRecoveredFirstWhenGreaterThanEMI,
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled,
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType, loanScheduleType, loanScheduleProcessingType, fixedLength, loanProductRelatedDetail.isEnableAccrualActivityPosting(),
fixedLength, loanProductRelatedDetail.isEnableAccrualActivityPosting(),
loanProductRelatedDetail.getSupportedInterestRefundTypes(), loanProductRelatedDetail.getChargeOffBehaviour()); 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 loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer nthDay, final DayOfWeekType weekDayType, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer nthDay, final DayOfWeekType weekDayType,
final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod,
@@ -830,7 +828,7 @@ public final class LoanApplicationTerms {
case INVALID: case INVALID:
break; break;
case WHOLE_TERM: case WHOLE_TERM:
log.error("TODO Implement getPeriodEndDate for WHOLE_TERM"); // TODO: Implement getPeriodEndDate for WHOLE_TERM
break; break;
} }
return dueRepaymentPeriodDate; return dueRepaymentPeriodDate;
@@ -1249,7 +1247,7 @@ public final class LoanApplicationTerms {
periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
break; break;
case WHOLE_TERM: case WHOLE_TERM:
log.error("TODO Implement periodicInterestRate for WHOLE_TERM"); // TODO: Implement getPeriodEndDate for WHOLE_TERM
break; break;
} }
break; break;
@@ -1533,8 +1531,8 @@ public final class LoanApplicationTerms {
} }
public LoanProductRelatedDetail toLoanProductRelatedDetail() { public LoanProductRelatedDetail toLoanProductRelatedDetail() {
final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces(), final CurrencyData currency = new CurrencyData(this.currency.getCode(), this.currency.getDecimalPlaces(),
this.currency.getCurrencyInMultiplesOf()); this.currency.getInMultiplesOf());
return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod, return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod,
this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod, this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod,
@@ -1548,6 +1546,15 @@ public final class LoanApplicationTerms {
this.chargeOffBehaviour); 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() { public Integer getLoanTermFrequency() {
return this.loanTermFrequency; return this.loanTermFrequency;
} }
@@ -1592,8 +1599,8 @@ public final class LoanApplicationTerms {
return this.amortizationMethod; return this.amortizationMethod;
} }
public MonetaryCurrency getCurrency() { public CurrencyData getCurrency() {
return this.principal.getCurrency(); return currency;
} }
public Integer getNumberOfRepayments() { public Integer getNumberOfRepayments() {
@@ -1787,10 +1794,6 @@ public final class LoanApplicationTerms {
this.totalInterestDue = totalInterestDue; this.totalInterestDue = totalInterestDue;
} }
public ApplicationCurrency getApplicationCurrency() {
return this.currency;
}
public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
return this.interestCalculationPeriodMethod; return this.interestCalculationPeriodMethod;
} }
@@ -2074,7 +2077,7 @@ public final class LoanApplicationTerms {
case INVALID: case INVALID:
break; break;
case WHOLE_TERM: case WHOLE_TERM:
log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM"); // TODO: Implement getPeriodEndDate for WHOLE_TERM
break; break;
} }
return maxDateForFixedLength; return maxDateForFixedLength;
@@ -22,11 +22,11 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; 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.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; 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 BigDecimal disbursementAmount, @NotNull LocalDate disbursementDate, @NotNull int numberOfRepayments,
@NotNull int repaymentFrequency, @NotBlank String repaymentFrequencyType, @NotNull BigDecimal annualNominalInterestRate, @NotNull int repaymentFrequency, @NotBlank String repaymentFrequencyType, @NotNull BigDecimal annualNominalInterestRate,
@NotNull boolean downPaymentEnabled, @NotNull DaysInMonthType daysInMonth, @NotNull DaysInYearType daysInYear, @NotNull boolean downPaymentEnabled, @NotNull DaysInMonthType daysInMonth, @NotNull DaysInYearType daysInYear,
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
import org.apache.fineract.organisation.monetary.data.CurrencyData; 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.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData; 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.data.LoanSchedulePeriodData;
@@ -36,7 +35,7 @@ public final class LoanScheduleModel {
@Getter @Getter
private final List<LoanScheduleModelPeriod> periods; private final List<LoanScheduleModelPeriod> periods;
private final ApplicationCurrency applicationCurrency; private final CurrencyData currency;
private final int loanTermInDays; private final int loanTermInDays;
private final Money totalPrincipalDisbursed; private final Money totalPrincipalDisbursed;
private final BigDecimal totalPrincipalExpected; private final BigDecimal totalPrincipalExpected;
@@ -49,20 +48,19 @@ public final class LoanScheduleModel {
private final BigDecimal totalRepaymentExpected; private final BigDecimal totalRepaymentExpected;
private final BigDecimal totalOutstanding; private final BigDecimal totalOutstanding;
public static LoanScheduleModel from(final List<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency, public static LoanScheduleModel from(final List<LoanScheduleModelPeriod> periods, final CurrencyData currency, final int loanTermInDays,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
return new LoanScheduleModel(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected, return new LoanScheduleModel(periods, currency, loanTermInDays, principalDisbursed, totalPrincipalExpected, totalPrincipalPaid,
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding);
totalOutstanding);
} }
public static LoanScheduleModel withOverdueChargeUpdation(final List<LoanScheduleModelPeriod> periods, public static LoanScheduleModel withOverdueChargeUpdation(final List<LoanScheduleModelPeriod> periods,
final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) { 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.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, totalPenaltyChargesCharged, loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, totalPenaltyChargesCharged,
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding); loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
@@ -71,18 +69,18 @@ public final class LoanScheduleModel {
public static LoanScheduleModel withLoanScheduleModelPeriods(final List<LoanScheduleModelPeriod> periods, public static LoanScheduleModel withLoanScheduleModelPeriods(final List<LoanScheduleModelPeriod> periods,
final LoanScheduleModel loanScheduleModel) { 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.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged,
loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding); loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
} }
private LoanScheduleModel(final List<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency, private LoanScheduleModel(final List<LoanScheduleModelPeriod> periods, final CurrencyData currency, final int loanTermInDays,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPenaltyChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) { final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
this.periods = periods; this.periods = periods;
this.applicationCurrency = applicationCurrency; this.currency = currency;
this.loanTermInDays = loanTermInDays; this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = principalDisbursed; this.totalPrincipalDisbursed = principalDisbursed;
this.totalPrincipalExpected = totalPrincipalExpected; this.totalPrincipalExpected = totalPrincipalExpected;
@@ -95,11 +93,6 @@ public final class LoanScheduleModel {
} }
public LoanScheduleData toData() { 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 BigDecimal totalCredits = BigDecimal.ZERO;
final List<LoanSchedulePeriodData> periodsData = new ArrayList<>(); final List<LoanSchedulePeriodData> periodsData = new ArrayList<>();
@@ -19,7 +19,7 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import 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.Money;
public class ScheduleCurrentPeriodParams { public class ScheduleCurrentPeriodParams {
@@ -36,11 +36,11 @@ public class ScheduleCurrentPeriodParams {
boolean isEmiAmountChanged; boolean isEmiAmountChanged;
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction; BigDecimal interestCalculationGraceOnRepaymentPeriodFraction;
ScheduleCurrentPeriodParams(final MonetaryCurrency currency) { ScheduleCurrentPeriodParams(final CurrencyData currency) {
this(currency, BigDecimal.ZERO); this(currency, BigDecimal.ZERO);
} }
ScheduleCurrentPeriodParams(final MonetaryCurrency currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) { ScheduleCurrentPeriodParams(final CurrencyData currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) {
this.earlyPaidAmount = Money.zero(currency); this.earlyPaidAmount = Money.zero(currency);
this.lastInstallment = null; this.lastInstallment = null;
this.skipCurrentLoop = false; this.skipCurrentLoop = false;
@@ -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<LoanRescheduleModelRepaymentPeriod> periods;
private final Collection<LoanRepaymentScheduleHistory> 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<LoanRescheduleModelRepaymentPeriod> periods,
final Collection<LoanRepaymentScheduleHistory> 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<LoanRescheduleModelRepaymentPeriod> periods,
final Collection<LoanRepaymentScheduleHistory> 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<LoanRepaymentScheduleHistory> 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<LoanSchedulePeriodData> 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<LoanRescheduleModelRepaymentPeriod> getPeriods() {
return this.periods;
}
public Collection<LoanRepaymentScheduleHistory> getOldPeriods() {
return this.oldPeriods;
}
}
@@ -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();
}
}
@@ -19,7 +19,7 @@
package org.apache.fineract.portfolio.loanproduct.domain; package org.apache.fineract.portfolio.loanproduct.domain;
import java.math.BigDecimal; 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.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -28,7 +28,7 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
*/ */
public interface LoanProductMinimumRepaymentScheduleRelatedDetail { public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
MonetaryCurrency getCurrency(); CurrencyData getCurrencyData();
Money getPrincipal(); Money getPrincipal();
@@ -61,4 +61,8 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail {
AmortizationMethod getAmortizationMethod(); AmortizationMethod getAmortizationMethod();
Integer getGraceOnArrearsAgeing(); Integer getGraceOnArrearsAgeing();
Integer getDaysInMonthType();
Integer getDaysInYearType();
} }
@@ -31,6 +31,7 @@ import java.util.Map;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.apache.fineract.infrastructure.core.api.JsonCommand; 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.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
@@ -164,7 +165,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private LoanChargeOffBehaviour chargeOffBehaviour; 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 nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion,
@@ -179,6 +180,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes, final boolean enableAccrualActivityPosting, final List<LoanSupportedInterestRefundTypes> supportedInterestRefundTypes,
final LoanChargeOffBehaviour chargeOffBehaviour) { final LoanChargeOffBehaviour chargeOffBehaviour) {
final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(currencyData);
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment,
@@ -252,19 +254,23 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return defaultTo; return defaultTo;
} }
@Override
public MonetaryCurrency getCurrency() { public MonetaryCurrency getCurrency() {
return this.currency.copy(); return this.currency.copy();
} }
@Override
public CurrencyData getCurrencyData() {
return currency.toData();
}
@Override @Override
public Money getPrincipal() { public Money getPrincipal() {
return Money.of(this.currency, this.principal); return Money.of(getCurrencyData(), this.principal);
} }
@Override @Override
public Money getInArrearsTolerance() { public Money getInArrearsTolerance() {
return Money.of(this.currency, this.inArrearsTolerance); return Money.of(getCurrencyData(), this.inArrearsTolerance);
} }
// TODO: REVIEW // TODO: REVIEW
@@ -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
```
@@ -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")
}
}
@@ -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'
}
@@ -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());
}
}
}
}
@@ -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);
}
}
@@ -48,7 +48,7 @@ public class ProgressiveTransactionCtx extends TransactionCtx {
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail, Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
ProgressiveLoanInterestScheduleModel model) { ProgressiveLoanInterestScheduleModel model) {
super(currency, installments, charges, overpaymentHolder, changedTransactionDetail); super(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
sumOfInterestRefundAmount = model.getZero(); sumOfInterestRefundAmount = model.zero();
this.model = model; this.model = model;
} }
} }
@@ -91,7 +91,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
.multipliedBy(getRateFactorTillPeriodDueDate(), mc).getAmount() // .multipliedBy(getRateFactorTillPeriodDueDate(), mc).getAmount() //
.divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) // .divide(BigDecimal.valueOf(lengthTillPeriodDueDate), mc) //
.multiply(BigDecimal.valueOf(getLength()), mc); // .multiply(BigDecimal.valueOf(getLength()), mc); //
return Money.of(outstandingLoanBalance.getCurrency(), interestDueTillRepaymentDueDate, mc); return Money.of(outstandingLoanBalance.getCurrencyData(), interestDueTillRepaymentDueDate, mc);
} }
public long getLength() { public long getLength() {
@@ -75,7 +75,7 @@ public class LoanSchedulePlan {
}); });
return new LoanSchedulePlan(periods, // return new LoanSchedulePlan(periods, //
model.getApplicationCurrency().toData(), // model.getCurrency(), //
model.getLoanTermInDays(), // model.getLoanTermInDays(), //
model.getTotalPrincipalDisbursed().getAmount(), // model.getTotalPrincipalDisbursed().getAmount(), //
model.getTotalPrincipalExpected(), // model.getTotalPrincipalExpected(), //
@@ -34,7 +34,7 @@ import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money; 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 @Data
@Accessors(fluent = true) @Accessors(fluent = true)
@@ -42,26 +42,31 @@ public class ProgressiveLoanInterestScheduleModel {
private final List<RepaymentPeriod> repaymentPeriods; private final List<RepaymentPeriod> repaymentPeriods;
private final TreeSet<InterestRate> interestRates; private final TreeSet<InterestRate> interestRates;
private final LoanProductRelatedDetail loanProductRelatedDetail; private final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail;
private final Integer installmentAmountInMultiplesOf; private final Integer installmentAmountInMultiplesOf;
private MathContext mc; private final MathContext mc;
private final Money zero;
public ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, LoanProductRelatedDetail loanProductRelatedDetail, public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
Integer installmentAmountInMultiplesOf, MathContext mc) { final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf,
final MathContext mc) {
this.repaymentPeriods = repaymentPeriods; this.repaymentPeriods = repaymentPeriods;
this.interestRates = new TreeSet<>(Collections.reverseOrder()); this.interestRates = new TreeSet<>(Collections.reverseOrder());
this.loanProductRelatedDetail = loanProductRelatedDetail; this.loanProductRelatedDetail = loanProductRelatedDetail;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.mc = mc; this.mc = mc;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
} }
private ProgressiveLoanInterestScheduleModel(List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates, private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates,
LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc) { final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf,
final MathContext mc) {
this.mc = mc; this.mc = mc;
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods); this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods);
this.interestRates = new TreeSet<>(interestRates); this.interestRates = new TreeSet<>(interestRates);
this.loanProductRelatedDetail = loanProductRelatedDetail; this.loanProductRelatedDetail = loanProductRelatedDetail;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
} }
public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) { public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) {
@@ -197,30 +202,26 @@ public class ProgressiveLoanInterestScheduleModel {
previousInterestPeriod.addDisbursementAmount(disbursedAmount); previousInterestPeriod.addDisbursementAmount(disbursedAmount);
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount); previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate, final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate,
BigDecimal.ZERO, BigDecimal.ZERO, getZero(), getZero(), getZero(), mc); BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, mc);
repaymentPeriod.getInterestPeriods().add(interestPeriod); repaymentPeriod.getInterestPeriods().add(interestPeriod);
} }
public Money getZero() {
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
}
public Money getTotalDueInterest() { public Money getTotalDueInterest() {
return repaymentPeriods().stream().flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest)) return repaymentPeriods().stream().flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getCalculatedDueInterest))
.reduce(getZero(), Money::plus); .reduce(zero(), Money::plus);
} }
public Money getTotalDuePrincipal() { public Money getTotalDuePrincipal() {
return repaymentPeriods.stream().flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount)) return repaymentPeriods.stream().flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
.reduce(getZero(), Money::plus); .reduce(zero(), Money::plus);
} }
public Money getTotalPaidInterest() { 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() { 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<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate transactionDate) { public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate transactionDate) {
@@ -31,9 +31,8 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.MathUtil; 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.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
@@ -55,7 +54,6 @@ import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
@@ -72,11 +70,10 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) { final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
// determine the total charges due at time of disbursement // determine the total charges due at time of disbursement
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); final CurrencyData currency = loanApplicationTerms.getCurrency();
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType()) LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
? loanApplicationTerms.getExpectedDisbursementDate() ? loanApplicationTerms.getExpectedDisbursementDate()
: loanApplicationTerms.getSubmittedOnDate(); : loanApplicationTerms.getSubmittedOnDate();
@@ -92,7 +89,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc, final List<LoanScheduleModelRepaymentPeriod> expectedRepaymentPeriods = scheduledDateGenerator.generateRepaymentPeriods(mc,
periodStartDate, loanApplicationTerms, holidayDetailDTO); periodStartDate, loanApplicationTerms, holidayDetailDTO);
final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetail(), expectedRepaymentPeriods, loanApplicationTerms.toLoanProductRelatedDetailMinimumData(),
loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc); loanApplicationTerms.getInstallmentAmountInMultiplesOf(), mc);
final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size()); final List<LoanScheduleModelPeriod> periods = new ArrayList<>(expectedRepaymentPeriods.size());
@@ -141,7 +138,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
final BigDecimal totalOutstanding = 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.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount(), mc),
scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount(), mc).getAmount(), scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount(), mc).getAmount(),
totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
@@ -308,7 +305,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
} }
private void applyChargesForCurrentPeriod(final LoanScheduleModelRepaymentPeriod repaymentPeriod, final Set<LoanCharge> loanCharges, private void applyChargesForCurrentPeriod(final LoanScheduleModelRepaymentPeriod repaymentPeriod, final Set<LoanCharge> 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(), final PrincipalInterest principalInterest = new PrincipalInterest(repaymentPeriod.getPrincipalDue(),
repaymentPeriod.getInterestDue(), null); repaymentPeriod.getInterestDue(), null);
@@ -325,10 +322,10 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
} }
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges, private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> 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 Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
final MathContext mc) { final MathContext mc) {
Money cumulative = Money.zero(monetaryCurrency, mc); Money cumulative = Money.zero(currency, mc);
if (loanCharges != null) { if (loanCharges != null) {
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) { if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
@@ -358,11 +355,10 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
} }
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency, final Set<LoanCharge> loanCharges, final CurrencyData currency, final PrincipalInterest principalInterestForThisPeriod,
final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable,
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod, final boolean isFirstPeriod, final MathContext mc) {
final MathContext mc) { Money cumulative = Money.zero(currency, mc);
Money cumulative = Money.zero(monetaryCurrency, mc);
if (loanCharges != null) { if (loanCharges != null) {
for (final LoanCharge loanCharge : loanCharges) { for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) { if (loanCharge.isPenaltyCharge()) {
@@ -409,7 +405,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
return cumulative; return cumulative;
} }
private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams, private void updatePeriodsWithCharges(final CurrencyData currency, LoanScheduleParams scheduleParams,
final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) { final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod.isRepaymentPeriod()) { if (loanScheduleModelPeriod.isRepaymentPeriod()) {
@@ -31,18 +31,20 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDeta
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; 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.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
public interface EMICalculator { public interface EMICalculator {
@NotNull @NotNull
ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods, ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf, MathContext mc); @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf,
MathContext mc);
@NotNull @NotNull
ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull LoanProductRelatedDetail loanProductRelatedDetail, @NotNull List<LoanRepaymentScheduleInstallment> installments,
Integer installmentAmountInMultiplesOf, MathContext mc); @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail, Integer installmentAmountInMultiplesOf,
MathContext mc);
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate); Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
@@ -43,7 +43,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDeta
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; 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.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@@ -56,8 +56,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
@NotNull @NotNull
public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods, public ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
@NotNull LoanProductRelatedDetail loanProductRelatedDetail, final Integer installmentAmountInMultiplesOf, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
final MathContext mc) { final Integer installmentAmountInMultiplesOf, final MathContext mc) {
return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate, return generateInterestScheduleModel(periods, LoanScheduleModelRepaymentPeriod::periodFromDate,
LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); LoanScheduleModelRepaymentPeriod::periodDueDate, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
} }
@@ -65,7 +65,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
@NotNull @NotNull
public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel( public ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
@NotNull List<LoanRepaymentScheduleInstallment> installments, @NotNull LoanProductRelatedDetail loanProductRelatedDetail, @NotNull List<LoanRepaymentScheduleInstallment> installments,
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
final Integer installmentAmountInMultiplesOf, final MathContext mc) { final Integer installmentAmountInMultiplesOf, final MathContext mc) {
installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList(); installments = installments.stream().filter(installment -> !installment.isDownPayment() && !installment.isAdditional()).toList();
return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate, return generateInterestScheduleModel(installments, LoanRepaymentScheduleInstallment::getFromDate,
@@ -74,9 +75,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@NotNull @NotNull
private <T> ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate> from, private <T> ProgressiveLoanInterestScheduleModel generateInterestScheduleModel(@NotNull List<T> periods, Function<T, LocalDate> from,
Function<T, LocalDate> to, @NotNull LoanProductRelatedDetail loanProductRelatedDetail, Function<T, LocalDate> to, @NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
final Integer installmentAmountInMultiplesOf, final MathContext mc) { final Integer installmentAmountInMultiplesOf, final MathContext mc) {
final Money zero = Money.zero(loanProductRelatedDetail.getCurrency(), mc); final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>(); final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> { List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> {
RepaymentPeriod rp = new RepaymentPeriod(prev.get(), from.apply(e), to.apply(e), zero, mc); RepaymentPeriod rp = new RepaymentPeriod(prev.get(), from.apply(e), to.apply(e), zero, mc);
@@ -101,7 +102,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate, public void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate disbursementDueDate,
final Money disbursedAmount) { final Money disbursedAmount) {
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, scheduleModel.getZero()) scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(disbursementDueDate, disbursedAmount, scheduleModel.zero())
.ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors( .ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, disbursementDueDate), scheduleModel)); getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, disbursementDueDate), scheduleModel));
} }
@@ -127,8 +128,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final LocalDate interestRateChangeEffectiveDate = newInterestSubmittedOnDate.minusDays(1); final LocalDate interestRateChangeEffectiveDate = newInterestSubmittedOnDate.minusDays(1);
scheduleModel.addInterestRate(interestRateChangeEffectiveDate, newInterestRate); scheduleModel.addInterestRate(interestRateChangeEffectiveDate, newInterestRate);
scheduleModel scheduleModel
.changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.getZero(), .changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.zero(),
scheduleModel.getZero()) scheduleModel.zero())
.ifPresent(repaymentPeriod -> calculateEMIValueAndRateFactors( .ifPresent(repaymentPeriod -> calculateEMIValueAndRateFactors(
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel)); getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel));
} }
@@ -136,8 +137,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@Override @Override
public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate, public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
Money balanceCorrectionAmount) { Money balanceCorrectionAmount) {
scheduleModel scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, scheduleModel.zero(), balanceCorrectionAmount)
.changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, scheduleModel.getZero(), balanceCorrectionAmount)
.ifPresent(repaymentPeriod -> { .ifPresent(repaymentPeriod -> {
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel); calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
calculateOutstandingBalance(scheduleModel); calculateOutstandingBalance(scheduleModel);
@@ -296,13 +296,13 @@ public final class ProgressiveEMICalculator implements EMICalculator {
private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) { private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestScheduleModel scheduleModel) {
MathContext mc = scheduleModel.mc(); MathContext mc = scheduleModel.mc();
Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest) Money totalDueInterest = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(scheduleModel.zero(),
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 1.46 (m1, m2) -> m1.plus(m2, mc)); // 1.46
Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmi) Money totalEMI = scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getEmi).reduce(scheduleModel.zero(),
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc), (m1, m2) -> m1.plus(m2, mc)); // 101.48 (m1, m2) -> m1.plus(m2, mc)); // 101.48
Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream() Money totalDisbursedAmount = scheduleModel.repaymentPeriods().stream()
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount)) .flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
.reduce(Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), 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); Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid()) Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
@@ -400,7 +400,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, final RepaymentPeriod repaymentPeriod, BigDecimal calculateRateFactorPerPeriod(final ProgressiveLoanInterestScheduleModel scheduleModel, final RepaymentPeriod repaymentPeriod,
final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) { final LocalDate interestPeriodFromDate, final LocalDate interestPeriodDueDate) {
final MathContext mc = scheduleModel.mc(); final MathContext mc = scheduleModel.mc();
final LoanProductRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = scheduleModel.loanProductRelatedDetail();
final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate), final BigDecimal interestRate = calcNominalInterestRatePercentage(scheduleModel.getInterestRate(interestPeriodFromDate),
scheduleModel.mc()); scheduleModel.mc());
final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType()); final DaysInYearType daysInYearType = DaysInYearType.fromInt(loanProductRelatedDetail.getDaysInYearType());
@@ -493,7 +493,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
// TODO: double check // TODO: double check
final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation(); final Money outstandingBalance = startPeriod.getInitialBalanceForEmiRecalculation();
final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrency(), final Money equalMonthlyInstallment = Money.of(outstandingBalance.getCurrencyData(),
calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc); calculateEMIValue(rateFactorN, outstandingBalance.getAmount(), fnResult, mc), mc);
final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment); final Money finalEqualMonthlyInstallment = applyInstallmentAmountInMultiplesOf(scheduleModel, equalMonthlyInstallment);
@@ -732,7 +732,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
return scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueDate) // return scheduleModel.repaymentPeriods().stream().map(RepaymentPeriod::getDueDate) //
.map(repaymentPeriodDueDate -> getDueAmounts(scheduleModel, repaymentPeriodDueDate, subjectDate) // .map(repaymentPeriodDueDate -> getDueAmounts(scheduleModel, repaymentPeriodDueDate, subjectDate) //
.getDueInterest()) // .getDueInterest()) //
.reduce(scheduleModel.getZero(), Money::add); // .reduce(scheduleModel.zero(), Money::add); //
} }
private long getUncountablePeriods(final List<RepaymentPeriod> relatedRepaymentPeriods, final Money originalEmi) { private long getUncountablePeriods(final List<RepaymentPeriod> relatedRepaymentPeriods, final Money originalEmi) {
@@ -561,7 +561,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
@Test @Test
public void calculateChargebackAllocationMap() { public void calculateChargebackAllocationMap() {
Map<AllocationType, Money> result; Map<AllocationType, Money> 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), result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0, currency), BigDecimal.valueOf(50.0),
List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency); List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
@@ -24,6 +24,7 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.DaysInYearType;
@@ -38,6 +39,7 @@ class LoanScheduleGeneratorTest {
private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator(); private static final ProgressiveEMICalculator emiCalculator = new ProgressiveEMICalculator();
private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$"); private static final ApplicationCurrency APPLICATION_CURRENCY = new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$");
private static final CurrencyData CURRENCY = APPLICATION_CURRENCY.toData();
private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22); private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22);
private static final BigDecimal DISBURSEMENT_AMOUNT_100 = BigDecimal.valueOf(100); private static final BigDecimal DISBURSEMENT_AMOUNT_100 = BigDecimal.valueOf(100);
private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99); private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99);
@@ -50,7 +52,7 @@ class LoanScheduleGeneratorTest {
@Test @Test
void testGenerateLoanSchedule() { 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, 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); NOMINAL_INTEREST_RATE, false, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null);
@@ -85,7 +87,7 @@ class LoanScheduleGeneratorTest {
@Test @Test
void testGenerateLoanScheduleWithDownPayment() { 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, 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); NOMINAL_INTEREST_RATE, true, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, DOWN_PAYMENT_PORTION, null, null);
@@ -26,8 +26,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
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.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
@@ -39,7 +38,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDeta
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel; 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.data.RepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@@ -59,10 +58,10 @@ class ProgressiveEMICalculatorTest {
private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class); private static MockedStatic<ThreadLocalContextUtil> threadLocalContextUtil = Mockito.mockStatic(ThreadLocalContextUtil.class);
private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class); private static MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
private static MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN); private static 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 private static final CurrencyData currency = new CurrencyData("USD", "USD", 2, 1, "$", "USD");
.fromApplicationCurrency(new ApplicationCurrency("USD", "USD", 2, 1, "USD", "$"));
private static List<LoanRepaymentScheduleInstallment> periods; private static List<LoanRepaymentScheduleInstallment> periods;
private final BigDecimal interestRate = BigDecimal.valueOf(0.094822); private final BigDecimal interestRate = BigDecimal.valueOf(0.094822);
@@ -166,7 +165,7 @@ class ProgressiveEMICalculatorTest {
expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1))); expectedRepaymentPeriods.add(repayment(3, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 4, 1)));
expectedRepaymentPeriods.add(repayment(4, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 5, 1))); expectedRepaymentPeriods.add(repayment(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( final ProgressiveLoanInterestScheduleModel interestScheduleModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -205,7 +204,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -249,7 +248,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -285,7 +284,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -321,7 +320,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -367,7 +366,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -403,7 +402,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
@@ -445,7 +444,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
@@ -492,7 +491,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
@@ -538,7 +537,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 14));
@@ -584,7 +583,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
@@ -663,7 +662,7 @@ class ProgressiveEMICalculatorTest {
Assertions.assertEquals(0.1, toDouble(repaymentDetails4th.getDueInterest())); Assertions.assertEquals(0.1, toDouble(repaymentDetails4th.getDueInterest()));
// balance update on the last period, check the right interest interval split // 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); final RepaymentPeriod lastRepaymentPeriod = interestSchedule.repaymentPeriods().get(interestSchedule.repaymentPeriods().size() - 1);
Assertions.assertEquals(2, lastRepaymentPeriod.getInterestPeriods().size()); Assertions.assertEquals(2, lastRepaymentPeriod.getInterestPeriods().size());
Assertions.assertEquals(LocalDate.of(2024, 6, 1), lastRepaymentPeriod.getInterestPeriods().get(0).getFromDate()); Assertions.assertEquals(LocalDate.of(2024, 6, 1), lastRepaymentPeriod.getInterestPeriods().get(0).getFromDate());
@@ -691,7 +690,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
@@ -762,7 +761,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); 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)); threadLocalContextUtil.when(ThreadLocalContextUtil::getBusinessDate).thenReturn(LocalDate.of(2024, 2, 15));
@@ -841,7 +840,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -890,7 +889,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -939,7 +938,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -983,7 +982,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -1017,7 +1016,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -1050,7 +1049,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -1083,7 +1082,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.ACTUAL.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.WEEKS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(2);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -1116,7 +1115,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.INVALID.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.DAYS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(15);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestSchedule = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -1148,7 +1147,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -1210,7 +1209,7 @@ class ProgressiveEMICalculatorTest {
Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue()); Mockito.when(loanProductRelatedDetail.getDaysInMonthType()).thenReturn(DaysInMonthType.DAYS_30.getValue());
Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS); Mockito.when(loanProductRelatedDetail.getRepaymentPeriodFrequencyType()).thenReturn(PeriodFrequencyType.MONTHS);
Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1); Mockito.when(loanProductRelatedDetail.getRepayEvery()).thenReturn(1);
Mockito.when(loanProductRelatedDetail.getCurrency()).thenReturn(monetaryCurrency); Mockito.when(loanProductRelatedDetail.getCurrencyData()).thenReturn(currency);
final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel( final ProgressiveLoanInterestScheduleModel interestModel = emiCalculator.generatePeriodInterestScheduleModel(
expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc); expectedRepaymentPeriods, loanProductRelatedDetail, installmentAmountInMultiplesOf, mc);
@@ -1266,7 +1265,7 @@ class ProgressiveEMICalculatorTest {
} }
private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) { private static LoanScheduleModelRepaymentPeriod repayment(int periodNumber, LocalDate fromDate, LocalDate dueDate) {
final Money zeroAmount = Money.zero(monetaryCurrency); final Money zeroAmount = Money.zero(currency);
return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount, return LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, dueDate, zeroAmount, zeroAmount, zeroAmount, zeroAmount,
zeroAmount, zeroAmount, false, mc); zeroAmount, zeroAmount, false, mc);
} }
@@ -1326,6 +1325,6 @@ class ProgressiveEMICalculatorTest {
} }
private static Money toMoney(final double value) { private static Money toMoney(final double value) {
return Money.of(monetaryCurrency, BigDecimal.valueOf(value)); return Money.of(currency, BigDecimal.valueOf(value));
} }
} }
@@ -518,8 +518,8 @@ public class LoanScheduleAssembler {
fixedLength = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.FIXED_LENGTH, element); fixedLength = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.FIXED_LENGTH, element);
} }
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, return LoanApplicationTerms.assembleFrom(applicationCurrency.toData(), loanTermFrequency, loanTermPeriodFrequencyType,
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate,
calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods,
@@ -122,7 +122,7 @@ public class LoanUtilService {
boolean isPrincipalCompoundingDisabledForOverdueLoans = this.configurationDomainService boolean isPrincipalCompoundingDisabledForOverdueLoans = this.configurationDomainService
.isPrincipalCompoundingDisabledForOverdueLoans(); .isPrincipalCompoundingDisabledForOverdueLoans();
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency.toData(),
calculatedRepaymentsStartingFromDate, holidayDetails, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetails, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod, floatingRateDTO, calendar, calendarHistoryDataWrapper, overdurPenaltyWaitPeriod, floatingRateDTO, calendar, calendarHistoryDataWrapper,
isInterestChargedFromDateAsDisbursementDateEnabled, numberOfDays, isSkipRepaymentOnFirstMonth, isInterestChargedFromDateAsDisbursementDateEnabled, numberOfDays, isSkipRepaymentOnFirstMonth,
@@ -106,7 +106,7 @@ public class LoanRepaymentBusinessEventSerializerTest {
Loan loanForProcessing = Mockito.mock(Loan.class); Loan loanForProcessing = Mockito.mock(Loan.class);
LoanSummary loanSummary = Mockito.mock(LoanSummary.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, LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
@@ -122,9 +122,6 @@ public class LoanRepaymentBusinessEventSerializerTest {
when(loanForProcessing.getSummary()).thenReturn(loanSummary); when(loanForProcessing.getSummary()).thenReturn(loanSummary);
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0)); when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0));
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); 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(mapper.mapLocalDate(any())).thenReturn(loanInstallmentRepaymentDueDate.format(DateTimeFormatter.ISO_DATE));
when(pastDueDataMapper.map(any())).thenReturn(pastDueAmount); when(pastDueDataMapper.map(any())).thenReturn(pastDueAmount);
when(pastDueService.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing)).thenReturn(null); when(pastDueService.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing)).thenReturn(null);
@@ -156,7 +153,7 @@ public class LoanRepaymentBusinessEventSerializerTest {
Loan loanForProcessing = Mockito.mock(Loan.class); Loan loanForProcessing = Mockito.mock(Loan.class);
LoanSummary loanSummary = Mockito.mock(LoanSummary.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, LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
@@ -173,9 +170,6 @@ public class LoanRepaymentBusinessEventSerializerTest {
when(loanForProcessing.getSummary()).thenReturn(loanSummary); when(loanForProcessing.getSummary()).thenReturn(loanSummary);
when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0)); when(loanSummary.getTotalOutstanding()).thenReturn(BigDecimal.valueOf(0.0));
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); 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(mapper.mapLocalDate(any())).thenReturn(loanInstallmentRepaymentDueDate.format(DateTimeFormatter.ISO_DATE));
when(pastDueDataMapper.map(any())).thenReturn(pastDueAmount); when(pastDueDataMapper.map(any())).thenReturn(pastDueAmount);
when(pastDueService.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing)).thenReturn(null); when(pastDueService.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing)).thenReturn(null);
@@ -92,8 +92,8 @@ public class DefaultScheduledDateGeneratorTest {
LocalDate dueRepaymentPeriodDate = LocalDate.of(2024, 2, 1); LocalDate dueRepaymentPeriodDate = LocalDate.of(2024, 2, 1);
LocalDate submittedOnDate = LocalDate.of(2024, 1, 1); LocalDate submittedOnDate = LocalDate.of(2024, 1, 1);
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(dollarCurrency, 1, MONTHS, 4, 1, MONTHS, null, LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(dollarCurrency.toData(), 1, MONTHS, 4, 1, MONTHS,
INVALID, EQUAL_PRINCIPAL, FLAT, ZERO, MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount, null, INVALID, EQUAL_PRINCIPAL, FLAT, ZERO, MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount,
expectedDisbursementDate, null, dueRepaymentPeriodDate, null, null, null, null, null, expectedDisbursementDate, null, dueRepaymentPeriodDate, null, null, null, null, null,
Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, BigDecimal.valueOf(36_000L), 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, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, null, NONE, null, ZERO,
@@ -164,12 +164,13 @@ public class DefaultScheduledDateGeneratorTest {
LocalDate expectedDisbursementDate = LocalDate.of(2023, 10, 26); LocalDate expectedDisbursementDate = LocalDate.of(2023, 10, 26);
LocalDate submittedOnDate = LocalDate.of(2023, 10, 24); LocalDate submittedOnDate = LocalDate.of(2023, 10, 24);
return LoanApplicationTerms.assembleFrom(dollarCurrency, 1, MONTHS, 1, 1, MONTHS, null, INVALID, EQUAL_PRINCIPAL, FLAT, ZERO, return LoanApplicationTerms.assembleFrom(dollarCurrency.toData(), 1, MONTHS, 1, 1, MONTHS, null, INVALID, EQUAL_PRINCIPAL, FLAT,
MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount, expectedDisbursementDate, null, dueRepaymentPeriodDate, ZERO, MONTHS, ZERO, SAME_AS_REPAYMENT_PERIOD, false, principalAmount, expectedDisbursementDate, null,
null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, dueRepaymentPeriodDate, null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null,
BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null,
null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, null, false, null, ZERO, null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false,
DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null); null, false, DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null,
null);
} }
private HolidayDetailDTO createHolidayDTO() { private HolidayDetailDTO createHolidayDTO() {
@@ -80,7 +80,7 @@ public class LoanCalculateRepaymentPastDueServiceTest {
// given // given
LocalDate businessDate = DateUtils.getBusinessLocalDate(); LocalDate businessDate = DateUtils.getBusinessLocalDate();
Loan loanForProcessing = Mockito.mock(Loan.class); Loan loanForProcessing = Mockito.mock(Loan.class);
MonetaryCurrency loanCurrency = Mockito.mock(MonetaryCurrency.class); MonetaryCurrency loanCurrency = new MonetaryCurrency("CODE", 1, 1);
// repayments // repayments
// closed repayment // closed repayment
@@ -111,9 +111,6 @@ public class LoanCalculateRepaymentPastDueServiceTest {
repaymentInstallment_2, repaymentInstallment_upcoming); repaymentInstallment_2, repaymentInstallment_upcoming);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepayments); when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepayments);
when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency);
when(loanCurrency.getCode()).thenReturn("CODE");
when(loanCurrency.getCurrencyInMultiplesOf()).thenReturn(1);
when(loanCurrency.getDigitsAfterDecimal()).thenReturn(1);
// when // when
LoanRepaymentPastDueData pastDueAmount = underTest.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing); LoanRepaymentPastDueData pastDueAmount = underTest.retrieveLoanRepaymentPastDueAmountTillDate(loanForProcessing);
+1
View File
@@ -69,6 +69,7 @@ include ':fineract-avro-schemas'
include ':fineract-e2e-tests-core' include ':fineract-e2e-tests-core'
include ':fineract-e2e-tests-runner' include ':fineract-e2e-tests-runner'
include 'fineract-progressive-loan' include 'fineract-progressive-loan'
include 'fineract-progressive-loan-embeddable-schedule-generator'
// NOTE: custom Docker image with all custom modules included // NOTE: custom Docker image with all custom modules included
include ':custom:docker' include ':custom:docker'
// NOTE: dynamically load custom modules with pattern "custom -> company -> category -> module" // NOTE: dynamically load custom modules with pattern "custom -> company -> category -> module"