FINERACT-1981: Embeddable Progressive Loan Schedule generator

This commit is contained in:
Janos Meszaros
2024-12-10 15:04:16 +01:00
committed by Adam Saghy
parent d0006a2169
commit bb8480a03d
41 changed files with 849 additions and 439 deletions
@@ -0,0 +1,51 @@
name: Fineract Build Progressive Loan Embeddable Jar & Test with a Sample Application
on: [push, pull_request]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4
with:
java-version: '17'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@cc4fc85e6b35bafd578d5ffbc76a5518407e1af0 # v4.2.1
- name: Build Embeddable Jar
run: ./gradlew --no-daemon --console=plain :fineract-progressive-loan-embeddable-schedule-generator:shadowJar
- name: Pick up the JAR filename
run: |
EMBEDDABLE_JAR_FILE=(`ls fineract-progressive-loan-embeddable-schedule-generator/build/libs/*-all.jar | head -n 1`)
echo "EMBEDDABLE_JAR_FILE=$EMBEDDABLE_JAR_FILE" >> $GITHUB_ENV
- name: Build Sample Application
run: |
mkdir sample-app
javac -cp $EMBEDDABLE_JAR_FILE -d sample-app fineract-progressive-loan-embeddable-schedule-generator/misc/Main.java
env:
EMBEDDABLE_JAR_FILE: ${{ env.EMBEDDABLE_JAR_FILE }}
- name: Run Schedule Generator Sample Application
run: |
java -cp $EMBEDDABLE_JAR_FILE:sample-app Main
java -cp $EMBEDDABLE_JAR_FILE:sample-app Main 25
env:
EMBEDDABLE_JAR_FILE: ${{ env.EMBEDDABLE_JAR_FILE }}
- name: Archive test results
if: always()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
with:
name: test-results
path: |
build/reports/
fineract-progressive-loan-embeddable-schedule-generator/build/reports/
+6 -3
View File
@@ -46,7 +46,8 @@ buildscript {
'fineract-avro-schemas',
'fineract-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()
@@ -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) {
@@ -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);
}
@@ -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() {
@@ -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() {
@@ -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);
}
}
@@ -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() {
@@ -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(),
@@ -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;
}
@@ -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()));
@@ -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;
}
@@ -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;
@@ -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,
@@ -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<>();
@@ -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;
@@ -1,118 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
public final class LoanRescheduleModel {
private final Collection<LoanRescheduleModelRepaymentPeriod> periods;
private final Collection<LoanRepaymentScheduleHistory> oldPeriods;
private final ApplicationCurrency applicationCurrency;
private final int loanTermInDays;
private final Money totalPrincipalDisbursed;
private final BigDecimal totalPrincipalExpected;
private final BigDecimal totalPrincipalPaid;
private final BigDecimal totalInterestCharged;
private final BigDecimal totalFeeChargesCharged;
private final BigDecimal totalPenaltyChargesCharged;
private final BigDecimal totalRepaymentExpected;
private final BigDecimal totalOutstanding;
private LoanRescheduleModel(final Collection<LoanRescheduleModelRepaymentPeriod> periods,
final Collection<LoanRepaymentScheduleHistory> oldPeriods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
this.periods = periods;
this.oldPeriods = oldPeriods;
this.applicationCurrency = applicationCurrency;
this.loanTermInDays = loanTermInDays;
this.totalPrincipalDisbursed = principalDisbursed;
this.totalPrincipalExpected = totalPrincipalExpected;
this.totalPrincipalPaid = totalPrincipalPaid;
this.totalInterestCharged = totalInterestCharged;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalOutstanding = totalOutstanding;
}
public static LoanRescheduleModel instance(final Collection<LoanRescheduleModelRepaymentPeriod> periods,
final Collection<LoanRepaymentScheduleHistory> oldPeriods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
return new LoanRescheduleModel(periods, oldPeriods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstanding);
}
public static LoanRescheduleModel createWithSchedulehistory(LoanRescheduleModel loanRescheduleModel,
final Collection<LoanRepaymentScheduleHistory> oldPeriods) {
return new LoanRescheduleModel(loanRescheduleModel.periods, oldPeriods, loanRescheduleModel.applicationCurrency,
loanRescheduleModel.loanTermInDays, loanRescheduleModel.totalPrincipalDisbursed, loanRescheduleModel.totalPrincipalExpected,
loanRescheduleModel.totalPrincipalPaid, loanRescheduleModel.totalInterestCharged,
loanRescheduleModel.totalFeeChargesCharged, loanRescheduleModel.totalPenaltyChargesCharged,
loanRescheduleModel.totalRepaymentExpected, loanRescheduleModel.totalOutstanding);
}
public LoanScheduleData toData() {
final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal();
final Integer inMultiplesOf = this.totalPrincipalDisbursed.getCurrencyInMultiplesOf();
final CurrencyData currency = this.applicationCurrency.toData(decimalPlaces, inMultiplesOf);
final BigDecimal totalCredits = BigDecimal.ZERO;
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
for (final LoanRescheduleModalPeriod modelPeriod : this.periods) {
periodsData.add(modelPeriod.toData());
}
final BigDecimal totalWaived = null;
final BigDecimal totalWrittenOff = null;
final BigDecimal totalRepayment = null;
final BigDecimal totalPaidInAdvance = null;
final BigDecimal totalPaidLate = null;
return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(),
this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged,
this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment,
totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits);
}
public Collection<LoanRescheduleModelRepaymentPeriod> getPeriods() {
return this.periods;
}
public Collection<LoanRepaymentScheduleHistory> getOldPeriods() {
return this.oldPeriods;
}
}
@@ -0,0 +1,186 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanproduct.data;
import java.math.BigDecimal;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
public class LoanProductRelatedDetailMinimumData implements LoanProductMinimumRepaymentScheduleRelatedDetail {
private final CurrencyData currency;
private final Money principal;
private final Money inArrearsTolerance;
private final BigDecimal interestRatePerPeriod;
private final BigDecimal annualNominalInterestRate;
private final Integer interestChargingGrace;
private final Integer interestPaymentGrace;
private final Integer principalGrace;
private final Integer recurringMoratoriumOnPrincipalPeriods;
private final InterestMethod interestMethod;
private final InterestCalculationPeriodMethod interestCalculationPeriodMethod;
private final DaysInYearType daysInYearType;
private final DaysInMonthType daysInMonthType;
private final AmortizationMethod amortizationMethod;
private final PeriodFrequencyType repaymentPeriodFrequencyType;
private final Integer repaymentEvery;
private final Integer numberOfRepayments;
public LoanProductRelatedDetailMinimumData(CurrencyData currency, Money principal, Money inArrearsTolerance,
BigDecimal interestRatePerPeriod, BigDecimal annualNominalInterestRate, Integer interestChargingGrace,
Integer interestPaymentGrace, Integer principalGrace, Integer recurringMoratoriumOnPrincipalPeriods,
InterestMethod interestMethod, InterestCalculationPeriodMethod interestCalculationPeriodMethod, DaysInYearType daysInYearType,
DaysInMonthType daysInMonthType, AmortizationMethod amortizationMethod, PeriodFrequencyType repaymentPeriodFrequencyType,
Integer repaymentEvery, Integer numberOfRepayments) {
this.currency = currency;
this.principal = principal;
this.inArrearsTolerance = inArrearsTolerance;
this.interestRatePerPeriod = interestRatePerPeriod;
this.annualNominalInterestRate = annualNominalInterestRate;
this.interestChargingGrace = defaultToNullIfZero(interestChargingGrace);
this.interestPaymentGrace = defaultToNullIfZero(interestPaymentGrace);
this.principalGrace = defaultToNullIfZero(principalGrace);
this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods;
this.interestMethod = interestMethod;
this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
this.daysInYearType = daysInYearType;
this.daysInMonthType = daysInMonthType;
this.amortizationMethod = amortizationMethod;
this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType;
this.repaymentEvery = repaymentEvery;
this.numberOfRepayments = numberOfRepayments;
}
private Integer defaultToNullIfZero(final Integer value) {
Integer defaultTo = value;
if (Integer.valueOf(0).equals(value)) {
defaultTo = null;
}
return defaultTo;
}
@Override
public CurrencyData getCurrencyData() {
return currency;
}
@Override
public Money getPrincipal() {
return principal;
}
@Override
public Integer getGraceOnInterestCharged() {
return interestChargingGrace;
}
@Override
public Integer getGraceOnInterestPayment() {
return interestPaymentGrace;
}
@Override
public Integer getGraceOnPrincipalPayment() {
return principalGrace;
}
@Override
public Integer getRecurringMoratoriumOnPrincipalPeriods() {
return recurringMoratoriumOnPrincipalPeriods;
}
@Override
public Money getInArrearsTolerance() {
return inArrearsTolerance;
}
@Override
public BigDecimal getNominalInterestRatePerPeriod() {
return interestRatePerPeriod;
}
@Override
public PeriodFrequencyType getInterestPeriodFrequencyType() {
return repaymentPeriodFrequencyType;
}
@Override
public BigDecimal getAnnualNominalInterestRate() {
return annualNominalInterestRate;
}
@Override
public InterestMethod getInterestMethod() {
return interestMethod;
}
@Override
public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
return interestCalculationPeriodMethod;
}
@Override
public Integer getRepayEvery() {
return repaymentEvery;
}
@Override
public PeriodFrequencyType getRepaymentPeriodFrequencyType() {
return repaymentPeriodFrequencyType;
}
@Override
public Integer getNumberOfRepayments() {
return numberOfRepayments;
}
@Override
public AmortizationMethod getAmortizationMethod() {
return amortizationMethod;
}
@Override
public Integer getGraceOnArrearsAgeing() {
return 0;
}
@Override
public Integer getDaysInMonthType() {
return daysInMonthType.getValue();
}
@Override
public Integer getDaysInYearType() {
return daysInYearType.getValue();
}
}
@@ -19,7 +19,7 @@
package org.apache.fineract.portfolio.loanproduct.domain;
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();
}
@@ -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());
}
}
}
}
@@ -0,0 +1,42 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
@SuppressWarnings("unused")
public class EmbeddableProgressiveLoanScheduleGenerator {
private final ProgressiveLoanScheduleGenerator scheduleGenerator;
private final ScheduledDateGenerator scheduledDateGenerator;
private final EMICalculator emiCalculator;
public EmbeddableProgressiveLoanScheduleGenerator() {
this.emiCalculator = new ProgressiveEMICalculator();
this.scheduledDateGenerator = new DefaultScheduledDateGenerator();
this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator);
}
public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) {
return scheduleGenerator.generate(mc, modelData);
}
}
@@ -48,7 +48,7 @@ public class ProgressiveTransactionCtx extends TransactionCtx {
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
ProgressiveLoanInterestScheduleModel model) {
super(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
sumOfInterestRefundAmount = model.getZero();
sumOfInterestRefundAmount = model.zero();
this.model = model;
}
}
@@ -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() {
@@ -75,7 +75,7 @@ public class LoanSchedulePlan {
});
return new LoanSchedulePlan(periods, //
model.getApplicationCurrency().toData(), //
model.getCurrency(), //
model.getLoanTermInDays(), //
model.getTotalPrincipalDisbursed().getAmount(), //
model.getTotalPrincipalExpected(), //
@@ -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) {
@@ -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()) {
@@ -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);
@@ -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) {
@@ -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);
@@ -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);
@@ -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));
}
}
@@ -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,
@@ -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,
@@ -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);
@@ -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() {
@@ -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);
+1
View File
@@ -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"