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