FINERACT-1981: Progressive loan schedule handling

This commit is contained in:
Adam Saghy
2023-10-31 08:15:50 +01:00
committed by Arnold Gálovics
parent 71d2e51051
commit 2ea23be9a4
33 changed files with 1326 additions and 480 deletions
@@ -1289,7 +1289,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
updateLoanScheduleDependentDerivedFields();
updateLoanSummaryDerivedFields();
applyAccurals();
}
public void updateLoanSchedule(final Collection<LoanRepaymentScheduleInstallment> installments) {
@@ -2530,9 +2529,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
if ((interestRecalculationEnabledParam && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} else if (downPaymentEnabled) {
LoanScheduleDTO loanSchedule = getRecalculatedSchedule(scheduleGeneratorDTO);
updateLoanSchedule(loanSchedule.getInstallments());
}
}
@@ -2827,11 +2823,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final MathContext mc = new MathContext(8, roundingMode);
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
LoanScheduleGenerator loanScheduleGenerator = null;
LoanScheduleGenerator loanScheduleGenerator;
if (loanApplicationTerms.isEqualAmortization()) {
if (loanApplicationTerms.getInterestMethod().isDecliningBalance()) {
final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
.create(InterestMethod.DECLINING_BALANCE);
.create(loanApplicationTerms.getLoanScheduleType(), InterestMethod.DECLINING_BALANCE);
Set<LoanCharge> loanCharges = getActiveCharges();
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges,
scheduleGeneratorDTO.getHolidayDetailDTO());
@@ -2840,9 +2836,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
}
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(InterestMethod.FLAT);
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(),
InterestMethod.FLAT);
} else {
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getInterestMethod());
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getLoanScheduleType(),
loanApplicationTerms.getInterestMethod());
}
return loanScheduleGenerator.generate(mc, loanApplicationTerms, getActiveCharges(), scheduleGeneratorDTO.getHolidayDetailDTO());
@@ -3401,8 +3399,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
List<LoanTransaction> trans = getLoanTransactions();
for (final LoanTransaction transaction : trans) {
if (transaction.isNotReversed()
&& (transaction.isChargeOff() || !(transaction.isDisbursement() || transaction.isNonMonetaryTransaction()))) {
if (transaction.isNotReversed() && (transaction.isChargeOff() || !transaction.isNonMonetaryTransaction())) {
repaymentsOrWaivers.add(transaction);
}
}
@@ -3413,9 +3410,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
public List<LoanTransaction> retrieveListOfTransactionsPostDisbursementExcludeAccruals() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isNotReversed()
&& !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement()
|| transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) {
if (transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isRepaymentAtDisbursement()
|| transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) {
repaymentsOrWaivers.add(transaction);
}
}
@@ -5754,7 +5750,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return null;
}
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory()
.create(this.loanRepaymentScheduleDetail.getLoanScheduleType(), interestMethod);
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(19, roundingMode);
@@ -5778,7 +5775,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory()
.create(loanApplicationTerms.getLoanScheduleType(), interestMethod);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode);
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, this,
@@ -419,13 +419,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
this.penaltyAccrued = null;
}
public void resetChargesFields() {
public void resetChargesCharged() {
this.feeChargesCharged = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.penaltyCharges = null;
this.penaltyChargesWaived = null;
this.penaltyChargesWrittenOff = null;
}
public Money payPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
@@ -656,6 +652,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.plus(this.penaltyCharges).getAmount());
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.plus(this.penaltyChargesWaived).getAmount());
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.plus(this.penaltyChargesWrittenOff).getAmount());
checkIfRepaymentPeriodObligationsAreMet(getObligationsMetOnDate(), feeChargesDue.getCurrency());
}
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
@@ -961,4 +958,13 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
return isDownPayment;
}
public void resetBalances() {
resetDerivedComponents();
resetPrincipalDue();
resetChargesCharged();
}
public void resetPrincipalDue() {
this.principal = null;
}
}
@@ -218,6 +218,7 @@ public final class LoanApplicationTerms {
private LocalDate submittedOnDate;
private boolean isScheduleExtensionForDownPaymentDisabled;
private Money disbursedPrincipal;
private final LoanScheduleType loanScheduleType;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
@@ -244,7 +245,8 @@ public final class LoanApplicationTerms {
final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans,
final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType,
final LocalDate submittedOnDate, final Boolean isScheduleExtensionForDownPaymentDisabled) {
final LocalDate submittedOnDate, final Boolean isScheduleExtensionForDownPaymentDisabled,
final LoanScheduleType loanScheduleType) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -262,7 +264,8 @@ public final class LoanApplicationTerms {
isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false,
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled);
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled,
loanScheduleType);
}
@@ -318,6 +321,7 @@ public final class LoanApplicationTerms {
isAutoRepaymentForDownPaymentEnabled = loanProductRelatedDetail.isEnableAutoRepaymentForDownPayment();
isScheduleExtensionForDownPaymentDisabled = loanProductRelatedDetail.isDisableScheduleExtensionForDownPayment();
}
LoanScheduleType loanScheduleType = loanProductRelatedDetail.getLoanScheduleType();
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod,
interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate,
@@ -333,7 +337,8 @@ public final class LoanApplicationTerms {
allowCompoundingOnEod, isEqualAmortization, isFirstRepaymentDateAllowedOnHoliday,
isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment,
isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment,
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled);
isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, isScheduleExtensionForDownPaymentDisabled,
loanScheduleType);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -362,7 +367,7 @@ public final class LoanApplicationTerms {
final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled,
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled,
final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate,
final boolean isScheduleExtensionForDownPaymentDisabled) {
final boolean isScheduleExtensionForDownPaymentDisabled, final LoanScheduleType loanScheduleType) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -446,6 +451,7 @@ public final class LoanApplicationTerms {
this.repaymentStartDateType = repaymentStartDateType;
this.submittedOnDate = submittedOnDate;
this.isScheduleExtensionForDownPaymentDisabled = isScheduleExtensionForDownPaymentDisabled;
this.loanScheduleType = loanScheduleType;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -600,7 +606,7 @@ public final class LoanApplicationTerms {
}
public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate,
final LocalDate periodEndDate) {
@@ -653,8 +659,6 @@ public final class LoanApplicationTerms {
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
} else if (isInterestFreeGracePeriod(periodNumber)) {
interestForInstallment = interestForInstallment.zero();
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
interestForInstallment = interestForThisInstallmentAfterGrace;
} else {
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace);
}
@@ -1185,7 +1189,7 @@ public final class LoanApplicationTerms {
}
private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance,
final BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance,
final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) {
Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance, periodStartDate,
@@ -1195,30 +1199,30 @@ public final class LoanApplicationTerms {
interest = interest.zero();
}
Double fraction = interestCalculationGraceOnRepaymentPeriodFraction;
BigDecimal fraction = interestCalculationGraceOnRepaymentPeriodFraction;
if (isInterestFreeGracePeriod(periodNumber)) {
interest = interest.zero();
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0) {
interest = interest.zero();
fraction = fraction - Integer.valueOf(1).doubleValue();
fraction = fraction.subtract(BigDecimal.ONE);
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.25")
&& interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
} else if (interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.valueOf(0.25)) > 0
&& interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ONE) < 0) {
final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
interest = interest.minus(graceOnInterestForRepaymentPeriod);
fraction = Double.valueOf("0");
fraction = BigDecimal.ZERO;
}
}
return interest;
}
private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) {
return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.parseDouble("0.0");
private boolean isInterestFreeGracePeriodFromDate(BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) {
return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction.compareTo(BigDecimal.ZERO) > 0;
}
private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) {
@@ -1291,7 +1295,7 @@ public final class LoanApplicationTerms {
this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(),
this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled,
this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled,
this.isScheduleExtensionForDownPaymentDisabled);
this.isScheduleExtensionForDownPaymentDisabled, this.loanScheduleType);
}
public Integer getLoanTermFrequency() {
@@ -1322,6 +1326,10 @@ public final class LoanApplicationTerms {
this.principal = principal;
}
public void setDisbursedPrincipal(Money disbursedPrincipal) {
this.disbursedPrincipal = disbursedPrincipal;
}
public LocalDate getInterestChargedFromLocalDate() {
return this.interestChargedFromDate;
}
@@ -1781,4 +1789,8 @@ public final class LoanApplicationTerms {
public Integer getInstallmentAmountInMultiplesOf() {
return installmentAmountInMultiplesOf;
}
public LoanScheduleType getLoanScheduleType() {
return loanScheduleType;
}
}
@@ -22,5 +22,5 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
public interface LoanScheduleGeneratorFactory {
LoanScheduleGenerator create(InterestMethod interestMethod);
LoanScheduleGenerator create(LoanScheduleType loanScheduleType, InterestMethod interestMethod);
}
@@ -20,7 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -32,7 +32,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
*/
public final class LoanScheduleModel {
private final Collection<LoanScheduleModelPeriod> periods;
private final List<LoanScheduleModelPeriod> periods;
private final ApplicationCurrency applicationCurrency;
private final int loanTermInDays;
private final Money totalPrincipalDisbursed;
@@ -44,7 +44,7 @@ public final class LoanScheduleModel {
private final BigDecimal totalRepaymentExpected;
private final BigDecimal totalOutstanding;
public static LoanScheduleModel from(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
public static LoanScheduleModel from(final List<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
@@ -54,7 +54,7 @@ public final class LoanScheduleModel {
totalOutstanding);
}
public static LoanScheduleModel withOverdueChargeUpdation(final Collection<LoanScheduleModelPeriod> periods,
public static LoanScheduleModel withOverdueChargeUpdation(final List<LoanScheduleModelPeriod> periods,
final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) {
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
@@ -63,7 +63,7 @@ public final class LoanScheduleModel {
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
}
public static LoanScheduleModel withLoanScheduleModelPeriods(final Collection<LoanScheduleModelPeriod> periods,
public static LoanScheduleModel withLoanScheduleModelPeriods(final List<LoanScheduleModelPeriod> periods,
final LoanScheduleModel loanScheduleModel) {
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
@@ -72,7 +72,7 @@ public final class LoanScheduleModel {
loanScheduleModel.totalPenaltyChargesCharged, loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
}
private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
private LoanScheduleModel(final List<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
@@ -97,7 +97,7 @@ public final class LoanScheduleModel {
final BigDecimal totalCredits = BigDecimal.ZERO;
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<>();
final List<LoanSchedulePeriodData> periodsData = new ArrayList<>();
for (final LoanScheduleModelPeriod modelPeriod : this.periods) {
periodsData.add(modelPeriod.toData());
}
@@ -114,7 +114,7 @@ public final class LoanScheduleModel {
totalPaidInAdvance, totalPaidLate, this.totalOutstanding, totalCredits);
}
public Collection<LoanScheduleModelPeriod> getPeriods() {
public List<LoanScheduleModelPeriod> getPeriods() {
return this.periods;
}
@@ -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
}
@@ -18,6 +18,8 @@
*/
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -25,7 +27,7 @@ public interface PaymentPeriodsInOneYearCalculator {
Integer calculate(PeriodFrequencyType repaymentFrequencyType);
double calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaidEvery);
BigDecimal calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, MathContext mc);
}
@@ -156,4 +156,7 @@ public interface LoanProductConstants {
String ENABLE_INSTALLMENT_LEVEL_DELINQUENCY = "enableInstallmentLevelDelinquency";
// loan schedule type
String LOAN_SCHEDULE_TYPE = "loanScheduleType";
}
@@ -62,6 +62,7 @@ import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductGeneralRuleException;
import org.apache.fineract.portfolio.rate.domain.Rate;
@@ -303,6 +304,14 @@ public class LoanProduct extends AbstractPersistableCustom {
final LocalDate closeDate = command.localDateValueOfParameterNamed("closeDate");
final ExternalId externalId = ExternalIdFactory.produce(command.stringValueOfParameterNamedAllowingNull("externalId"));
final LoanScheduleType loanScheduleType;
if (command.hasParameter("loanScheduleType")) {
loanScheduleType = LoanScheduleType.valueOf(command.stringValueOfParameterNamed("loanScheduleType"));
} else {
// For backward compatibility
loanScheduleType = LoanScheduleType.CUMULATIVE;
}
final boolean useBorrowerCycle = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME);
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations = new HashSet<>();
@@ -427,7 +436,7 @@ public class LoanProduct extends AbstractPersistableCustom {
disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber,
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment,
enableAutoRepaymentForDownPayment, repaymentStartDateType, disableScheduleExtensionForDownPayment,
enableInstallmentLevelDelinquency);
enableInstallmentLevelDelinquency, loanScheduleType);
}
@@ -642,7 +651,8 @@ public class LoanProduct extends AbstractPersistableCustom {
final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent,
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType,
final boolean disableScheduleExtensionForDownPayment, final boolean enableInstallmentLevelDelinquency) {
final boolean disableScheduleExtensionForDownPayment, final boolean enableInstallmentLevelDelinquency,
final LoanScheduleType loanScheduleType) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
@@ -684,7 +694,7 @@ public class LoanProduct extends AbstractPersistableCustom {
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(),
isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment);
enableAutoRepaymentForDownPayment, disableScheduleExtensionForDownPayment, loanScheduleType);
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
@@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
/**
@@ -141,6 +142,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "disable_schedule_extension_for_down_payment", nullable = false)
private boolean disableScheduleExtensionForDownPayment;
@Column(name = "loan_schedule_type", nullable = false)
@Enumerated(EnumType.STRING)
private LoanScheduleType loanScheduleType;
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
@@ -151,7 +156,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment) {
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment,
final LoanScheduleType loanScheduleType) {
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
@@ -159,7 +165,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment,
disableScheduleExtensionForDownPayment);
disableScheduleExtensionForDownPayment, loanScheduleType);
}
protected LoanProductRelatedDetail() {
@@ -176,7 +182,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType,
final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization,
final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment,
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment) {
final boolean enableAutoRepaymentForDownPayment, final boolean disableScheduleExtensionForDownPayment,
final LoanScheduleType loanScheduleType) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -207,6 +214,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment;
this.loanScheduleType = loanScheduleType;
}
private Integer defaultToNullIfZero(final Integer value) {
@@ -729,4 +737,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.disableScheduleExtensionForDownPayment = disableScheduleExtensionForDownPayment;
}
public LoanScheduleType getLoanScheduleType() {
return loanScheduleType;
}
}
@@ -32,4 +32,5 @@
<include relativeToChangelogFile="true" file="parts/1007_add_loan_product_schedule_extension_for_down_payment_configuration.xml"/>
<include relativeToChangelogFile="true" file="parts/1008_add_loan_installment_delinquency_tag.xml"/>
<include relativeToChangelogFile="true" file="parts/1009_refactor_loan_Installment_delinquency_tag.xml"/>
<include relativeToChangelogFile="true" file="parts/1010_introduce_loan_schedule_type_configuration.xml"/>
</databaseChangeLog>
@@ -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>