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;
|
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;
|
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) {
|
public static double ceiling(final double n, final double s) {
|
||||||
double c;
|
double c;
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -717,15 +717,12 @@ public final class CalendarUtils {
|
|||||||
if (recur == null) {
|
if (recur == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
LocalDate date = startDate;
|
|
||||||
final LocalDate seedDate = calendar.getStartDateLocalDate();
|
final LocalDate seedDate = calendar.getStartDateLocalDate();
|
||||||
/**
|
/**
|
||||||
* if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) { date = date.plusDays(1); }
|
* if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) { date = date.plusDays(1); }
|
||||||
**/
|
**/
|
||||||
|
|
||||||
final LocalDate scheduleDate = getNextRecurringDate(recur, seedDate, date);
|
return getNextRecurringDate(recur, seedDate, startDate);
|
||||||
|
|
||||||
return scheduleDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void validateNthDayOfMonthFrequency(DataValidatorBuilder baseDataValidator, final String repeatsOnNthDayOfMonthParamName,
|
public static void validateNthDayOfMonthFrequency(DataValidatorBuilder baseDataValidator, final String repeatsOnNthDayOfMonthParamName,
|
||||||
|
|||||||
+13
-15
@@ -1289,7 +1289,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
updateLoanScheduleDependentDerivedFields();
|
updateLoanScheduleDependentDerivedFields();
|
||||||
updateLoanSummaryDerivedFields();
|
updateLoanSummaryDerivedFields();
|
||||||
applyAccurals();
|
applyAccurals();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments) {
|
public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments) {
|
||||||
@@ -2530,9 +2529,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||||
if ((interestRecalculationEnabledParam && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) {
|
if ((interestRecalculationEnabledParam && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) {
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
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 MathContext mc = new MathContext(8, roundingMode);
|
||||||
|
|
||||||
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
|
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
|
||||||
LoanScheduleGenerator loanScheduleGenerator = null;
|
LoanScheduleGenerator loanScheduleGenerator;
|
||||||
if (loanApplicationTerms.isEqualAmortization()) {
|
if (loanApplicationTerms.isEqualAmortization()) {
|
||||||
if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) {
|
if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) {
|
||||||
final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
|
final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
|
||||||
.create(InterestMethod.DECLINING_BALANCE);
|
.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE);
|
||||||
Set<LoanCharge> loanCharges = getActiveCharges();
|
Set<LoanCharge> loanCharges = getActiveCharges();
|
||||||
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges,
|
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges,
|
||||||
scheduleGeneratorDTO.getHolidayDetailDTO());
|
scheduleGeneratorDTO.getHolidayDetailDTO());
|
||||||
@@ -2840,9 +2836,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
|
.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
|
||||||
|
|
||||||
}
|
}
|
||||||
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(InterestMethod.FLAT);
|
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(),
|
||||||
|
InterestMethod.FLAT);
|
||||||
} else {
|
} else {
|
||||||
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getInterestMethod());
|
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(),
|
||||||
|
loanApplicationTerms.getInterestMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
return loanScheduleGenerator.generate(mc, loanApplicationTerms, getActiveCharges(), scheduleGeneratorDTO.getHolidayDetailDTO());
|
return loanScheduleGenerator.generate(mc, loanApplicationTerms, getActiveCharges(), scheduleGeneratorDTO.getHolidayDetailDTO());
|
||||||
@@ -3401,8 +3399,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||||
List<LoanTransaction> trans = getLoanTransactions();
|
List<LoanTransaction> trans = getLoanTransactions();
|
||||||
for (final LoanTransaction transaction : trans) {
|
for (final LoanTransaction transaction : trans) {
|
||||||
if (transaction.isNotReversed()
|
if (transaction.isNotReversed() && (transaction.isChargeOff() || !transaction.isNonMonetaryTransaction())) {
|
||||||
&& (transaction.isChargeOff() || !(transaction.isDisbursement() || transaction.isNonMonetaryTransaction()))) {
|
|
||||||
repaymentsOrWaivers.add(transaction);
|
repaymentsOrWaivers.add(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3413,9 +3410,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
public List<LoanTransaction> retrieveListOfTransactionsPostDisbursementExcludeAccruals() {
|
public List<LoanTransaction> retrieveListOfTransactionsPostDisbursementExcludeAccruals() {
|
||||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||||
if (transaction.isNotReversed()
|
if (transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isRepaymentAtDisbursement()
|
||||||
&& !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement()
|
|| transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) {
|
||||||
|| transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) {
|
|
||||||
repaymentsOrWaivers.add(transaction);
|
repaymentsOrWaivers.add(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5754,7 +5750,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
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 RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||||
final MathContext mc = new MathContext(19, roundingMode);
|
final MathContext mc = new MathContext(19, roundingMode);
|
||||||
@@ -5778,7 +5775,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||||
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
|
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
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||||
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
|
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
|
||||||
|
|||||||
+11
-5
@@ -419,13 +419,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
this.penaltyAccrued = null;
|
this.penaltyAccrued = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetChargesFields() {
|
public void resetChargesCharged() {
|
||||||
this.feeChargesCharged = null;
|
this.feeChargesCharged = null;
|
||||||
this.feeChargesWaived = null;
|
|
||||||
this.feeChargesWrittenOff = null;
|
|
||||||
this.penaltyCharges = null;
|
this.penaltyCharges = null;
|
||||||
this.penaltyChargesWaived = null;
|
|
||||||
this.penaltyChargesWrittenOff = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
|
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.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.plus(this.penaltyCharges).getAmount());
|
||||||
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.plus(this.penaltyChargesWaived).getAmount());
|
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.plus(this.penaltyChargesWaived).getAmount());
|
||||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.plus(this.penaltyChargesWrittenOff).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) {
|
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
|
||||||
@@ -961,4 +958,13 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
return isDownPayment;
|
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 LocalDate submittedOnDate;
|
||||||
private boolean isScheduleExtensionForDownPaymentDisabled;
|
private boolean isScheduleExtensionForDownPaymentDisabled;
|
||||||
private Money disbursedPrincipal;
|
private Money disbursedPrincipal;
|
||||||
|
private final LoanScheduleType loanScheduleType;
|
||||||
|
|
||||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
|
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
|
||||||
@@ -244,7 +245,8 @@ public final class LoanApplicationTerms {
|
|||||||
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
|
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
|
||||||
final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
||||||
final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType,
|
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 LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
|
||||||
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
|
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
|
||||||
@@ -262,7 +264,8 @@ public final class LoanApplicationTerms {
|
|||||||
isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false,
|
isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false,
|
||||||
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
|
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
|
||||||
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
|
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
|
||||||
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled);
|
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled,
|
||||||
|
loanScheduleType);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +321,7 @@ public final class LoanApplicationTerms {
|
|||||||
isAutoRepaymentForDownPaymentEnabled = loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment();
|
isAutoRepaymentForDownPaymentEnabled = loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment();
|
||||||
isScheduleExtensionForDownPaymentDisabled = loanProductRelatedDetail.isDisableScheduleExtensionForDownPayment();
|
isScheduleExtensionForDownPaymentDisabled = loanProductRelatedDetail.isDisableScheduleExtensionForDownPayment();
|
||||||
}
|
}
|
||||||
|
LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType();
|
||||||
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||||
repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod,
|
repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod,
|
||||||
interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate,
|
interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate,
|
||||||
@@ -333,7 +337,8 @@ public final class LoanApplicationTerms {
|
|||||||
allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday,
|
allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday,
|
||||||
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
|
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
|
||||||
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment,
|
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment,
|
||||||
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled);
|
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled,
|
||||||
|
loanScheduleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||||
@@ -362,7 +367,7 @@ public final class LoanApplicationTerms {
|
|||||||
final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled,
|
final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled,
|
||||||
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled,
|
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled,
|
||||||
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate,
|
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate,
|
||||||
final boolean isScheduleExtensionForDownPaymentDisabled) {
|
final boolean isScheduleExtensionForDownPaymentDisabled, final LoanScheduleType loanScheduleType) {
|
||||||
|
|
||||||
this.currency = currency;
|
this.currency = currency;
|
||||||
this.loanTermFrequency = loanTermFrequency;
|
this.loanTermFrequency = loanTermFrequency;
|
||||||
@@ -446,6 +451,7 @@ public final class LoanApplicationTerms {
|
|||||||
this.repaymentStartDateType = repaymentStartDateType;
|
this.repaymentStartDateType = repaymentStartDateType;
|
||||||
this.submittedOnDate = submittedOnDate;
|
this.submittedOnDate = submittedOnDate;
|
||||||
this.isScheduleExtensionForDownPaymentDisabled = isScheduleExtensionForDownPaymentDisabled;
|
this.isScheduleExtensionForDownPaymentDisabled = isScheduleExtensionForDownPaymentDisabled;
|
||||||
|
this.loanScheduleType = loanScheduleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
|
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
|
||||||
@@ -600,7 +606,7 @@ public final class LoanApplicationTerms {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
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 Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate,
|
||||||
final LocalDate periodEndDate) {
|
final LocalDate periodEndDate) {
|
||||||
|
|
||||||
@@ -653,8 +659,6 @@ public final class LoanApplicationTerms {
|
|||||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
|
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
|
||||||
} else if (isInterestFreeGracePeriod(periodNumber)) {
|
} else if (isInterestFreeGracePeriod(periodNumber)) {
|
||||||
interestForInstallment = interestForInstallment.zero();
|
interestForInstallment = interestForInstallment.zero();
|
||||||
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
|
|
||||||
interestForInstallment = interestForThisInstallmentAfterGrace;
|
|
||||||
} else {
|
} else {
|
||||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace);
|
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace);
|
||||||
}
|
}
|
||||||
@@ -1185,7 +1189,7 @@ public final class LoanApplicationTerms {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
|
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) {
|
final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) {
|
||||||
|
|
||||||
Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance, periodStartDate,
|
Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance, periodStartDate,
|
||||||
@@ -1195,30 +1199,30 @@ public final class LoanApplicationTerms {
|
|||||||
interest = interest.zero();
|
interest = interest.zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
Double fraction = interestCalculationGraceOnRepaymentPeriodFraction;
|
BigDecimal fraction = interestCalculationGraceOnRepaymentPeriodFraction;
|
||||||
|
|
||||||
if (isInterestFreeGracePeriod(periodNumber)) {
|
if (isInterestFreeGracePeriod(periodNumber)) {
|
||||||
interest = interest.zero();
|
interest = interest.zero();
|
||||||
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
|
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
|
||||||
|
|
||||||
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
|
if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
interest = interest.zero();
|
interest = interest.zero();
|
||||||
fraction = fraction - Integer.valueOf(1).doubleValue();
|
fraction = fraction.subtract(BigDecimal.ONE);
|
||||||
|
|
||||||
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.25")
|
} else if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.valueOf(0.25)) > 0
|
||||||
&& interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
|
&& interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ONE) < 0) {
|
||||||
|
|
||||||
final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
||||||
interest = interest.minus(graceOnInterestForRepaymentPeriod);
|
interest = interest.minus(graceOnInterestForRepaymentPeriod);
|
||||||
fraction = Double.valueOf("0");
|
fraction = BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return interest;
|
return interest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) {
|
private boolean isInterestFreeGracePeriodFromDate(BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) {
|
||||||
return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.0");
|
return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) {
|
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.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(),
|
||||||
this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled,
|
this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled,
|
||||||
this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled,
|
this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled,
|
||||||
this.isScheduleExtensionForDownPaymentDisabled);
|
this.isScheduleExtensionForDownPaymentDisabled, this.loanScheduleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getLoanTermFrequency() {
|
public Integer getLoanTermFrequency() {
|
||||||
@@ -1322,6 +1326,10 @@ public final class LoanApplicationTerms {
|
|||||||
this.principal = principal;
|
this.principal = principal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDisbursedPrincipal(Money disbursedPrincipal) {
|
||||||
|
this.disbursedPrincipal = disbursedPrincipal;
|
||||||
|
}
|
||||||
|
|
||||||
public LocalDate getInterestChargedFromLocalDate() {
|
public LocalDate getInterestChargedFromLocalDate() {
|
||||||
return this.interestChargedFromDate;
|
return this.interestChargedFromDate;
|
||||||
}
|
}
|
||||||
@@ -1781,4 +1789,8 @@ public final class LoanApplicationTerms {
|
|||||||
public Integer getInstallmentAmountInMultiplesOf() {
|
public Integer getInstallmentAmountInMultiplesOf() {
|
||||||
return installmentAmountInMultiplesOf;
|
return installmentAmountInMultiplesOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoanScheduleType getLoanScheduleType() {
|
||||||
|
return loanScheduleType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -22,5 +22,5 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
|||||||
|
|
||||||
public interface LoanScheduleGeneratorFactory {
|
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.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
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.data.CurrencyData;
|
||||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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 {
|
public final class LoanScheduleModel {
|
||||||
|
|
||||||
private final Collection<LoanScheduleModelPeriod> periods;
|
private final List<LoanScheduleModelPeriod> periods;
|
||||||
private final ApplicationCurrency applicationCurrency;
|
private final ApplicationCurrency applicationCurrency;
|
||||||
private final int loanTermInDays;
|
private final int loanTermInDays;
|
||||||
private final Money totalPrincipalDisbursed;
|
private final Money totalPrincipalDisbursed;
|
||||||
@@ -44,7 +44,7 @@ public final class LoanScheduleModel {
|
|||||||
private final BigDecimal totalRepaymentExpected;
|
private final BigDecimal totalRepaymentExpected;
|
||||||
private final BigDecimal totalOutstanding;
|
private final BigDecimal totalOutstanding;
|
||||||
|
|
||||||
public static LoanScheduleModel from(final 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 int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
|
||||||
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
|
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
|
||||||
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
|
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
|
||||||
@@ -54,7 +54,7 @@ public final class LoanScheduleModel {
|
|||||||
totalOutstanding);
|
totalOutstanding);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanScheduleModel withOverdueChargeUpdation(final Collection<LoanScheduleModelPeriod> periods,
|
public static LoanScheduleModel withOverdueChargeUpdation(final List<LoanScheduleModelPeriod> periods,
|
||||||
final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) {
|
final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) {
|
||||||
|
|
||||||
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
|
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
|
||||||
@@ -63,7 +63,7 @@ public final class LoanScheduleModel {
|
|||||||
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
|
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection<LoanScheduleModelPeriod> periods,
|
public static LoanScheduleModel withLoanScheduleModelPeriods(final List<LoanScheduleModelPeriod> periods,
|
||||||
final LoanScheduleModel loanScheduleModel) {
|
final LoanScheduleModel loanScheduleModel) {
|
||||||
|
|
||||||
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
|
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
|
||||||
@@ -72,7 +72,7 @@ public final class LoanScheduleModel {
|
|||||||
loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
|
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 int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
|
||||||
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
|
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
|
||||||
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
|
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
|
||||||
@@ -97,7 +97,7 @@ public final class LoanScheduleModel {
|
|||||||
|
|
||||||
final BigDecimal totalCredits = BigDecimal.ZERO;
|
final BigDecimal totalCredits = BigDecimal.ZERO;
|
||||||
|
|
||||||
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
|
final List<LoanSchedulePeriodData> periodsData = new ArrayList<>();
|
||||||
for (final LoanScheduleModelPeriod modelPeriod : this.periods) {
|
for (final LoanScheduleModelPeriod modelPeriod : this.periods) {
|
||||||
periodsData.add(modelPeriod.toData());
|
periodsData.add(modelPeriod.toData());
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,7 @@ public final class LoanScheduleModel {
|
|||||||
totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits);
|
totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<LoanScheduleModelPeriod> getPeriods() {
|
public List<LoanScheduleModelPeriod> getPeriods() {
|
||||||
return this.periods;
|
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;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ public interface PaymentPeriodsInOneYearCalculator {
|
|||||||
|
|
||||||
Integer calculate(PeriodFrequencyType repaymentFrequencyType);
|
Integer calculate(PeriodFrequencyType repaymentFrequencyType);
|
||||||
|
|
||||||
double calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
|
BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
|
||||||
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaidEvery);
|
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, MathContext mc);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -156,4 +156,7 @@ public interface LoanProductConstants {
|
|||||||
|
|
||||||
String ENABLE_INSTALLMENT_LEVEL_DELINQUENCY = "enableInstallmentLevelDelinquency";
|
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.floatingrates.domain.FloatingRate;
|
||||||
import org.apache.fineract.portfolio.fund.domain.Fund;
|
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.AprCalculator;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
|
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
|
||||||
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
|
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
|
||||||
import org.apache.fineract.portfolio.rate.domain.Rate;
|
import org.apache.fineract.portfolio.rate.domain.Rate;
|
||||||
@@ -303,6 +304,14 @@ public class LoanProduct extends AbstractPersistableCustom {
|
|||||||
final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
|
final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
|
||||||
final ExternalId externalId = ExternalIdFactory.produce(command.stringValueOfParameterNamedAllowingNull("externalId"));
|
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
|
final boolean useBorrowerCycle = command
|
||||||
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME);
|
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME);
|
||||||
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations = new HashSet<>();
|
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations = new HashSet<>();
|
||||||
@@ -427,7 +436,7 @@ public class LoanProduct extends AbstractPersistableCustom {
|
|||||||
disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber,
|
disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber,
|
||||||
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment,
|
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment,
|
||||||
enableAutoRepaymentForDownPayment, repaymentStartDateType, disableScheduleExtensionForDownPayment,
|
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 Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent,
|
||||||
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
||||||
final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType,
|
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.fund = fund;
|
||||||
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
|
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
|
||||||
|
|
||||||
@@ -684,7 +694,7 @@ public class LoanProduct extends AbstractPersistableCustom {
|
|||||||
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||||
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(),
|
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(),
|
||||||
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
|
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
|
||||||
enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment);
|
enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment, loanScheduleType);
|
||||||
|
|
||||||
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
|
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.common.domain.PeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
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.AprCalculator;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
|
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)
|
@Column(name = "disable_schedule_extension_for_down_payment", nullable = false)
|
||||||
private boolean disableScheduleExtensionForDownPayment;
|
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,
|
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
|
||||||
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
|
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
|
||||||
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
|
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
|
||||||
@@ -151,7 +156,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
|||||||
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
|
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
|
||||||
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
|
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
|
||||||
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
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,
|
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||||
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
|
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
|
||||||
@@ -159,7 +165,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
|||||||
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||||
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||||
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
|
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
|
||||||
disableScheduleExtensionForDownPayment);
|
disableScheduleExtensionForDownPayment, loanScheduleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LoanProductRelatedDetail() {
|
protected LoanProductRelatedDetail() {
|
||||||
@@ -176,7 +182,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
|||||||
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
|
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
|
||||||
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
|
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
|
||||||
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
|
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.currency = currency;
|
||||||
this.principal = defaultPrincipal;
|
this.principal = defaultPrincipal;
|
||||||
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
|
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
|
||||||
@@ -207,6 +214,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
|||||||
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
|
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
|
||||||
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
|
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
|
||||||
this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment;
|
this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment;
|
||||||
|
this.loanScheduleType = loanScheduleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer defaultToNullIfZero(final Integer value) {
|
private Integer defaultToNullIfZero(final Integer value) {
|
||||||
@@ -729,4 +737,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
|||||||
this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment;
|
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/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/1008_add_loan_installment_delinquency_tag.xml"/>
|
||||||
<include relativeToChangelogFile="true" file="parts/1009_refactor_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>
|
</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;
|
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -31,6 +32,7 @@ import lombok.Setter;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
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.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||||
@@ -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.SingleLoanChargeRepaymentScheduleProcessingWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
|
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.PaymentAllocationTransactionType;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -144,8 +147,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||||
currentInstallment.resetDerivedComponents();
|
currentInstallment.resetBalances();
|
||||||
currentInstallment.resetChargesFields();
|
|
||||||
currentInstallment.updateDerivedFields(currency, disbursementDate);
|
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.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -54,11 +56,26 @@ import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
|||||||
* minus <b>interest due</b>.
|
* minus <b>interest due</b>.
|
||||||
* </p>
|
* </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
|
@Override
|
||||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
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 totalCumulativeInterest,
|
||||||
@SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
|
@SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
|
||||||
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
|
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;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
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
|
@Override
|
||||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
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,
|
Money totalCumulativeInterest, Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
|
||||||
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
|
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
|
||||||
@SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
|
@SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
|
||||||
+21
-14
@@ -18,29 +18,36 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGeneratorFactory {
|
public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGeneratorFactory {
|
||||||
|
|
||||||
|
private final ProgressiveLoanScheduleGenerator progressiveLoanScheduleGenerator;
|
||||||
|
private final CumulativeFlatInterestLoanScheduleGenerator cumulativeFlatInterestLoanScheduleGenerator;
|
||||||
|
private final CumulativeDecliningBalanceInterestLoanScheduleGenerator cumulativeDecliningBalanceInterestLoanScheduleGenerator;
|
||||||
|
|
||||||
@Override
|
@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) {
|
private LoanScheduleGenerator progressiveLoanScheduleGenerator(final InterestMethod interestMethod) {
|
||||||
case FLAT:
|
return progressiveLoanScheduleGenerator;
|
||||||
loanScheduleGenerator = new FlatInterestLoanScheduleGenerator();
|
|
||||||
break;
|
|
||||||
case DECLINING_BALANCE:
|
|
||||||
loanScheduleGenerator = new DecliningBalanceInterestLoanScheduleGenerator();
|
|
||||||
break;
|
|
||||||
case INVALID:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return loanScheduleGenerator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-40
@@ -18,90 +18,83 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
|
||||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||||
import org.slf4j.Logger;
|
import org.springframework.stereotype.Component;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsInOneYearCalculator {
|
public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsInOneYearCalculator {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultPaymentPeriodsInOneYearCalculator.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer calculate(final PeriodFrequencyType repaymentFrequencyType) {
|
public Integer calculate(final PeriodFrequencyType repaymentFrequencyType) {
|
||||||
|
|
||||||
Integer paymentPeriodsInOneYear = Integer.valueOf(0);
|
Integer paymentPeriodsInOneYear = 0;
|
||||||
switch (repaymentFrequencyType) {
|
switch (repaymentFrequencyType) {
|
||||||
case DAYS:
|
case DAYS:
|
||||||
paymentPeriodsInOneYear = Integer.valueOf(365);
|
paymentPeriodsInOneYear = 365;
|
||||||
break;
|
break;
|
||||||
case WEEKS:
|
case WEEKS:
|
||||||
paymentPeriodsInOneYear = Integer.valueOf(52);
|
paymentPeriodsInOneYear = 52;
|
||||||
break;
|
break;
|
||||||
case MONTHS:
|
case MONTHS:
|
||||||
paymentPeriodsInOneYear = Integer.valueOf(12);
|
paymentPeriodsInOneYear = 12;
|
||||||
break;
|
break;
|
||||||
case YEARS:
|
case YEARS:
|
||||||
paymentPeriodsInOneYear = Integer.valueOf(1);
|
paymentPeriodsInOneYear = 1;
|
||||||
break;
|
break;
|
||||||
case INVALID:
|
case INVALID:
|
||||||
paymentPeriodsInOneYear = Integer.valueOf(0);
|
paymentPeriodsInOneYear = 0;
|
||||||
break;
|
break;
|
||||||
case WHOLE_TERM:
|
case WHOLE_TERM:
|
||||||
LOG.error("TODO Implement repaymentFrequencyType for WHOLE_TERM");
|
log.error("TODO Implement repaymentFrequencyType for WHOLE_TERM");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return paymentPeriodsInOneYear;
|
return paymentPeriodsInOneYear;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate,
|
public BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate,
|
||||||
final LocalDate scheduledDueDate, final LocalDate interestChargedFromLocalDate,
|
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);
|
final LocalDateInterval repaymentPeriod = new LocalDateInterval(repaymentPeriodStartDate, scheduledDueDate);
|
||||||
|
|
||||||
if (interestChargedFromLocalDate != null && repaymentPeriod.fallsBefore(interestChargedFromLocalDate.plusDays(1))) {
|
if (interestChargedFromLocalDate != null && repaymentPeriod.fallsBefore(interestChargedFromLocalDate.plusDays(1))) {
|
||||||
periodFraction = Double.valueOf("1.0");
|
periodFraction = BigDecimal.ONE;
|
||||||
} else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) {
|
} else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) {
|
||||||
|
|
||||||
final int numberOfDaysInterestCalculationGraceInPeriod = Math
|
final int numberOfDaysInterestCalculationGraceInPeriod = Math
|
||||||
.toIntExact(ChronoUnit.DAYS.between(repaymentPeriodStartDate, interestChargedFromLocalDate));
|
.toIntExact(ChronoUnit.DAYS.between(repaymentPeriodStartDate, interestChargedFromLocalDate));
|
||||||
periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery,
|
periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery,
|
||||||
numberOfDaysInterestCalculationGraceInPeriod);
|
numberOfDaysInterestCalculationGraceInPeriod, mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return periodFraction;
|
return periodFraction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer every,
|
private BigDecimal calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final int repaidEvery,
|
||||||
final Integer numberOfDaysInterestCalculationGrace) {
|
final int numberOfDaysInterestCalculationGrace, final MathContext mc) {
|
||||||
|
|
||||||
Double fraction = Double.valueOf("0");
|
BigDecimal repayEveryBD = BigDecimal.valueOf(repaidEvery);
|
||||||
switch (repaymentPeriodFrequencyType) {
|
BigDecimal noDaysInterestCalculationGrace = BigDecimal.valueOf(numberOfDaysInterestCalculationGrace);
|
||||||
case DAYS:
|
return switch (repaymentPeriodFrequencyType) {
|
||||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() * every.doubleValue();
|
case DAYS -> noDaysInterestCalculationGrace.multiply(repayEveryBD, mc);
|
||||||
break;
|
case WEEKS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(7), mc).multiply(repayEveryBD, mc);
|
||||||
case WEEKS:
|
case MONTHS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(30), mc).multiply(repayEveryBD, mc);
|
||||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("7.0") * every.doubleValue());
|
case YEARS -> noDaysInterestCalculationGrace.divide(BigDecimal.valueOf(365), mc).multiply(repayEveryBD, mc);
|
||||||
break;
|
case WHOLE_TERM -> {
|
||||||
case MONTHS:
|
log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
||||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.parseDouble("30.0") * every.doubleValue());
|
yield BigDecimal.ZERO;
|
||||||
break;
|
}
|
||||||
case YEARS:
|
default -> BigDecimal.ZERO;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.fortuna.ical4j.model.Recur;
|
import net.fortuna.ical4j.model.Recur;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||||
@@ -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.common.domain.PeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||||
import org.slf4j.Logger;
|
import org.springframework.stereotype.Component;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultScheduledDateGenerator.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
|||||||
case INVALID:
|
case INVALID:
|
||||||
break;
|
break;
|
||||||
case WHOLE_TERM:
|
case WHOLE_TERM:
|
||||||
LOG.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM");
|
log.error("TODO Implement getRepaymentPeriodDate for WHOLE_TERM");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return dueRepaymentPeriodDate;
|
return dueRepaymentPeriodDate;
|
||||||
@@ -282,7 +282,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
|||||||
case INVALID:
|
case INVALID:
|
||||||
break;
|
break;
|
||||||
case WHOLE_TERM:
|
case WHOLE_TERM:
|
||||||
LOG.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM");
|
log.error("TODO Implement isDateFallsInSchedule for WHOLE_TERM");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return isScheduledDate;
|
return isScheduledDate;
|
||||||
@@ -319,7 +319,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
|||||||
case INVALID:
|
case INVALID:
|
||||||
break;
|
break;
|
||||||
case WHOLE_TERM:
|
case WHOLE_TERM:
|
||||||
LOG.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
log.error("TODO Implement repaymentPeriodFrequencyType for WHOLE_TERM");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -40,8 +40,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
|
|||||||
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
|
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
|
||||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||||
|
|
||||||
final int periodNumber = 0;
|
return new LoanScheduleModelDisbursementPeriod(null, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||||
return new LoanScheduleModelDisbursementPeriod(periodNumber, loanApplicationTerms.getExpectedDisbursementDate(),
|
|
||||||
loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
|
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.LoanScheduleGenerator;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
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.LoanScheduleModel;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator;
|
import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
@@ -471,6 +472,7 @@ public class LoanScheduleAssembler {
|
|||||||
.isDisableScheduleExtensionForDownPayment();
|
.isDisableScheduleExtensionForDownPayment();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType();
|
||||||
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||||
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
|
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
|
||||||
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
|
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
|
||||||
@@ -485,7 +487,7 @@ public class LoanScheduleAssembler {
|
|||||||
allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI,
|
allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI,
|
||||||
fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled,
|
fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled,
|
||||||
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
|
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate,
|
||||||
isScheduleExtensionForDownPaymentDisabled);
|
isScheduleExtensionForDownPaymentDisabled, loanScheduleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
|
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
|
||||||
@@ -666,20 +668,22 @@ public class LoanScheduleAssembler {
|
|||||||
final MathContext mc = new MathContext(8, roundingMode);
|
final MathContext mc = new MathContext(8, roundingMode);
|
||||||
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
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.isEqualAmortization()) {
|
||||||
if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) {
|
if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) {
|
||||||
final LoanScheduleGenerator decliningLoanScheduleGenerator = this.loanScheduleFactory
|
final LoanScheduleGenerator decliningLoanScheduleGenerator = this.loanScheduleFactory
|
||||||
.create(InterestMethod.DECLINING_BALANCE);
|
.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE);
|
||||||
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
|
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
|
||||||
|
|
||||||
loanApplicationTerms
|
loanApplicationTerms
|
||||||
.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
|
.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
|
||||||
|
|
||||||
}
|
}
|
||||||
loanScheduleGenerator = this.loanScheduleFactory.create(InterestMethod.FLAT);
|
loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.FLAT);
|
||||||
} else {
|
} else {
|
||||||
loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getLoanScheduleType(),
|
||||||
|
loanApplicationTerms.getInterestMethod());
|
||||||
}
|
}
|
||||||
|
|
||||||
return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
|
return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
|
||||||
@@ -696,7 +700,8 @@ public class LoanScheduleAssembler {
|
|||||||
loanApplicationTerms.getExpectedDisbursementDate(), HolidayStatusType.ACTIVE.getValue());
|
loanApplicationTerms.getExpectedDisbursementDate(), HolidayStatusType.ACTIVE.getValue());
|
||||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
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);
|
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
|
||||||
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, detailDTO,
|
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loan, detailDTO,
|
||||||
loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel();
|
loanRepaymentScheduleTransactionProcessor, rescheduleFrom).getLoanScheduleModel();
|
||||||
@@ -705,7 +710,8 @@ public class LoanScheduleAssembler {
|
|||||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||||
LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId,
|
LoanApplicationTerms loanApplicationTerms, Loan loan, final Long officeId,
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
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 RoundingMode roundingMode = MoneyHelper.getRoundingMode();
|
||||||
final MathContext mc = new MathContext(8, roundingMode);
|
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 MathContext mathContext = new MathContext(8, roundingMode);
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
|
||||||
.determineProcessor(loan.transactionProcessingStrategy());
|
.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);
|
loan.setHelpers(null, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
|
||||||
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan,
|
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan,
|
||||||
loanApplicationTerms.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
|
loanApplicationTerms.getHolidayDetailDTO(), loanRepaymentScheduleTransactionProcessor, rescheduleFromDate);
|
||||||
|
|||||||
+2
-1
@@ -436,7 +436,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
final MathContext mathContext = new MathContext(8, roundingMode);
|
final MathContext mathContext = new MathContext(8, roundingMode);
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.loanRepaymentScheduleTransactionProcessorFactory
|
||||||
.determineProcessor(loan.transactionProcessingStrategy());
|
.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;
|
final LoanLifecycleStateMachine loanLifecycleStateMachine = null;
|
||||||
loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
|
loan.setHelpers(loanLifecycleStateMachine, this.loanSummaryWrapper, this.loanRepaymentScheduleTransactionProcessorFactory);
|
||||||
final LoanScheduleDTO loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mathContext, loanApplicationTerms, loan,
|
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.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
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.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
@@ -103,8 +104,8 @@ public class LoanAccountAutoStarter {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
||||||
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor() {
|
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(LoanUtilService loanUtilService) {
|
||||||
return new AdvancedPaymentScheduleTransactionProcessor();
|
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.common.domain.PeriodFrequencyType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
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.LoanProductConstants;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||||
@@ -163,7 +164,8 @@ public final class LoanProductDataValidator {
|
|||||||
LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT,
|
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_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
|
||||||
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.REPAYMENT_START_DATE_TYPE,
|
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,
|
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
|
||||||
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
|
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
|
||||||
@@ -771,6 +773,10 @@ public final class LoanProductDataValidator {
|
|||||||
.value(enableInstallmentLevelDelinquency).ignoreIfNull().validateForBooleanValue();
|
.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);
|
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
@@ -28,6 +28,7 @@ import java.util.Map;
|
|||||||
import org.apache.fineract.client.models.AdvancedPaymentData;
|
import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||||
import org.apache.fineract.integrationtests.common.Utils;
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
import org.apache.fineract.integrationtests.common.accounting.Account;
|
import org.apache.fineract.integrationtests.common.accounting.Account;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
|
|
||||||
public class LoanProductTestBuilder {
|
public class LoanProductTestBuilder {
|
||||||
|
|
||||||
@@ -151,6 +152,7 @@ public class LoanProductTestBuilder {
|
|||||||
private boolean enableAutoRepaymentForDownPayment = false;
|
private boolean enableAutoRepaymentForDownPayment = false;
|
||||||
private Integer repaymentStartDateType = null;
|
private Integer repaymentStartDateType = null;
|
||||||
private boolean disableScheduleExtensionForDownPayment = false;
|
private boolean disableScheduleExtensionForDownPayment = false;
|
||||||
|
private LoanScheduleType loanScheduleType = LoanScheduleType.CUMULATIVE;
|
||||||
|
|
||||||
public String build() {
|
public String build() {
|
||||||
final HashMap<String, Object> map = build(null, null);
|
final HashMap<String, Object> map = build(null, null);
|
||||||
@@ -307,6 +309,7 @@ public class LoanProductTestBuilder {
|
|||||||
if (disableScheduleExtensionForDownPayment) {
|
if (disableScheduleExtensionForDownPayment) {
|
||||||
map.put("disableScheduleExtensionForDownPayment", disableScheduleExtensionForDownPayment);
|
map.put("disableScheduleExtensionForDownPayment", disableScheduleExtensionForDownPayment);
|
||||||
}
|
}
|
||||||
|
map.put("loanScheduleType", loanScheduleType);
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -747,4 +750,8 @@ public class LoanProductTestBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoanProductTestBuilder withLoanScheduleType(LoanScheduleType loanScheduleType) {
|
||||||
|
this.loanScheduleType = loanScheduleType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user