FINERACT-1981: Progressive loan schedule handling
This commit is contained in:
committed by
Arnold Gálovics
parent
71d2e51051
commit
2ea23be9a4
+17
@@ -450,4 +450,21 @@ public final class MathUtil {
|
||||
}
|
||||
return percentageOf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate percentage of a value
|
||||
*
|
||||
* @param value
|
||||
* @param percentage
|
||||
* @param mc
|
||||
* @return
|
||||
*/
|
||||
public static BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage, final MathContext mc) {
|
||||
BigDecimal percentageOf = BigDecimal.ZERO;
|
||||
if (isGreaterThanZero(value)) {
|
||||
final BigDecimal multiplicand = percentage.divide(BigDecimal.valueOf(100L), mc);
|
||||
percentageOf = value.multiply(multiplicand, mc);
|
||||
}
|
||||
return percentageOf;
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -129,6 +129,15 @@ public class Money implements Comparable<Money> {
|
||||
return amountScaled;
|
||||
}
|
||||
|
||||
public static Money roundToMultiplesOf(final Money existingVal, final Integer inMultiplesOf) {
|
||||
BigDecimal amountScaled = existingVal.getAmount();
|
||||
BigDecimal inMultiplesOfValue = BigDecimal.valueOf(inMultiplesOf.intValue());
|
||||
if (inMultiplesOfValue.compareTo(BigDecimal.ZERO) > 0) {
|
||||
amountScaled = amountScaled.divide(inMultiplesOfValue, 0, RoundingMode.HALF_UP).multiply(inMultiplesOfValue);
|
||||
}
|
||||
return Money.of(existingVal.getCurrency(), amountScaled);
|
||||
}
|
||||
|
||||
public static double ceiling(final double n, final double s) {
|
||||
double c;
|
||||
|
||||
|
||||
+1
-4
@@ -717,15 +717,12 @@ public final class CalendarUtils {
|
||||
if (recur == null) {
|
||||
return null;
|
||||
}
|
||||
LocalDate date = startDate;
|
||||
final LocalDate seedDate = calendar.getStartDateLocalDate();
|
||||
/**
|
||||
* if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) { date = date.plusDays(1); }
|
||||
**/
|
||||
|
||||
final LocalDate scheduleDate = getNextRecurringDate(recur, seedDate, date);
|
||||
|
||||
return scheduleDate;
|
||||
return getNextRecurringDate(recur, seedDate, startDate);
|
||||
}
|
||||
|
||||
public static void validateNthDayOfMonthFrequency(DataValidatorBuilder baseDataValidator, final String repeatsOnNthDayOfMonthParamName,
|
||||
|
||||
+13
-15
@@ -1289,7 +1289,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
updateLoanSummaryDerivedFields();
|
||||
applyAccurals();
|
||||
|
||||
}
|
||||
|
||||
public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments) {
|
||||
@@ -2530,9 +2529,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||
if ((interestRecalculationEnabledParam && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
} else if (downPaymentEnabled) {
|
||||
LoanScheduleDTO loanSchedule = getRecalculatedSchedule(scheduleGeneratorDTO);
|
||||
updateLoanSchedule(loanSchedule.getInstallments());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2827,11 +2823,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
|
||||
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
|
||||
LoanScheduleGenerator loanScheduleGenerator = null;
|
||||
LoanScheduleGenerator loanScheduleGenerator;
|
||||
if (loanApplicationTerms.isEqualAmortization()) {
|
||||
if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) {
|
||||
final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
|
||||
.create(InterestMethod.DECLINING_BALANCE);
|
||||
.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE);
|
||||
Set<LoanCharge> loanCharges = getActiveCharges();
|
||||
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges,
|
||||
scheduleGeneratorDTO.getHolidayDetailDTO());
|
||||
@@ -2840,9 +2836,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
|
||||
|
||||
}
|
||||
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(InterestMethod.FLAT);
|
||||
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(),
|
||||
InterestMethod.FLAT);
|
||||
} else {
|
||||
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getInterestMethod());
|
||||
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(),
|
||||
loanApplicationTerms.getInterestMethod());
|
||||
}
|
||||
|
||||
return loanScheduleGenerator.generate(mc, loanApplicationTerms, getActiveCharges(), scheduleGeneratorDTO.getHolidayDetailDTO());
|
||||
@@ -3401,8 +3399,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||
List<LoanTransaction> trans = getLoanTransactions();
|
||||
for (final LoanTransaction transaction : trans) {
|
||||
if (transaction.isNotReversed()
|
||||
&& (transaction.isChargeOff() || !(transaction.isDisbursement() || transaction.isNonMonetaryTransaction()))) {
|
||||
if (transaction.isNotReversed() && (transaction.isChargeOff() || !transaction.isNonMonetaryTransaction())) {
|
||||
repaymentsOrWaivers.add(transaction);
|
||||
}
|
||||
}
|
||||
@@ -3413,9 +3410,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
public List<LoanTransaction> retrieveListOfTransactionsPostDisbursementExcludeAccruals() {
|
||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
if (transaction.isNotReversed()
|
||||
&& !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement()
|
||||
|| transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) {
|
||||
if (transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isRepaymentAtDisbursement()
|
||||
|| transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) {
|
||||
repaymentsOrWaivers.add(transaction);
|
||||
}
|
||||
}
|
||||
@@ -5754,7 +5750,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return null;
|
||||
}
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
|
||||
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory()
|
||||
.create(this.loanRepaymentScheduleDetail.getLoanScheduleType(), interestMethod);
|
||||
|
||||
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||
final MathContext mc = new MathContext(19, roundingMode);
|
||||
@@ -5778,7 +5775,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
|
||||
|
||||
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
|
||||
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
|
||||
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
|
||||
|
||||
+11
-5
@@ -419,13 +419,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
this.penaltyAccrued = null;
|
||||
}
|
||||
|
||||
public void resetChargesFields() {
|
||||
public void resetChargesCharged() {
|
||||
this.feeChargesCharged = null;
|
||||
this.feeChargesWaived = null;
|
||||
this.feeChargesWrittenOff = null;
|
||||
this.penaltyCharges = null;
|
||||
this.penaltyChargesWaived = null;
|
||||
this.penaltyChargesWrittenOff = null;
|
||||
}
|
||||
|
||||
public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
||||
@@ -656,6 +652,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.plus(this.penaltyCharges).getAmount());
|
||||
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.plus(this.penaltyChargesWaived).getAmount());
|
||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.plus(this.penaltyChargesWrittenOff).getAmount());
|
||||
checkIfRepaymentPeriodObligationsAreMet(getObligationsMetOnDate(), feeChargesDue.getCurrency());
|
||||
}
|
||||
|
||||
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
|
||||
@@ -961,4 +958,13 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
return isDownPayment;
|
||||
}
|
||||
|
||||
public void resetBalances() {
|
||||
resetDerivedComponents();
|
||||
resetPrincipalDue();
|
||||
resetChargesCharged();
|
||||
}
|
||||
|
||||
public void resetPrincipalDue() {
|
||||
this.principal = null;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-17
@@ -218,6 +218,7 @@ public final class LoanApplicationTerms {
|
||||
private LocalDate submittedOnDate;
|
||||
private boolean isScheduleExtensionForDownPaymentDisabled;
|
||||
private Money disbursedPrincipal;
|
||||
private final LoanScheduleType loanScheduleType;
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
|
||||
@@ -244,7 +245,8 @@ public final class LoanApplicationTerms {
|
||||
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
|
||||
final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
||||
final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType,
|
||||
final LocalDate submittedOnDate, final Boolean isScheduleExtensionForDownPaymentDisabled) {
|
||||
final LocalDate submittedOnDate, final Boolean isScheduleExtensionForDownPaymentDisabled,
|
||||
final LoanScheduleType loanScheduleType) {
|
||||
|
||||
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
|
||||
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
|
||||
@@ -262,7 +264,8 @@ public final class LoanApplicationTerms {
|
||||
isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false,
|
||||
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
|
||||
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
|
||||
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled);
|
||||
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled,
|
||||
loanScheduleType);
|
||||
|
||||
}
|
||||
|
||||
@@ -318,6 +321,7 @@ public final class LoanApplicationTerms {
|
||||
isAutoRepaymentForDownPaymentEnabled = loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment();
|
||||
isScheduleExtensionForDownPaymentDisabled = loanProductRelatedDetail.isDisableScheduleExtensionForDownPayment();
|
||||
}
|
||||
LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType();
|
||||
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod,
|
||||
interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate,
|
||||
@@ -333,7 +337,8 @@ public final class LoanApplicationTerms {
|
||||
allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday,
|
||||
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
|
||||
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment,
|
||||
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled);
|
||||
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled,
|
||||
loanScheduleType);
|
||||
}
|
||||
|
||||
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
@@ -362,7 +367,7 @@ public final class LoanApplicationTerms {
|
||||
final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled,
|
||||
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled,
|
||||
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate,
|
||||
final boolean isScheduleExtensionForDownPaymentDisabled) {
|
||||
final boolean isScheduleExtensionForDownPaymentDisabled, final LoanScheduleType loanScheduleType) {
|
||||
|
||||
this.currency = currency;
|
||||
this.loanTermFrequency = loanTermFrequency;
|
||||
@@ -446,6 +451,7 @@ public final class LoanApplicationTerms {
|
||||
this.repaymentStartDateType = repaymentStartDateType;
|
||||
this.submittedOnDate = submittedOnDate;
|
||||
this.isScheduleExtensionForDownPaymentDisabled = isScheduleExtensionForDownPaymentDisabled;
|
||||
this.loanScheduleType = loanScheduleType;
|
||||
}
|
||||
|
||||
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
|
||||
@@ -600,7 +606,7 @@ public final class LoanApplicationTerms {
|
||||
}
|
||||
|
||||
public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
|
||||
final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
|
||||
final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate,
|
||||
final LocalDate periodEndDate) {
|
||||
|
||||
@@ -653,8 +659,6 @@ public final class LoanApplicationTerms {
|
||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
|
||||
} else if (isInterestFreeGracePeriod(periodNumber)) {
|
||||
interestForInstallment = interestForInstallment.zero();
|
||||
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
|
||||
interestForInstallment = interestForThisInstallmentAfterGrace;
|
||||
} else {
|
||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace);
|
||||
}
|
||||
@@ -1185,7 +1189,7 @@ public final class LoanApplicationTerms {
|
||||
}
|
||||
|
||||
private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance,
|
||||
final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance,
|
||||
final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) {
|
||||
|
||||
Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance, periodStartDate,
|
||||
@@ -1195,30 +1199,30 @@ public final class LoanApplicationTerms {
|
||||
interest = interest.zero();
|
||||
}
|
||||
|
||||
Double fraction = interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
BigDecimal fraction = interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
|
||||
if (isInterestFreeGracePeriod(periodNumber)) {
|
||||
interest = interest.zero();
|
||||
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
|
||||
|
||||
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
|
||||
if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0) {
|
||||
interest = interest.zero();
|
||||
fraction = fraction - Integer.valueOf(1).doubleValue();
|
||||
fraction = fraction.subtract(BigDecimal.ONE);
|
||||
|
||||
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.25")
|
||||
&& interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
|
||||
} else if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.valueOf(0.25)) > 0
|
||||
&& interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ONE) < 0) {
|
||||
|
||||
final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
||||
interest = interest.minus(graceOnInterestForRepaymentPeriod);
|
||||
fraction = Double.valueOf("0");
|
||||
fraction = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
return interest;
|
||||
}
|
||||
|
||||
private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) {
|
||||
return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.0");
|
||||
private boolean isInterestFreeGracePeriodFromDate(BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) {
|
||||
return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
|
||||
private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) {
|
||||
@@ -1291,7 +1295,7 @@ public final class LoanApplicationTerms {
|
||||
this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(),
|
||||
this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled,
|
||||
this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled,
|
||||
this.isScheduleExtensionForDownPaymentDisabled);
|
||||
this.isScheduleExtensionForDownPaymentDisabled, this.loanScheduleType);
|
||||
}
|
||||
|
||||
public Integer getLoanTermFrequency() {
|
||||
@@ -1322,6 +1326,10 @@ public final class LoanApplicationTerms {
|
||||
this.principal = principal;
|
||||
}
|
||||
|
||||
public void setDisbursedPrincipal(Money disbursedPrincipal) {
|
||||
this.disbursedPrincipal = disbursedPrincipal;
|
||||
}
|
||||
|
||||
public LocalDate getInterestChargedFromLocalDate() {
|
||||
return this.interestChargedFromDate;
|
||||
}
|
||||
@@ -1781,4 +1789,8 @@ public final class LoanApplicationTerms {
|
||||
public Integer getInstallmentAmountInMultiplesOf() {
|
||||
return installmentAmountInMultiplesOf;
|
||||
}
|
||||
|
||||
public LoanScheduleType getLoanScheduleType() {
|
||||
return loanScheduleType;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,5 +22,5 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||
|
||||
public interface LoanScheduleGeneratorFactory {
|
||||
|
||||
LoanScheduleGenerator create(InterestMethod interestMethod);
|
||||
LoanScheduleGenerator create(LoanScheduleType loanScheduleType, InterestMethod interestMethod);
|
||||
}
|
||||
|
||||
+8
-8
@@ -20,7 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
@@ -32,7 +32,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
|
||||
*/
|
||||
public final class LoanScheduleModel {
|
||||
|
||||
private final Collection<LoanScheduleModelPeriod> periods;
|
||||
private final List<LoanScheduleModelPeriod> periods;
|
||||
private final ApplicationCurrency applicationCurrency;
|
||||
private final int loanTermInDays;
|
||||
private final Money totalPrincipalDisbursed;
|
||||
@@ -44,7 +44,7 @@ public final class LoanScheduleModel {
|
||||
private final BigDecimal totalRepaymentExpected;
|
||||
private final BigDecimal totalOutstanding;
|
||||
|
||||
public static LoanScheduleModel from(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
|
||||
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) {
|
||||
@@ -54,7 +54,7 @@ public final class LoanScheduleModel {
|
||||
totalOutstanding);
|
||||
}
|
||||
|
||||
public static LoanScheduleModel withOverdueChargeUpdation(final Collection<LoanScheduleModelPeriod> periods,
|
||||
public static LoanScheduleModel withOverdueChargeUpdation(final List<LoanScheduleModelPeriod> periods,
|
||||
final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) {
|
||||
|
||||
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
|
||||
@@ -63,7 +63,7 @@ public final class LoanScheduleModel {
|
||||
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
|
||||
}
|
||||
|
||||
public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection<LoanScheduleModelPeriod> periods,
|
||||
public static LoanScheduleModel withLoanScheduleModelPeriods(final List<LoanScheduleModelPeriod> periods,
|
||||
final LoanScheduleModel loanScheduleModel) {
|
||||
|
||||
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
|
||||
@@ -72,7 +72,7 @@ public final class LoanScheduleModel {
|
||||
loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
|
||||
}
|
||||
|
||||
private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
|
||||
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) {
|
||||
@@ -97,7 +97,7 @@ public final class LoanScheduleModel {
|
||||
|
||||
final BigDecimal totalCredits = BigDecimal.ZERO;
|
||||
|
||||
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
|
||||
final List<LoanSchedulePeriodData> periodsData = new ArrayList<>();
|
||||
for (final LoanScheduleModelPeriod modelPeriod : this.periods) {
|
||||
periodsData.add(modelPeriod.toData());
|
||||
}
|
||||
@@ -114,7 +114,7 @@ public final class LoanScheduleModel {
|
||||
totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits);
|
||||
}
|
||||
|
||||
public Collection<LoanScheduleModelPeriod> getPeriods() {
|
||||
public List<LoanScheduleModelPeriod> getPeriods() {
|
||||
return this.periods;
|
||||
}
|
||||
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public enum LoanScheduleType {
|
||||
CUMULATIVE, PROGRESSIVE
|
||||
}
|
||||
+4
-2
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
|
||||
@@ -25,7 +27,7 @@ public interface PaymentPeriodsInOneYearCalculator {
|
||||
|
||||
Integer calculate(PeriodFrequencyType repaymentFrequencyType);
|
||||
|
||||
double calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
|
||||
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaidEvery);
|
||||
BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
|
||||
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, MathContext mc);
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -156,4 +156,7 @@ public interface LoanProductConstants {
|
||||
|
||||
String ENABLE_INSTALLMENT_LEVEL_DELINQUENCY = "enableInstallmentLevelDelinquency";
|
||||
|
||||
// loan schedule type
|
||||
String LOAN_SCHEDULE_TYPE = "loanScheduleType";
|
||||
|
||||
}
|
||||
|
||||
+13
-3
@@ -62,6 +62,7 @@ import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
|
||||
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
|
||||
import org.apache.fineract.portfolio.fund.domain.Fund;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
|
||||
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
|
||||
import org.apache.fineract.portfolio.rate.domain.Rate;
|
||||
@@ -303,6 +304,14 @@ public class LoanProduct extends AbstractPersistableCustom {
|
||||
final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
|
||||
final ExternalId externalId = ExternalIdFactory.produce(command.stringValueOfParameterNamedAllowingNull("externalId"));
|
||||
|
||||
final LoanScheduleType loanScheduleType;
|
||||
if (command.hasParameter("loanScheduleType")) {
|
||||
loanScheduleType = LoanScheduleType.valueOf(command.stringValueOfParameterNamed("loanScheduleType"));
|
||||
} else {
|
||||
// For backward compatibility
|
||||
loanScheduleType = LoanScheduleType.CUMULATIVE;
|
||||
}
|
||||
|
||||
final boolean useBorrowerCycle = command
|
||||
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME);
|
||||
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations = new HashSet<>();
|
||||
@@ -427,7 +436,7 @@ public class LoanProduct extends AbstractPersistableCustom {
|
||||
disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber,
|
||||
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment,
|
||||
enableAutoRepaymentForDownPayment, repaymentStartDateType, disableScheduleExtensionForDownPayment,
|
||||
enableInstallmentLevelDelinquency);
|
||||
enableInstallmentLevelDelinquency, loanScheduleType);
|
||||
|
||||
}
|
||||
|
||||
@@ -642,7 +651,8 @@ public class LoanProduct extends AbstractPersistableCustom {
|
||||
final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent,
|
||||
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
||||
final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType,
|
||||
final boolean disableScheduleExtensionForDownPayment, final boolean enableInstallmentLevelDelinquency) {
|
||||
final boolean disableScheduleExtensionForDownPayment, final boolean enableInstallmentLevelDelinquency,
|
||||
final LoanScheduleType loanScheduleType) {
|
||||
this.fund = fund;
|
||||
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
|
||||
|
||||
@@ -684,7 +694,7 @@ public class LoanProduct extends AbstractPersistableCustom {
|
||||
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(),
|
||||
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
|
||||
enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment);
|
||||
enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment, loanScheduleType);
|
||||
|
||||
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
|
||||
|
||||
|
||||
+14
-3
@@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearType;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
|
||||
|
||||
/**
|
||||
@@ -141,6 +142,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
@Column(name = "disable_schedule_extension_for_down_payment", nullable = false)
|
||||
private boolean disableScheduleExtensionForDownPayment;
|
||||
|
||||
@Column(name = "loan_schedule_type", nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private LoanScheduleType loanScheduleType;
|
||||
|
||||
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
|
||||
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
|
||||
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
|
||||
@@ -151,7 +156,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
|
||||
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
|
||||
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
||||
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment) {
|
||||
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment,
|
||||
final LoanScheduleType loanScheduleType) {
|
||||
|
||||
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
|
||||
@@ -159,7 +165,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
|
||||
disableScheduleExtensionForDownPayment);
|
||||
disableScheduleExtensionForDownPayment, loanScheduleType);
|
||||
}
|
||||
|
||||
protected LoanProductRelatedDetail() {
|
||||
@@ -176,7 +182,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
|
||||
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
|
||||
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
||||
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment) {
|
||||
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment,
|
||||
final LoanScheduleType loanScheduleType) {
|
||||
this.currency = currency;
|
||||
this.principal = defaultPrincipal;
|
||||
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
|
||||
@@ -207,6 +214,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
|
||||
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
|
||||
this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment;
|
||||
this.loanScheduleType = loanScheduleType;
|
||||
}
|
||||
|
||||
private Integer defaultToNullIfZero(final Integer value) {
|
||||
@@ -729,4 +737,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment;
|
||||
}
|
||||
|
||||
public LoanScheduleType getLoanScheduleType() {
|
||||
return loanScheduleType;
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -32,4 +32,5 @@
|
||||
<include relativeToChangelogFile="true" file="parts/1007_add_loan_product_schedule_extension_for_down_payment_configuration.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1008_add_loan_installment_delinquency_tag.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1009_refactor_loan_Installment_delinquency_tag.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1010_introduce_loan_schedule_type_configuration.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_product_loan">
|
||||
<column defaultValue="CUMULATIVE" type="varchar(20)" name="loan_schedule_type">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<addColumn tableName="m_loan">
|
||||
<column defaultValue="CUMULATIVE" type="varchar(20)" name="loan_schedule_type">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+4
-2
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -31,6 +32,7 @@ import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
@@ -43,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepayme
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -144,8 +147,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
currentInstallment.resetDerivedComponents();
|
||||
currentInstallment.resetChargesFields();
|
||||
currentInstallment.resetBalances();
|
||||
currentInstallment.updateDerivedFields(currency, disbursementDate);
|
||||
}
|
||||
|
||||
|
||||
+156
-341
File diff suppressed because it is too large
Load Diff
+629
@@ -0,0 +1,629 @@
|
||||
/**
|
||||
* 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.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||
|
||||
public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
@Override
|
||||
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
||||
// TODO: handle interest calculation
|
||||
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
|
||||
// generate list of proposed schedule due dates
|
||||
LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
|
||||
LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations()
|
||||
.fetchLoanTermDueDateVariationsData(loanEndDate);
|
||||
if (lastDueDateVariation != null) {
|
||||
loanEndDate = lastDueDateVariation.getDateValue();
|
||||
}
|
||||
loanApplicationTerms.updateLoanEndDate(loanEndDate);
|
||||
|
||||
// determine the total charges due at time of disbursement
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
|
||||
// setup variables for tracking important facts required for loan
|
||||
// schedule generation.
|
||||
|
||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
|
||||
? loanApplicationTerms.getExpectedDisbursementDate()
|
||||
: loanApplicationTerms.getSubmittedOnDate();
|
||||
|
||||
LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms));
|
||||
|
||||
List<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams,
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
|
||||
boolean isFirstRepayment = true;
|
||||
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
/* fetches the first tranche amount and also updates other tranche details to map */
|
||||
BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(),
|
||||
scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation());
|
||||
BigDecimal downPaymentAmt = BigDecimal.ZERO;
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
downPaymentAmt = MathUtil.percentageOf(disburseAmt, loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19);
|
||||
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
|
||||
downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, loanApplicationTerms.getInstallmentAmountInMultiplesOf());
|
||||
}
|
||||
}
|
||||
BigDecimal remainingPrincipalAmt = disburseAmt.subtract(downPaymentAmt);
|
||||
scheduleParams.setPrincipalToBeScheduled(Money.of(currency, remainingPrincipalAmt));
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(remainingPrincipalAmt));
|
||||
scheduleParams.setOutstandingBalance(Money.of(currency, remainingPrincipalAmt));
|
||||
scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, remainingPrincipalAmt));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
}
|
||||
|
||||
// charges which depends on total loan interest will be added to this
|
||||
// set and handled separately after all installments generated
|
||||
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
|
||||
boolean isNextRepaymentAvailable = true;
|
||||
|
||||
while (!scheduleParams.getOutstandingBalance().isZero()) {
|
||||
scheduleParams.setActualRepaymentDate(getScheduledDateGenerator()
|
||||
.generateNextRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment));
|
||||
AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator()
|
||||
.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
|
||||
scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
|
||||
isFirstRepayment = false;
|
||||
LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
|
||||
|
||||
// this block is to generate the schedule till the specified
|
||||
// date(used for calculating preclosure)
|
||||
boolean isCompletePeriod = true;
|
||||
if (scheduleParams.getScheduleTillDate() != null && !scheduledDueDate.isBefore(scheduleParams.getScheduleTillDate())) {
|
||||
if (!scheduledDueDate.isEqual(scheduleParams.getScheduleTillDate())) {
|
||||
isCompletePeriod = false;
|
||||
}
|
||||
scheduledDueDate = scheduleParams.getScheduleTillDate();
|
||||
isNextRepaymentAvailable = false;
|
||||
}
|
||||
|
||||
ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO);
|
||||
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate);
|
||||
}
|
||||
|
||||
if (currentPeriodParams.isSkipCurrentLoop()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 5 determine principal,interest of repayment period
|
||||
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
|
||||
getPaymentPeriodsInOneYearCalculator(), currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(),
|
||||
scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()),
|
||||
scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(),
|
||||
scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(),
|
||||
loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, null, scheduleParams.getCompoundingMap(), null,
|
||||
scheduledDueDate, null);
|
||||
|
||||
// update cumulative fields for principal
|
||||
currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal());
|
||||
updateOutstandingBalance(scheduleParams, currentPeriodParams);
|
||||
|
||||
if (scheduleParams.getOutstandingBalance().isLessThanZero() || !isNextRepaymentAvailable) {
|
||||
currentPeriodParams.plusPrincipalForThisPeriod(scheduleParams.getOutstandingBalance());
|
||||
scheduleParams.setOutstandingBalance(Money.zero(currency));
|
||||
}
|
||||
|
||||
if (!isNextRepaymentAvailable) {
|
||||
scheduleParams.getDisburseDetailMap().clear();
|
||||
}
|
||||
|
||||
// applies charges for the period
|
||||
applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams, mc);
|
||||
|
||||
// sum up real totalInstallmentDue from components
|
||||
final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod();
|
||||
|
||||
// if previous installment is last then add interest to same
|
||||
// installment
|
||||
if (currentPeriodParams.getLastInstallment() != null && currentPeriodParams.getPrincipalForThisPeriod().isZero()) {
|
||||
currentPeriodParams.getLastInstallment().addInterestAmount(currentPeriodParams.getInterestForThisPeriod());
|
||||
continue;
|
||||
}
|
||||
|
||||
// create repayment period from parts
|
||||
LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
|
||||
scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
|
||||
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
|
||||
totalInstallmentDue, !isCompletePeriod);
|
||||
if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) {
|
||||
installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount());
|
||||
}
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
|
||||
if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) {
|
||||
installment.setEMIFixedSpecificToInstallmentTrue();
|
||||
}
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
// Updates principal paid map with efective date for reducing
|
||||
// the amount from outstanding balance(interest calculation)
|
||||
updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams,
|
||||
installment);
|
||||
|
||||
// handle cumulative fields
|
||||
|
||||
scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod());
|
||||
scheduleParams.addTotalRepaymentExpected(totalInstallmentDue);
|
||||
scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod());
|
||||
scheduleParams.setPeriodStartDate(scheduledDueDate);
|
||||
scheduleParams.incrementInstalmentNumber();
|
||||
scheduleParams.incrementPeriodNumber();
|
||||
// if (termVariationParams.isRecalculateAmounts()) {
|
||||
// loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null);
|
||||
// loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null);
|
||||
// adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(),
|
||||
// scheduleParams.getPeriodNumber(), mc);
|
||||
// }
|
||||
}
|
||||
|
||||
// determine fees and penalties for charges which depends on total
|
||||
// loan interest
|
||||
updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc);
|
||||
|
||||
final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
final BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(),
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid,
|
||||
scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(),
|
||||
scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalRepaymentExpected().getAmount(),
|
||||
totalOutstanding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
|
||||
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
LocalDate rescheduleFrom) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Abstract methods
|
||||
public abstract ScheduledDateGenerator getScheduledDateGenerator();
|
||||
|
||||
public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
|
||||
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
|
||||
Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance,
|
||||
LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap<LocalDate, Money> principalVariation,
|
||||
Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
|
||||
Collection<LoanTermVariationsData> termVariations);
|
||||
|
||||
// Private, internal methods
|
||||
private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
|
||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueAtDisbursement()) {
|
||||
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
return chargesDueAtTimeOfDisbursement;
|
||||
}
|
||||
|
||||
/**
|
||||
* this method calculates the principal amount for generating the repayment schedule.
|
||||
*/
|
||||
private Money getPrincipalToBeScheduled(final LoanApplicationTerms loanApplicationTerms) {
|
||||
Money principalToBeScheduled;
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
if (loanApplicationTerms.getTotalDisbursedAmount().isGreaterThanZero()) {
|
||||
principalToBeScheduled = loanApplicationTerms.getTotalMultiDisbursedAmount();
|
||||
} else if (loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) {
|
||||
principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal();
|
||||
} else {
|
||||
principalToBeScheduled = loanApplicationTerms.getPrincipal();
|
||||
}
|
||||
} else {
|
||||
principalToBeScheduled = loanApplicationTerms.getPrincipal();
|
||||
}
|
||||
return principalToBeScheduled;
|
||||
}
|
||||
|
||||
private List<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
List<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||
|
||||
// In case of `disallowExpectedDisbursementDetails = true`, anyway at least 1 disbursement details must exist
|
||||
if (loanApplicationTerms.getDisbursementDatas().isEmpty()) {
|
||||
loanApplicationTerms.getDisbursementDatas()
|
||||
.add(new DisbursementData(1L, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount(), null, null,
|
||||
null, null));
|
||||
}
|
||||
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
|
||||
if (disbursementData.disbursementDate().equals(loanScheduleParams.getPeriodStartDate())) {
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||
disbursementData.disbursementDate(), Money.of(loanScheduleParams.getCurrency(), disbursementData.getPrincipal()),
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
|
||||
loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), disbursementData.getPrincipal());
|
||||
periods.add(downPaymentPeriod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
|
||||
private LoanScheduleModelDownPaymentPeriod createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams scheduleParams, LocalDate date, BigDecimal periodBaseAmount) {
|
||||
BigDecimal downPaymentAmount = MathUtil.percentageOf(periodBaseAmount,
|
||||
loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19);
|
||||
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
|
||||
downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, loanApplicationTerms.getInstallmentAmountInMultiplesOf());
|
||||
}
|
||||
Money downPayment = Money.of(loanApplicationTerms.getCurrency(), downPaymentAmount);
|
||||
LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod
|
||||
.downPayment(scheduleParams.getInstalmentNumber(), date, downPayment, scheduleParams.getOutstandingBalance());
|
||||
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
|
||||
scheduleParams.incrementInstalmentNumber();
|
||||
scheduleParams.addTotalRepaymentExpected(downPayment);
|
||||
|
||||
return installment;
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
||||
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
||||
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
||||
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
||||
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
|
||||
scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod());
|
||||
installments.add(installment);
|
||||
}
|
||||
return installment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method add extra disbursement periods (if applicable) and update the schedule params
|
||||
*/
|
||||
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement,
|
||||
LoanScheduleParams scheduleParams, final Collection<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
|
||||
for (Map.Entry<LocalDate, Money> disburseDetail : scheduleParams.getDisburseDetailMap().entrySet()) {
|
||||
if (disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate())
|
||||
&& !disburseDetail.getKey().isAfter(scheduledDueDate)) {
|
||||
// validation check for amount not exceeds specified max
|
||||
// amount as per the configuration
|
||||
loanApplicationTerms.getMaxOutstandingBalance();
|
||||
if (scheduleParams.getOutstandingBalance().plus(disburseDetail.getValue())
|
||||
.isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) {
|
||||
String errorMsg = "Outstanding balance must not exceed the amount: " + loanApplicationTerms.getMaxOutstandingBalance();
|
||||
throw new MultiDisbursementOutstandingAmoutException(errorMsg,
|
||||
loanApplicationTerms.getMaxOutstandingBalance().getAmount(), disburseDetail.getValue());
|
||||
}
|
||||
|
||||
// creates and add disbursement detail to the repayments
|
||||
// period
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
BigDecimal downPaymentAmt = BigDecimal.ZERO;
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
|
||||
scheduleParams, disburseDetail.getKey(), disburseDetail.getValue().getAmount());
|
||||
periods.add(downPaymentPeriod);
|
||||
downPaymentAmt = downPaymentPeriod.principalDue();
|
||||
}
|
||||
// updates actual outstanding balance with new
|
||||
// disbursement detail
|
||||
Money remainingPrincipal = disburseDetail.getValue().minus(downPaymentAmt);
|
||||
scheduleParams.addOutstandingBalance(remainingPrincipal);
|
||||
scheduleParams.addOutstandingBalanceAsPerRest(remainingPrincipal);
|
||||
scheduleParams.addPrincipalToBeScheduled(remainingPrincipal);
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(remainingPrincipal));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
|
||||
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
|
||||
final MathContext mc) {
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
currentPeriodParams.getInterestForThisPeriod(), null);
|
||||
currentPeriodParams.setFeeChargesForInstallment(cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(), scheduledDueDate,
|
||||
loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
|
||||
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
|
||||
currentPeriodParams.setPenaltyChargesForInstallment(cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(),
|
||||
scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
|
||||
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
|
||||
scheduleParams.addTotalFeeChargesCharged(currentPeriodParams.getFeeChargesForInstallment());
|
||||
scheduleParams.addTotalPenaltyChargesCharged(currentPeriodParams.getPenaltyChargesForInstallment());
|
||||
}
|
||||
|
||||
private Money cumulativeFeeChargesDueWithin(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);
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
|
||||
cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
|
||||
totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
|
||||
}
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod,
|
||||
Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable,
|
||||
boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) {
|
||||
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
|
||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
|
||||
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
|
||||
cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
|
||||
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
cumulative = cumulative.plus(loanCharge.chargeAmount());
|
||||
} else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative,
|
||||
loanCharge, mc);
|
||||
} else if (isDue) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
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);
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
|
||||
totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
|
||||
}
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative,
|
||||
final LoanCharge loanCharge, final MathContext mc) {
|
||||
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
|
||||
amount = amount.add(principalInterestForThisPeriod.principal().getAmount())
|
||||
.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
|
||||
amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
} else {
|
||||
amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
|
||||
}
|
||||
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
|
||||
cumulative = cumulative.plus(loanChargeAmt);
|
||||
} else {
|
||||
cumulative = cumulative.plus(loanCharge.amountOrPercentage());
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge, final MathContext mc) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
|
||||
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
|
||||
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
|
||||
amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
|
||||
} else {
|
||||
amount = amount.add(principalDisbursed.getAmount());
|
||||
}
|
||||
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
|
||||
cumulative = cumulative.plus(loanChargeAmt);
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
|
||||
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
|
||||
LoanScheduleModelPeriod installment) {
|
||||
LocalDate amountApplicableDate = installment.periodDueDate();
|
||||
if (loanApplicationTerms.isInterestRecalculationEnabled()) {
|
||||
amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms,
|
||||
holidayDetailDTO);
|
||||
}
|
||||
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(),
|
||||
currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate);
|
||||
|
||||
// update outstanding balance for interest calculation
|
||||
updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate);
|
||||
}
|
||||
|
||||
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
|
||||
final HolidayDetailDTO holidayDetailDTO) {
|
||||
LocalDate nextScheduleDate;
|
||||
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
|
||||
nextScheduleDate = getScheduledDateGenerator().generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate,
|
||||
loanApplicationTerms, holidayDetailDTO);
|
||||
} else {
|
||||
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
|
||||
nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
|
||||
}
|
||||
|
||||
return nextScheduleDate;
|
||||
}
|
||||
|
||||
private void updateMapWithAmount(final Map<LocalDate, Money> map, final Money amount, final LocalDate amountApplicableDate) {
|
||||
Money principalPaid = amount;
|
||||
if (map.containsKey(amountApplicableDate)) {
|
||||
principalPaid = map.get(amountApplicableDate).plus(principalPaid);
|
||||
}
|
||||
map.put(amountApplicableDate, principalPaid);
|
||||
|
||||
}
|
||||
|
||||
private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
|
||||
scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getPrincipalPortionMap(),
|
||||
scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies all the past date principal changes and apply them on outstanding balance for future calculations
|
||||
*/
|
||||
private Money updateBalanceForInterestCalculation(final Map<LocalDate, Money> principalPortionMap, final LocalDate scheduledDueDate,
|
||||
final Money outstandingBalanceAsPerRest, boolean addMapDetails) {
|
||||
List<LocalDate> removeFromPrincipalPortionMap = new ArrayList<>();
|
||||
Money outstandingBalance = outstandingBalanceAsPerRest;
|
||||
for (Map.Entry<LocalDate, Money> principal : principalPortionMap.entrySet()) {
|
||||
if (!principal.getKey().isAfter(scheduledDueDate)) {
|
||||
if (addMapDetails) {
|
||||
outstandingBalance = outstandingBalance.plus(principal.getValue());
|
||||
} else {
|
||||
outstandingBalance = outstandingBalance.minus(principal.getValue());
|
||||
}
|
||||
removeFromPrincipalPortionMap.add(principal.getKey());
|
||||
}
|
||||
}
|
||||
for (LocalDate date : removeFromPrincipalPortionMap) {
|
||||
principalPortionMap.remove(date);
|
||||
}
|
||||
return outstandingBalance;
|
||||
}
|
||||
|
||||
private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams,
|
||||
final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
|
||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
|
||||
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
|
||||
Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
|
||||
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
|
||||
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
|
||||
!loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
|
||||
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
|
||||
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
|
||||
!loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
|
||||
scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
|
||||
scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
|
||||
scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment));
|
||||
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<LoanCharge> separateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
|
||||
Set<LoanCharge> interestCharges = new HashSet<>();
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest()
|
||||
|| loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) {
|
||||
interestCharges.add(loanCharge);
|
||||
}
|
||||
}
|
||||
loanCharges.removeAll(interestCharges);
|
||||
return interestCharges;
|
||||
}
|
||||
|
||||
private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate,
|
||||
final Map<LocalDate, Money> disburseDetails, final boolean excludePastUnDisbursed) {
|
||||
|
||||
// this method relates to multi-disbursement loans
|
||||
BigDecimal principal = BigDecimal.ZERO;
|
||||
if (loanApplicationTerms.getDisbursementDatas().size() == 0) {
|
||||
// non tranche loans have no disbursement data entries in submitted and approved status
|
||||
// the appropriate approved amount or applied for amount is used to show a proposed schedule
|
||||
if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
principal = loanApplicationTerms.getApprovedPrincipal().getAmount();
|
||||
} else {
|
||||
principal = loanApplicationTerms.getPrincipal().getAmount();
|
||||
}
|
||||
} else {
|
||||
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
|
||||
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
|
||||
if (disbursementData.disbursementDate().equals(disbursementDate)) {
|
||||
principal = principal.add(disbursementData.getPrincipal());
|
||||
} else if (!excludePastUnDisbursed || disbursementData.isDisbursed()
|
||||
|| !disbursementData.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) {
|
||||
/*
|
||||
* JW: sums up amounts by disbursal date in case of side-effect issues. Original assumed that there
|
||||
* were no duplicate disbursal dates and 'put' each amount into the map keyed by date
|
||||
*/
|
||||
Money previousSum = disburseDetails.get(disbursementData.disbursementDate());
|
||||
BigDecimal sumToNow = BigDecimal.ZERO;
|
||||
if (previousSum != null) {
|
||||
sumToNow = previousSum.getAmount();
|
||||
}
|
||||
sumToNow = sumToNow.add(disbursementData.getPrincipal());
|
||||
disburseDetails.put(disbursementData.disbursementDate(), Money.of(currency, sumToNow));
|
||||
}
|
||||
}
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
|
||||
private void updateOutstandingBalance(LoanScheduleParams scheduleParams, ScheduleCurrentPeriodParams currentPeriodParams) {
|
||||
// update outstandingLoanBlance using current period
|
||||
// 'principalDue'
|
||||
scheduleParams
|
||||
.reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()));
|
||||
}
|
||||
}
|
||||
+19
-2
@@ -26,10 +26,12 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -54,11 +56,26 @@ import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
* minus <b>interest due</b>.
|
||||
* </p>
|
||||
*/
|
||||
public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator {
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||
|
||||
@Override
|
||||
public ScheduledDateGenerator getScheduledDateGenerator() {
|
||||
return scheduledDateGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() {
|
||||
return paymentPeriodsInOneYearCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
|
||||
final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
|
||||
@SuppressWarnings("unused") final Money totalCumulativeInterest,
|
||||
@SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
|
||||
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
|
||||
+20
-2
@@ -18,19 +18,37 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator {
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||
|
||||
@Override
|
||||
public ScheduledDateGenerator getScheduledDateGenerator() {
|
||||
return scheduledDateGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() {
|
||||
return paymentPeriodsInOneYearCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
|
||||
final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
|
||||
Money totalCumulativeInterest, Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
|
||||
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
|
||||
@SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
|
||||
+21
-14
@@ -18,29 +18,36 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGeneratorFactory {
|
||||
|
||||
private final ProgressiveLoanScheduleGenerator progressiveLoanScheduleGenerator;
|
||||
private final CumulativeFlatInterestLoanScheduleGenerator cumulativeFlatInterestLoanScheduleGenerator;
|
||||
private final CumulativeDecliningBalanceInterestLoanScheduleGenerator cumulativeDecliningBalanceInterestLoanScheduleGenerator;
|
||||
|
||||
@Override
|
||||
public LoanScheduleGenerator create(final InterestMethod interestMethod) {
|
||||
public LoanScheduleGenerator create(final LoanScheduleType loanScheduleType, final InterestMethod interestMethod) {
|
||||
return switch (loanScheduleType) {
|
||||
case CUMULATIVE -> cumulativeLoanScheduleGenerator(interestMethod);
|
||||
case PROGRESSIVE -> progressiveLoanScheduleGenerator(interestMethod);
|
||||
};
|
||||
}
|
||||
|
||||
LoanScheduleGenerator loanScheduleGenerator = null;
|
||||
private LoanScheduleGenerator cumulativeLoanScheduleGenerator(final InterestMethod interestMethod) {
|
||||
return switch (interestMethod) {
|
||||
case FLAT -> cumulativeFlatInterestLoanScheduleGenerator;
|
||||
case DECLINING_BALANCE -> cumulativeDecliningBalanceInterestLoanScheduleGenerator;
|
||||
case INVALID -> null;
|
||||
};
|
||||
}
|
||||
|
||||
switch (interestMethod) {
|
||||
case FLAT:
|
||||
loanScheduleGenerator = new FlatInterestLoanScheduleGenerator();
|
||||
break;
|
||||
case DECLINING_BALANCE:
|
||||
loanScheduleGenerator = new DecliningBalanceInterestLoanScheduleGenerator();
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return loanScheduleGenerator;
|
||||
private LoanScheduleGenerator progressiveLoanScheduleGenerator(final InterestMethod interestMethod) {
|
||||
return progressiveLoanScheduleGenerator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
-40
@@ -18,90 +18,83 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsInOneYearCalculator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultPaymentPeriodsInOneYearCalculator.class);
|
||||
|
||||
@Override
|
||||
public Integer calculate(final PeriodFrequencyType repaymentFrequencyType) {
|
||||
|
||||
Integer paymentPeriodsInOneYear = Integer.valueOf(0);
|
||||
Integer paymentPeriodsInOneYear = 0;
|
||||
switch (repaymentFrequencyType) {
|
||||
case DAYS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(365);
|
||||
paymentPeriodsInOneYear = 365;
|
||||
break;
|
||||
case WEEKS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(52);
|
||||
paymentPeriodsInOneYear = 52;
|
||||
break;
|
||||
case MONTHS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(12);
|
||||
paymentPeriodsInOneYear = 12;
|
||||
break;
|
||||
case YEARS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(1);
|
||||
paymentPeriodsInOneYear = 1;
|
||||
break;
|
||||
case INVALID:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(0);
|
||||
paymentPeriodsInOneYear = 0;
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
LOG.error("TODO Implement repaymentFrequencyType for WHOLE_TERM");
|
||||
log.error("TODO Implement repaymentFrequencyType for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
return paymentPeriodsInOneYear;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate,
|
||||
public BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate,
|
||||
final LocalDate scheduledDueDate, final LocalDate interestChargedFromLocalDate,
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer repaidEvery) {
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType, final int repaidEvery, MathContext mc) {
|
||||
|
||||
Double periodFraction = Double.valueOf("0.0");
|
||||
BigDecimal periodFraction = BigDecimal.ZERO;
|
||||
|
||||
final LocalDateInterval repaymentPeriod = new LocalDateInterval(repaymentPeriodStartDate, scheduledDueDate);
|
||||
|
||||
if (interestChargedFromLocalDate != null && repaymentPeriod.fallsBefore(interestChargedFromLocalDate.plusDays(1))) {
|
||||
periodFraction = Double.valueOf("1.0");
|
||||
periodFraction = BigDecimal.ONE;
|
||||
} else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) {
|
||||
|
||||
final int numberOfDaysInterestCalculationGraceInPeriod = Math
|
||||
.toIntExact(ChronoUnit.DAYS.between(repaymentPeriodStartDate, interestChargedFromLocalDate));
|
||||
periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery,
|
||||
numberOfDaysInterestCalculationGraceInPeriod);
|
||||
numberOfDaysInterestCalculationGraceInPeriod, mc);
|
||||
}
|
||||
|
||||
return periodFraction;
|
||||
}
|
||||
|
||||
private double calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer every,
|
||||
final Integer numberOfDaysInterestCalculationGrace) {
|
||||
private BigDecimal calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final int repaidEvery,
|
||||
final int numberOfDaysInterestCalculationGrace, final MathContext mc) {
|
||||
|
||||
Double fraction = Double.valueOf("0");
|
||||
switch (repaymentPeriodFrequencyType) {
|
||||
case DAYS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() * every.doubleValue();
|
||||
break;
|
||||
case WEEKS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("7.0") * every.doubleValue());
|
||||
break;
|
||||
case MONTHS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("30.0") * every.doubleValue());
|
||||
break;
|
||||
case YEARS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("365.0") * every.doubleValue());
|
||||
break;
|
||||
case INVALID:
|
||||
fraction = Double.valueOf("0");
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
LOG.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
return fraction;
|
||||
BigDecimal repayEveryBD = BigDecimal.valueOf(repaidEvery);
|
||||
BigDecimal noDaysInterestCalculationGrace = BigDecimal.valueOf(numberOfDaysInterestCalculationGrace);
|
||||
return switch (repaymentPeriodFrequencyType) {
|
||||
case DAYS -> noDaysInterestCalculationGrace.multiply(repayEveryBD, mc);
|
||||
case WEEKS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(7), mc).multiply(repayEveryBD, mc);
|
||||
case MONTHS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(30), mc).multiply(repayEveryBD, mc);
|
||||
case YEARS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(365), mc).multiply(repayEveryBD, mc);
|
||||
case WHOLE_TERM -> {
|
||||
log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
||||
yield BigDecimal.ZERO;
|
||||
}
|
||||
default -> BigDecimal.ZERO;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
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;
|
||||
@@ -34,13 +35,12 @@ import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultScheduledDateGenerator.class);
|
||||
|
||||
@Override
|
||||
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||
|
||||
@@ -240,7 +240,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
case INVALID:
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
LOG.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM");
|
||||
log.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
return dueRepaymentPeriodDate;
|
||||
@@ -282,7 +282,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
case INVALID:
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
LOG.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM");
|
||||
log.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
return isScheduledDate;
|
||||
@@ -319,7 +319,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
case INVALID:
|
||||
break;
|
||||
case WHOLE_TERM:
|
||||
LOG.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
||||
log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -40,8 +40,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
|
||||
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
|
||||
final int periodNumber = 0;
|
||||
return new LoanScheduleModelDisbursementPeriod(periodNumber, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
return new LoanScheduleModelDisbursementPeriod(null, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
|
||||
public record LoanTermVariationParams(boolean skipPeriod, boolean recalculateAmounts, LocalDate scheduledDueDate,
|
||||
List<LoanTermVariationsData> variationsData) {
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||
|
||||
@Override
|
||||
public ScheduledDateGenerator getScheduledDateGenerator() {
|
||||
return scheduledDateGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() {
|
||||
return paymentPeriodsInOneYearCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
|
||||
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
|
||||
Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance,
|
||||
LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap<LocalDate, Money> principalVariation,
|
||||
Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
|
||||
Collection<LoanTermVariationsData> termVariations) {
|
||||
// TODO: handle interest calculation
|
||||
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
|
||||
periodNumber, mc, Money.zero(loanApplicationTerms.getCurrency()));
|
||||
final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment);
|
||||
// adjust if needed
|
||||
principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
|
||||
totalCumulativePrincipalToDate, periodNumber);
|
||||
|
||||
return new PrincipalInterest(principalForThisInstallment, Money.zero(loanApplicationTerms.getCurrency()),
|
||||
Money.zero(loanApplicationTerms.getCurrency()));
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
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.domain.Money;
|
||||
|
||||
public class ScheduleCurrentPeriodParams {
|
||||
|
||||
Money earlyPaidAmount;
|
||||
LoanScheduleModelPeriod lastInstallment;
|
||||
boolean skipCurrentLoop;
|
||||
Money interestForThisPeriod;
|
||||
Money principalForThisPeriod;
|
||||
Money feeChargesForInstallment;
|
||||
Money penaltyChargesForInstallment;
|
||||
// for adjusting outstandingBalances
|
||||
Money reducedBalance;
|
||||
boolean isEmiAmountChanged;
|
||||
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
|
||||
ScheduleCurrentPeriodParams(final MonetaryCurrency currency) {
|
||||
this(currency, BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
ScheduleCurrentPeriodParams(final MonetaryCurrency currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) {
|
||||
this.earlyPaidAmount = Money.zero(currency);
|
||||
this.lastInstallment = null;
|
||||
this.skipCurrentLoop = false;
|
||||
this.interestForThisPeriod = Money.zero(currency);
|
||||
this.principalForThisPeriod = Money.zero(currency);
|
||||
this.reducedBalance = Money.zero(currency);
|
||||
this.feeChargesForInstallment = Money.zero(currency);
|
||||
this.penaltyChargesForInstallment = Money.zero(currency);
|
||||
this.isEmiAmountChanged = false;
|
||||
this.interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
}
|
||||
|
||||
public Money getEarlyPaidAmount() {
|
||||
return this.earlyPaidAmount;
|
||||
}
|
||||
|
||||
public void plusEarlyPaidAmount(Money earlyPaidAmount) {
|
||||
this.earlyPaidAmount = this.earlyPaidAmount.plus(earlyPaidAmount);
|
||||
}
|
||||
|
||||
public void minusEarlyPaidAmount(Money earlyPaidAmount) {
|
||||
this.earlyPaidAmount = this.earlyPaidAmount.minus(earlyPaidAmount);
|
||||
}
|
||||
|
||||
public LoanScheduleModelPeriod getLastInstallment() {
|
||||
return this.lastInstallment;
|
||||
}
|
||||
|
||||
public void setLastInstallment(LoanScheduleModelPeriod lastInstallment) {
|
||||
this.lastInstallment = lastInstallment;
|
||||
}
|
||||
|
||||
public boolean isSkipCurrentLoop() {
|
||||
return this.skipCurrentLoop;
|
||||
}
|
||||
|
||||
public void setSkipCurrentLoop(boolean skipCurrentLoop) {
|
||||
this.skipCurrentLoop = skipCurrentLoop;
|
||||
}
|
||||
|
||||
public Money getInterestForThisPeriod() {
|
||||
return this.interestForThisPeriod;
|
||||
}
|
||||
|
||||
public void setInterestForThisPeriod(Money interestForThisPeriod) {
|
||||
this.interestForThisPeriod = interestForThisPeriod;
|
||||
}
|
||||
|
||||
public void minusInterestForThisPeriod(Money interestForThisPeriod) {
|
||||
this.interestForThisPeriod = this.interestForThisPeriod.minus(interestForThisPeriod);
|
||||
}
|
||||
|
||||
public Money getPrincipalForThisPeriod() {
|
||||
return this.principalForThisPeriod;
|
||||
}
|
||||
|
||||
public void setPrincipalForThisPeriod(Money principalForThisPeriod) {
|
||||
this.principalForThisPeriod = principalForThisPeriod;
|
||||
}
|
||||
|
||||
public void plusPrincipalForThisPeriod(Money principalForThisPeriod) {
|
||||
this.principalForThisPeriod = this.principalForThisPeriod.plus(principalForThisPeriod);
|
||||
}
|
||||
|
||||
public void minusPrincipalForThisPeriod(Money principalForThisPeriod) {
|
||||
this.principalForThisPeriod = this.principalForThisPeriod.minus(principalForThisPeriod);
|
||||
}
|
||||
|
||||
public Money getReducedBalance() {
|
||||
return this.reducedBalance;
|
||||
}
|
||||
|
||||
public void setReducedBalance(Money reducedBalance) {
|
||||
this.reducedBalance = reducedBalance;
|
||||
}
|
||||
|
||||
public Money getFeeChargesForInstallment() {
|
||||
return this.feeChargesForInstallment;
|
||||
}
|
||||
|
||||
public void setFeeChargesForInstallment(Money feeChargesForInstallment) {
|
||||
this.feeChargesForInstallment = feeChargesForInstallment;
|
||||
}
|
||||
|
||||
public void minusFeeChargesForInstallment(Money feeChargesForInstallment) {
|
||||
this.feeChargesForInstallment = this.feeChargesForInstallment.minus(feeChargesForInstallment);
|
||||
}
|
||||
|
||||
public Money getPenaltyChargesForInstallment() {
|
||||
return this.penaltyChargesForInstallment;
|
||||
}
|
||||
|
||||
public void setPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
|
||||
this.penaltyChargesForInstallment = penaltyChargesForInstallment;
|
||||
}
|
||||
|
||||
public void minusPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
|
||||
this.penaltyChargesForInstallment = this.penaltyChargesForInstallment.minus(penaltyChargesForInstallment);
|
||||
}
|
||||
|
||||
public Money fetchTotalAmountForPeriod() {
|
||||
return this.principalForThisPeriod.plus(interestForThisPeriod).plus(feeChargesForInstallment).plus(penaltyChargesForInstallment);
|
||||
}
|
||||
|
||||
public boolean isEmiAmountChanged() {
|
||||
return this.isEmiAmountChanged;
|
||||
}
|
||||
|
||||
public void setEmiAmountChanged(boolean isEmiAmountChanged) {
|
||||
this.isEmiAmountChanged = isEmiAmountChanged;
|
||||
}
|
||||
|
||||
public BigDecimal getInterestCalculationGraceOnRepaymentPeriodFraction() {
|
||||
return this.interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
}
|
||||
|
||||
}
|
||||
+13
-7
@@ -98,6 +98,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplica
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
@@ -471,6 +472,7 @@ public class LoanScheduleAssembler {
|
||||
.isDisableScheduleExtensionForDownPayment();
|
||||
|
||||
}
|
||||
LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType();
|
||||
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
|
||||
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
|
||||
@@ -485,7 +487,7 @@ public class LoanScheduleAssembler {
|
||||
allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI,
|
||||
fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled,
|
||||
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
|
||||
isScheduleExtensionForDownPaymentDisabled);
|
||||
isScheduleExtensionForDownPaymentDisabled, loanScheduleType);
|
||||
}
|
||||
|
||||
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
|
||||
@@ -666,20 +668,22 @@ public class LoanScheduleAssembler {
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||
loanApplicationTerms.getInterestMethod());
|
||||
if (loanApplicationTerms.isEqualAmortization()) {
|
||||
if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) {
|
||||
final LoanScheduleGenerator decliningLoanScheduleGenerator = this.loanScheduleFactory
|
||||
.create(InterestMethod.DECLINING_BALANCE);
|
||||
.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE);
|
||||
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
|
||||
|
||||
loanApplicationTerms
|
||||
.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
|
||||
|
||||
}
|
||||
loanScheduleGenerator = this.loanScheduleFactory.create(InterestMethod.FLAT);
|
||||
loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.FLAT);
|
||||
} else {
|
||||
loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||
loanApplicationTerms.getInterestMethod());
|
||||
}
|
||||
|
||||
return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
|
||||
@@ -696,7 +700,8 @@ public class LoanScheduleAssembler {
|
||||
loanApplicationTerms.getExpectedDisbursementDate(), HolidayStatusType.ACTIVE.getValue());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||
loanApplicationTerms.getInterestMethod());
|
||||
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, detailDTO,
|
||||
loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel();
|
||||
@@ -705,7 +710,8 @@ public class LoanScheduleAssembler {
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||
LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||
loanApplicationTerms.getInterestMethod());
|
||||
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
|
||||
|
||||
+2
-1
@@ -131,7 +131,8 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
|
||||
final MathContext mathContext = new MathContext(8, roundingMode);
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
|
||||
.determineProcessor(loan.transactionProcessingStrategy());
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||
loanApplicationTerms.getInterestMethod());
|
||||
loan.setHelpers(null, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
|
||||
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan,
|
||||
loanApplicationTerms.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
|
||||
|
||||
+2
-1
@@ -436,7 +436,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
||||
final MathContext mathContext = new MathContext(8, roundingMode);
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
|
||||
.determineProcessor(loan.transactionProcessingStrategy());
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||
loanApplicationTerms.getInterestMethod());
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine = null;
|
||||
loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
|
||||
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan,
|
||||
|
||||
+3
-2
@@ -31,6 +31,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
@@ -103,8 +104,8 @@ public class LoanAccountAutoStarter {
|
||||
|
||||
@Bean
|
||||
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
||||
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor() {
|
||||
return new AdvancedPaymentScheduleTransactionProcessor();
|
||||
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(LoanUtilService loanUtilService) {
|
||||
return new AdvancedPaymentScheduleTransactionProcessor(loanUtilService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -46,6 +46,7 @@ import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||
@@ -163,7 +164,8 @@ public final class LoanProductDataValidator {
|
||||
LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT,
|
||||
LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
|
||||
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.REPAYMENT_START_DATE_TYPE,
|
||||
LoanProductConstants.DISABLE_SCHEDULE_EXTENSION_FOR_DOWN_PAYMENT, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY));
|
||||
LoanProductConstants.DISABLE_SCHEDULE_EXTENSION_FOR_DOWN_PAYMENT, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
|
||||
LoanProductConstants.LOAN_SCHEDULE_TYPE));
|
||||
|
||||
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
|
||||
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
|
||||
@@ -771,6 +773,10 @@ public final class LoanProductDataValidator {
|
||||
.value(enableInstallmentLevelDelinquency).ignoreIfNull().validateForBooleanValue();
|
||||
}
|
||||
|
||||
final String loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType).ignoreIfNull()
|
||||
.isOneOfEnumValues(LoanScheduleType.class);
|
||||
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -28,6 +28,7 @@ import java.util.Map;
|
||||
import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.accounting.Account;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
|
||||
public class LoanProductTestBuilder {
|
||||
|
||||
@@ -151,6 +152,7 @@ public class LoanProductTestBuilder {
|
||||
private boolean enableAutoRepaymentForDownPayment = false;
|
||||
private Integer repaymentStartDateType = null;
|
||||
private boolean disableScheduleExtensionForDownPayment = false;
|
||||
private LoanScheduleType loanScheduleType = LoanScheduleType.CUMULATIVE;
|
||||
|
||||
public String build() {
|
||||
final HashMap<String, Object> map = build(null, null);
|
||||
@@ -307,6 +309,7 @@ public class LoanProductTestBuilder {
|
||||
if (disableScheduleExtensionForDownPayment) {
|
||||
map.put("disableScheduleExtensionForDownPayment", disableScheduleExtensionForDownPayment);
|
||||
}
|
||||
map.put("loanScheduleType", loanScheduleType);
|
||||
|
||||
return map;
|
||||
}
|
||||
@@ -747,4 +750,8 @@ public class LoanProductTestBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoanProductTestBuilder withLoanScheduleType(LoanScheduleType loanScheduleType) {
|
||||
this.loanScheduleType = loanScheduleType;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user