Merge branch 'FINERACT-467' into develop

This commit is contained in:
Nazeer Hussain Shaik
2017-11-20 15:56:19 +05:30
20 changed files with 330 additions and 90 deletions
@@ -124,4 +124,6 @@ public interface LoanApiConstants {
public static final String topupAmount = "topupAmount";
public static final String datatables = "datatables";
public static final String isEqualAmortizationParam = "isEqualAmortization";
}
@@ -213,6 +213,7 @@ public class LoanAccountData {
private final Integer maximumGap;
private List<DatatableData> datatables = null;
private final Boolean isEqualAmortization;
/**
* Used to produce a {@link LoanAccountData} with only collateral options
@@ -331,7 +332,7 @@ public class LoanAccountData {
final Long closureLoanId = null;
final String closureLoanAccountNo = null;
final BigDecimal topupAmount = null;
final boolean isEqualAmortization = false;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -350,7 +351,7 @@ public class LoanAccountData {
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount);
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
}
@@ -471,7 +472,7 @@ public class LoanAccountData {
final Long closureLoanId = null;
final String closureLoanAccountNo = null;
final BigDecimal topupAmount = null;
final boolean isEqualAmortization = false;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -490,7 +491,7 @@ public class LoanAccountData {
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount);
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
}
@@ -520,7 +521,7 @@ public class LoanAccountData {
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
/**
@@ -642,7 +643,7 @@ public class LoanAccountData {
final Long closureLoanId = null;
final String closureLoanAccountNo = null;
final BigDecimal topupAmount = null;
final boolean isEqualAmortization = false;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal,
@@ -661,7 +662,7 @@ public class LoanAccountData {
maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount);
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
}
@@ -691,7 +692,7 @@ public class LoanAccountData {
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
@@ -821,7 +822,6 @@ public class LoanAccountData {
final Long closureLoanId = null;
final String closureLoanAccountNo = null;
final BigDecimal topupAmount = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(),
product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
@@ -846,7 +846,7 @@ public class LoanAccountData {
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
product.getMaximumGapBetweenInstallments(), subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId,
closureLoanAccountNo, topupAmount);
closureLoanAccountNo, topupAmount, product.isEqualAmortization());
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -906,7 +906,7 @@ public class LoanAccountData {
product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement,
paidInAdvance, acc.interestRatesPeriods, product.isVariableInstallmentsAllowed(),
product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments(), acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization());
}
@@ -938,7 +938,7 @@ public class LoanAccountData {
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement,
final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus,
final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo,
final BigDecimal topupAmount) {
final BigDecimal topupAmount, final boolean isEqualAmortization) {
final LoanScheduleData repaymentSchedule = null;
final Collection<LoanTransactionData> transactions = null;
@@ -994,7 +994,7 @@ public class LoanAccountData {
outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount);
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
}
/*
@@ -1047,7 +1047,7 @@ public class LoanAccountData {
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
@@ -1089,7 +1089,7 @@ public class LoanAccountData {
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -1154,7 +1154,7 @@ public class LoanAccountData {
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
@@ -1188,7 +1188,7 @@ public class LoanAccountData {
acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
@@ -1216,7 +1216,7 @@ public class LoanAccountData {
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
@@ -1245,7 +1245,7 @@ public class LoanAccountData {
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup,
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount);
acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization);
}
private LoanAccountData(final Long id, //
@@ -1295,7 +1295,7 @@ public class LoanAccountData {
final Collection<InterestRatePeriodData> interestRatesPeriods, final Boolean isVariableInstallmentsAllowed,
final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus, final Boolean canUseForTopup,
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final boolean isTopup,
final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount) {
final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization) {
this.id = id;
this.accountNo = accountNo;
@@ -1477,6 +1477,7 @@ public class LoanAccountData {
this.closureLoanId = closureLoanId;
this.closureLoanAccountNo = closureLoanAccountNo;
this.topupAmount = topupAmount;
this.isEqualAmortization = isEqualAmortization;
}
@@ -115,6 +115,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignme
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.apache.fineract.portfolio.loanaccount.exception.UndoLastTrancheDisbursementException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
@@ -2616,10 +2617,24 @@ public class Loan extends AbstractPersistableCustom<Long> {
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(8, roundingMode);
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(scheduleGeneratorDTO);
final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
LoanScheduleGenerator loanScheduleGenerator = null;
if (loanApplicationTerms.isEqualAmortization()) {
if (loanApplicationTerms.getInterestMethod().isDecliningBalnce()) {
final LoanScheduleGenerator decliningLoanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(
InterestMethod.DECLINING_BALANCE);
Set<LoanCharge> loanCharges = charges();
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, scheduleGeneratorDTO.getHolidayDetailDTO());
loanApplicationTerms.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
}
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(InterestMethod.FLAT);
} else {
loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(loanApplicationTerms.getInterestMethod());
}
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
scheduleGeneratorDTO.getHolidayDetailDTO());
return loanSchedule;
@@ -67,7 +67,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final LoanScheduleParams loanScheduleRecalculationDTO = null;
return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleRecalculationDTO);
}
private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams loanScheduleParams) {
@@ -114,10 +114,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// Determine the total interest owed over the full loan for FLAT
// interest method .
if (!scheduleParams.isPartialUpdate()) {
if (!scheduleParams.isPartialUpdate() && !loanApplicationTerms.isEqualAmortization()) {
Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
this.paymentPeriodsInOneYearCalculator, mc);
this.paymentPeriodsInOneYearCalculator, mc);
loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm);
}
boolean isFirstRepayment = true;
@@ -177,6 +179,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
isFirstRepayment = false;
}
while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) {
LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate();
scheduleParams.setActualRepaymentDate(this.scheduledDateGenerator.generateNextRepaymentDate(
@@ -210,7 +210,7 @@ public final class LoanApplicationTerms {
private Money totalInterestAccounted;
private int periodsCompleted = 0;
private int extraPeriods = 0;
private boolean isEqualAmortization;
@@ -234,7 +234,8 @@ public final class LoanApplicationTerms {
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations,
Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final Integer numberOfdays,
boolean isSkipRepaymentOnFirstDayofMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod) {
boolean isSkipRepaymentOnFirstDayofMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod,
final boolean isEqualAmortization) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -249,7 +250,7 @@ public final class LoanApplicationTerms {
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO,
allowCompoundingOnEod);
allowCompoundingOnEod, isEqualAmortization);
}
@@ -317,6 +318,7 @@ public final class LoanApplicationTerms {
final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType();
final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled();
final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false;
final boolean isEqualAmortization = loanProductRelatedDetail.isEqualAmortization();
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod,
interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate,
@@ -328,7 +330,7 @@ public final class LoanApplicationTerms {
recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount,
loanTermVariations, calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays,
isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO, allowCompoundingOnEod);
isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -375,7 +377,7 @@ public final class LoanApplicationTerms {
}
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false;
final boolean isEqualAmortization = loanProductRelatedDetail.isEqualAmortization();
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, null, null, amortizationMethod, interestMethod, interestRatePerPeriod,
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
@@ -387,7 +389,7 @@ public final class LoanApplicationTerms {
compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO,
allowCompoundingOnEod);
allowCompoundingOnEod, isEqualAmortization);
}
@@ -413,7 +415,7 @@ public final class LoanApplicationTerms {
applicationTerms.approvedPrincipal.getAmount(), loanTermVariations, applicationTerms.calendarHistoryDataWrapper,
applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, applicationTerms.numberOfDays,
applicationTerms.isSkipRepaymentOnFirstDayOfMonth, applicationTerms.holidayDetailDTO,
applicationTerms.allowCompoundingOnEod);
applicationTerms.allowCompoundingOnEod, applicationTerms.isEqualAmortization);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -437,7 +439,7 @@ public final class LoanApplicationTerms {
BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations,
final CalendarHistoryDataWrapper calendarHistoryDataWrapper, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled,
final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth, final HolidayDetailDTO holidayDetailDTO,
final boolean allowCompoundingOnEod) {
final boolean allowCompoundingOnEod, final boolean isEqualAmortization) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
@@ -509,6 +511,7 @@ public final class LoanApplicationTerms {
this.totalPrincipalAccountedForInterestCalcualtion = principal.zero();
this.totalInterestAccounted = principal.zero();
this.totalPrincipalAccounted = principal.zero();
this.isEqualAmortization = isEqualAmortization;
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -667,27 +670,36 @@ public final class LoanApplicationTerms {
Money interestForInstallment = this.principal.zero();
Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy();
InterestMethod interestMethod = this.interestMethod;
switch (this.interestMethod) {
if (this.isEqualAmortization() && this.totalInterestDue != null) {
interestMethod = InterestMethod.FLAT;
}
switch (interestMethod) {
case FLAT:
switch (this.amortizationMethod) {
case EQUAL_INSTALLMENTS:
// average out outstanding interest over remaining
// instalments where interest is applicable
interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(calculator, periodNumber,
mc);
break;
case EQUAL_PRINCIPAL:
// interest follows time-value of money and is brought
// forward to next applicable interest payment period
final PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc,
interestBroughtForwardDueToGrace);
interestForInstallment = result.interest();
interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace();
break;
case INVALID:
break;
if (this.isEqualAmortization() && this.totalInterestDue != null && this.interestMethod.isDecliningBalnce()) {
interestForInstallment = flatInterestPerInstallment(mc, this.totalInterestDue);
} else {
switch (this.amortizationMethod) {
case EQUAL_INSTALLMENTS:
// average out outstanding interest over remaining
// instalments where interest is applicable
interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(calculator,
periodNumber, mc);
break;
case EQUAL_PRINCIPAL:
// interest follows time-value of money and is
// brought
// forward to next applicable interest payment
// period
final PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc,
interestBroughtForwardDueToGrace);
interestForInstallment = result.interest();
interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace();
break;
case INVALID:
break;
}
}
break;
case DECLINING_BALANCE:
@@ -868,13 +880,18 @@ public final class LoanApplicationTerms {
private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments) - defaultToZeroIfNull(this.excludePeriodsForCalculation), mc.getRoundingMode());
return flatInterestPerInstallment(mc, totalInterestForLoanTerm);
}
private Money flatInterestPerInstallment(final MathContext mc, final Money totalInterestForLoanTerm) {
Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments)
- defaultToZeroIfNull(this.excludePeriodsForCalculation), mc.getRoundingMode());
if (this.excludePeriodsForCalculation < this.periodsCompleted) {
Money interestLeft = this.totalInterestDue.minus(this.totalInterestAccounted);
interestPerInstallment = interestLeft.dividedBy(Long.valueOf(this.actualNumberOfRepayments)
- defaultToZeroIfNull(this.periodsCompleted), mc.getRoundingMode());
}
return interestPerInstallment;
}
@@ -1320,7 +1337,7 @@ public final class LoanApplicationTerms {
this.interestCalculationPeriodMethod, this.allowPartialPeriodInterestCalcualtion, this.repaymentEvery,
this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.principalGrace, this.recurringMoratoriumOnPrincipalPeriods, this.interestPaymentGrace,
this.interestChargingGrace, this.amortizationMethod, this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing,
this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled);
this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled, this.isEqualAmortization);
}
public Integer getLoanTermFrequency() {
@@ -1746,5 +1763,13 @@ public final class LoanApplicationTerms {
public void setSeedDate(LocalDate seedDate) {
this.seedDate = seedDate;
}
public boolean isEqualAmortization() {
return isEqualAmortization;
}
public void setEqualAmortization(boolean isEqualAmortization) {
this.isEqualAmortization = isEqualAmortization;
}
}
@@ -120,4 +120,11 @@ public final class LoanScheduleModel {
public BigDecimal getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
public BigDecimal getTotalInterestCharged() {
return this.totalInterestCharged;
}
}
@@ -89,6 +89,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.apache.fineract.portfolio.loanaccount.exception.MinDaysBetweenDisbursalAndFirstRepaymentViolationException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
@@ -201,6 +202,11 @@ public class LoanScheduleAssembler {
final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element);
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType);
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam, element);
}
// interest terms
final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
@@ -432,6 +438,8 @@ public class LoanScheduleAssembler {
HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
@@ -442,7 +450,7 @@ public class LoanScheduleAssembler {
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(),
calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled,numberOfDays, isSkipMeetingOnFirstDay, detailDTO,
allowCompoundingOnEod);
allowCompoundingOnEod, isEqualAmortization);
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
@@ -609,13 +617,26 @@ public class LoanScheduleAssembler {
List<LoanDisbursementDetails> disbursementDetails) {
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails);
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(8, roundingMode);
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
if (loanApplicationTerms.isEqualAmortization()) {
if (loanApplicationTerms.getInterestMethod().isDecliningBalnce()) {
final LoanScheduleGenerator decliningLoanScheduleGenerator = this.loanScheduleFactory
.create(InterestMethod.DECLINING_BALANCE);
LoanScheduleModel loanSchedule = decliningLoanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
loanApplicationTerms.updateTotalInterestDue(Money.of(loanApplicationTerms.getCurrency(), loanSchedule.getTotalInterestCharged()));
}
loanScheduleGenerator = this.loanScheduleFactory.create(InterestMethod.FLAT);
} else {
loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
}
return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
}
@@ -69,7 +69,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName,
LoanApiConstants.isFloatingInterestRateParameterName, LoanApiConstants.interestRateDifferentialParameterName,
LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName,
LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables));
LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam));
private final FromJsonHelper fromApiJsonHelper;
@@ -37,7 +37,6 @@ import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterExc
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.client.api.ClientApiConstants;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
@@ -45,6 +44,7 @@ import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
@@ -93,7 +93,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.disbursementDataParameterName,
LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName,
LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName,
LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables));
LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam));
private final FromJsonHelper fromApiJsonHelper;
private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
@@ -119,7 +119,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
final String loanTypeParameterName = "loanType";
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
baseDataValidator.reset().parameter(loanTypeParameterName).value(loanTypeStr).notNull();
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanType = AccountType.fromName(loanTypeStr);
baseDataValidator.reset().parameter(loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 3);
@@ -160,6 +160,15 @@ public final class LoanApplicationCommandFromApiJsonHelper {
}
}
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam, element);
baseDataValidator.reset().parameter(LoanApiConstants.isEqualAmortizationParam).value(isEqualAmortization).ignoreIfNull()
.validateForBooleanValue();
if (isEqualAmortization && loanProduct.isInterestRecalculationEnabled()) { throw new EqualAmortizationUnsupportedFeatureException(
"interest.recalculation", "interest recalculation"); }
}
final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
baseDataValidator.reset().parameter("productId").value(productId).notNull().integerGreaterThanZero();
@@ -235,6 +244,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
.inMinMaxRange(0, 1);
if (loanProduct.isLinkedToFloatingInterestRate()) {
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate",
"floating interest rate"); }
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
baseDataValidator
.reset()
@@ -461,6 +472,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
throw new UnsupportedParameterException(unsupportedParameterList);
}
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("fixed.emi", "fixed emi"); }
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
@@ -531,6 +543,15 @@ public final class LoanApplicationCommandFromApiJsonHelper {
final String accountNo = this.fromApiJsonHelper.extractStringNamed(accountNoParameterName, element);
baseDataValidator.reset().parameter(accountNoParameterName).value(accountNo).notBlank().notExceedingLengthOf(20);
}
boolean isEqualAmortization = existingLoanApplication.getLoanProductRelatedDetail().isEqualAmortization();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.isEqualAmortizationParam, element);
baseDataValidator.reset().parameter(LoanProductConstants.isEqualAmortizationParam).value(isEqualAmortization).ignoreIfNull()
.validateForBooleanValue();
if (isEqualAmortization && loanProduct.isInterestRecalculationEnabled()) { throw new EqualAmortizationUnsupportedFeatureException(
"interest.recalculation", "interest recalculation"); }
}
final String externalIdParameterName = "externalId";
if (this.fromApiJsonHelper.parameterExists(externalIdParameterName, element)) {
@@ -632,6 +653,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
}
if (loanProduct.isLinkedToFloatingInterestRate()) {
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate",
"floating interest rate"); }
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
baseDataValidator
.reset()
@@ -907,6 +930,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
throw new UnsupportedParameterException(unsupportedParameterList);
}
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("fixed.emi", "fixed emi"); }
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
@@ -1076,11 +1100,18 @@ public final class LoanApplicationCommandFromApiJsonHelper {
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.disbursementDataParameterName, element) && expectedDisbursement != null
&& totalPrincipal != null) {
BigDecimal tatalDisbursement = BigDecimal.ZERO;
final JsonArray variationArray = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.disbursementDataParameterName,
element);
List<LocalDate> expectedDisbursementDates = new ArrayList<>();
if (variationArray != null && variationArray.size() > 0) {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
boolean isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam,
element);
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("tranche.disbursal",
"tranche disbursal"); }
}
int i = 0;
do {
final JsonObject jsonObject = variationArray.get(i).getAsJsonObject();
@@ -563,7 +563,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, "
+ " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, "
+ " l.term_frequency as termFrequency, l.term_period_frequency_enum as termPeriodFrequencyType, "
+ " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.interest_calculated_in_period_enum as interestCalculationPeriodType,"
+ " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.is_equal_amortization as isEqualAmortization, l.interest_calculated_in_period_enum as interestCalculationPeriodType,"
+ " l.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion,"
+ " l.loan_status_id as lifeCycleStatusId, l.loan_transaction_strategy_id as transactionStrategyId, "
+ " lps.name as transactionStrategyName, "
@@ -790,7 +790,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final int amortizationTypeInt = JdbcSupport.getInteger(rs, "amortizationType");
final int interestTypeInt = JdbcSupport.getInteger(rs, "interestType");
final int interestCalculationPeriodTypeInt = JdbcSupport.getInteger(rs, "interestCalculationPeriodType");
final boolean isEqualAmortization = rs.getBoolean("isEqualAmortization");
final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeInt);
final EnumOptionData interestType = LoanEnumerations.interestType(interestTypeInt);
final EnumOptionData interestCalculationPeriodType = LoanEnumerations
@@ -965,7 +965,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears,
graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap,
maximumGap, loanSubStatus, canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount);
maximumGap, loanSubStatus, canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization);
}
}
@@ -132,5 +132,7 @@ public interface LoanProductConstants {
public static final String canUseForTopup = "canUseForTopup";
public static final String isEqualAmortizationParam = "isEqualAmortization";
}
@@ -91,7 +91,7 @@ public class LoanProductsApiResource {
"interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions",
"accountingRuleOptions", "accountingMappingOptions", "floatingRateOptions", "isLinkedToFloatingInterestRates",
"floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate",
"maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", LoanProductConstants.canUseForTopup));
"maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam));
private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName", "restrictedProducts",
"allowedProducts", "productOptions"));
@@ -183,6 +183,7 @@ public class LoanProductData {
private final Boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
private LoanProductConfigurableAttributes allowAttributeOverrides;
private final boolean syncExpectedWithDisbursementDate;
private final boolean isEqualAmortization;
/**
* Used when returning lookup information about loan product for dropdowns.
@@ -258,7 +259,7 @@ public class LoanProductData {
final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
final boolean syncExpectedWithDisbursementDate = false;
final boolean canUseForTopup = false;
final boolean isEqualAmortization = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
@@ -273,7 +274,7 @@ public class LoanProductData {
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup);
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
}
@@ -350,6 +351,7 @@ public class LoanProductData {
final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
final boolean syncExpectedWithDisbursementDate = false;
final boolean canUseForTopup = false;
final boolean isEqualAmortization = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -365,7 +367,7 @@ public class LoanProductData {
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup);
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
}
@@ -449,6 +451,7 @@ public class LoanProductData {
final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
final boolean syncExpectedWithDisbursementDate = false;
final boolean canUseForTopup = false;
final boolean isEqualAmortization = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -464,7 +467,7 @@ public class LoanProductData {
installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup);
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
}
@@ -542,6 +545,7 @@ public class LoanProductData {
final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
final boolean syncExpectedWithDisbursementDate = false;
final boolean canUseForTopup = false;
final boolean isEqualAmortization = false;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -557,7 +561,7 @@ public class LoanProductData {
installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup);
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
}
@@ -571,7 +575,7 @@ public class LoanProductData {
productData.penaltyToIncomeAccountMappings = penaltyToGLAccountMappings;
return productData;
}
public LoanProductData(final Long id, final String name, final String shortName, final String description, final CurrencyData currency,
final BigDecimal principal, final BigDecimal minPrincipal, final BigDecimal maxPrincipal, final BigDecimal tolerance,
final Integer numberOfRepayments, final Integer minNumberOfRepayments, final Integer maxNumberOfRepayments,
@@ -596,7 +600,7 @@ public class LoanProductData {
BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate,
boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed,
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup) {
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -700,6 +704,7 @@ public class LoanProductData {
this.preClosureInterestCalculationStrategyOptions = null;
this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate;
this.canUseForTopup = canUseForTopup;
this.isEqualAmortization = isEqualAmortization;
}
@@ -836,6 +841,7 @@ public class LoanProductData {
this.preClosureInterestCalculationStrategyOptions = preCloseInterestCalculationStrategyOptions;
this.syncExpectedWithDisbursementDate = productData.syncExpectedWithDisbursementDate;
this.canUseForTopup = productData.canUseForTopup;
this.isEqualAmortization = productData.isEqualAmortization;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -1196,4 +1202,9 @@ public class LoanProductData {
public BigDecimal getInterestRateDifferential() {
return this.interestRateDifferential;
}
public boolean isEqualAmortization() {
return isEqualAmortization;
}
}
@@ -182,6 +182,9 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
@Column(name = "can_use_for_topup", nullable = false)
private boolean canUseForTopup = false;
@Column(name = "is_equal_amortization", nullable = false)
private boolean isEqualAmortization = false;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
@@ -331,6 +334,9 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
final boolean canUseForTopup = command.parameterExists(LoanProductConstants.canUseForTopup)
? command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.canUseForTopup)
: false;
final boolean isEqualAmortization = command.parameterExists(LoanProductConstants.isEqualAmortizationParam) ? command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isEqualAmortizationParam) : false;
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
@@ -345,7 +351,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
installmentAmountInMultiplesOf, loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate,
interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments,
maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup);
maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
}
@@ -575,7 +581,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate,
Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed,
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup) {
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -607,7 +613,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
considerPartialPeriodInterest, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods,
graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing,
daysInMonthType.getValue(), daysInYearType.getValue(), isInterestRecalculationEnabled);
daysInMonthType.getValue(), daysInYearType.getValue(), isInterestRecalculationEnabled, isEqualAmortization);
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
@@ -652,6 +658,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
this.syncExpectedWithDisbursementDate =
syncExpectedWithDisbursementDate;
this.canUseForTopup = canUseForTopup;
this.isEqualAmortization = isEqualAmortization;
}
public MonetaryCurrency getCurrency() {
@@ -1365,4 +1372,12 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
return this.canUseForTopup;
}
public boolean isEqualAmortization() {
return isEqualAmortization;
}
public void setEqualAmortization(boolean isEqualAmortization) {
this.isEqualAmortization = isEqualAmortization;
}
}
@@ -127,6 +127,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Column(name = "interest_recalculation_enabled")
private boolean isInterestRecalculationEnabled;
@Column(name = "is_equal_amortization", nullable = false)
private boolean isEqualAmortization = false;
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
@@ -135,13 +138,13 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing,
final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) {
final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization) {
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion,
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment,
graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled);
isInterestRecalculationEnabled, isEqualAmortization);
}
protected LoanProductRelatedDetail() {
@@ -155,7 +158,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments,
final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing,
final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) {
final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization) {
this.currency = currency;
this.principal = defaultPrincipal;
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
@@ -181,6 +184,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.daysInMonthType = daysInMonthType;
this.daysInYearType = daysInYearType;
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
this.isEqualAmortization = isEqualAmortization;
}
private Integer defaultToNullIfZero(final Integer value) {
@@ -493,6 +497,12 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, newValue);
this.isInterestRecalculationEnabled = newValue;
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isEqualAmortizationParam, this.isEqualAmortization)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isEqualAmortizationParam);
actualChanges.put(LoanProductConstants.isEqualAmortizationParam, newValue);
this.isEqualAmortization = newValue;
}
validateRepaymentPeriodWithGraceSettings();
@@ -648,5 +658,13 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
public boolean isAllowPartialPeriodInterestCalcualtion() {
return this.allowPartialPeriodInterestCalcualtion;
}
public boolean isEqualAmortization() {
return isEqualAmortization;
}
public void setEqualAmortization(boolean isEqualAmortization) {
this.isEqualAmortization = isEqualAmortization;
}
}
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanproduct.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
@SuppressWarnings("serial")
public class EqualAmortizationUnsupportedFeatureException extends AbstractPlatformDomainRuleException {
public EqualAmortizationUnsupportedFeatureException(String code, String property) {
super("error.msg.equal.amortization.does.not.support." + code, "Equal Amortization does not support " + property, property);
}
}
@@ -45,9 +45,9 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -108,7 +108,7 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationRestFrequencyWeekdayParamName,
LoanProductConstants.recalculationRestFrequencyNthDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName,
LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName,
LoanProductConstants.canUseForTopup));
LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam));
private static final String[] supportedloanConfigurableAttributes = {LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyIdParamName,
@@ -148,6 +148,13 @@ public final class LoanProductDataValidator {
final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element);
baseDataValidator.reset().parameter("fundId").value(fundId).ignoreIfNull().integerGreaterThanZero();
}
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.isEqualAmortizationParam, element);
baseDataValidator.reset().parameter(LoanProductConstants.isEqualAmortizationParam).value(isEqualAmortization).ignoreIfNull()
.validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, element)) {
final Long minimumDaysBetweenDisbursalAndFirstRepayment = this.fromApiJsonHelper.extractLongNamed(
@@ -314,6 +321,8 @@ public final class LoanProductDataValidator {
if (isInterestRecalculationEnabled != null) {
if (isInterestRecalculationEnabled.booleanValue()) {
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("interest.recalculation",
"interest recalculation"); }
validateInterestRecalculationParams(element, baseDataValidator, null);
}
}
@@ -321,7 +330,9 @@ public final class LoanProductDataValidator {
// interest rates
if (this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)
&& this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true) {
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate",
"floating interest rate"); }
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
baseDataValidator
.reset()
.parameter("interestRatePerPeriod")
@@ -641,6 +652,13 @@ public final class LoanProductDataValidator {
private void validateVariableInstallmentSettings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowVariableInstallmentsParamName, element)
&& this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.allowVariableInstallmentsParamName, element)) {
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.isEqualAmortizationParam, element);
}
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("variable.installment",
"variable installment"); }
Long minimumGapBetweenInstallments = null;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGapBetweenInstallments, element)) {
@@ -721,6 +739,13 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LoanProductConstants.multiDisburseLoanParameterName).value(multiDisburseLoan)
.ignoreIfNull().validateForBooleanValue();
}
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.isEqualAmortizationParam, element);
}
if (isEqualAmortization && multiDisburseLoan) { throw new EqualAmortizationUnsupportedFeatureException("tranche.disbursal",
"tranche disbursal"); }
if (multiDisburseLoan) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.outstandingLoanBalanceParameterName, element)) {
@@ -1114,6 +1139,13 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName)
.value(npaChangeConfig).notNull().isOneOfTheseValues(true, false);
}
boolean isEqualAmortization = loanProduct.isEqualAmortization();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.isEqualAmortizationParam, element);
baseDataValidator.reset().parameter(LoanProductConstants.isEqualAmortizationParam).value(isEqualAmortization).ignoreIfNull()
.validateForBooleanValue();
}
// Interest recalculation settings
Boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
@@ -1126,6 +1158,8 @@ public final class LoanProductDataValidator {
if (isInterestRecalculationEnabled != null) {
if (isInterestRecalculationEnabled) {
if (isEqualAmortization) { throw new EqualAmortizationUnsupportedFeatureException("interest.recalculation",
"interest recalculation"); }
validateInterestRecalculationParams(element, baseDataValidator, loanProduct);
}
}
@@ -1136,6 +1170,9 @@ public final class LoanProductDataValidator {
isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element);
}
if (isLinkedToFloatingInterestRates) {
if(isEqualAmortization){
throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate", "floating interest rate");
}
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
baseDataValidator
.reset()
@@ -227,7 +227,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.allow_variabe_installments as isVariableIntallmentsAllowed, "
+ "lvi.minimum_gap as minimumGap, "
+ "lvi.maximum_gap as maximumGap, "
+ "lp.can_use_for_topup as canUseForTopup "
+ "lp.can_use_for_topup as canUseForTopup, lp.is_equal_amortization as isEqualAmortization "
+ " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id "
+ " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id "
@@ -308,7 +308,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final int amortizationTypeId = JdbcSupport.getInteger(rs, "amortizationMethod");
final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeId);
final boolean isEqualAmortization = rs.getBoolean("isEqualAmortization");
final Integer interestRateFrequencyTypeId = JdbcSupport.getInteger(rs, "interestRatePerPeriodFreq");
final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeId);
@@ -464,7 +465,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup);
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization);
}
}
@@ -0,0 +1,21 @@
--
-- 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.
--
ALTER TABLE `m_product_loan` ADD COLUMN `is_equal_amortization` TINYINT(1) NOT NULL DEFAULT '0';
ALTER TABLE `m_loan` ADD COLUMN `is_equal_amortization` TINYINT(1) NOT NULL DEFAULT '0';
@@ -153,10 +153,10 @@ public class LoanProductRelatedDetailTestHelper {
final Integer daysInYearType = DaysInYearType.ACTUAL.getValue();
final boolean isInterestRecalculationEnabled = false;
final boolean considerPartialPeriodInterest = false;
final boolean isEqualAmortization = false;
return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, considerPartialPeriodInterest, repayEvery,
repaymentFrequencyType, defaultNumberOfRepayments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged,
amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, isEqualAmortization);
}
}