Merge branch 'FINERACT-467' into develop
This commit is contained in:
+2
@@ -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";
|
||||
}
|
||||
|
||||
+21
-20
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
+18
-3
@@ -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;
|
||||
|
||||
+6
-3
@@ -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(
|
||||
|
||||
+56
-31
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -120,4 +120,11 @@ public final class LoanScheduleModel {
|
||||
public BigDecimal getTotalPenaltyChargesCharged() {
|
||||
return this.totalPenaltyChargesCharged;
|
||||
}
|
||||
|
||||
|
||||
public BigDecimal getTotalInterestCharged() {
|
||||
return this.totalInterestCharged;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+25
-4
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
+34
-3
@@ -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();
|
||||
|
||||
+3
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -132,5 +132,7 @@ public interface LoanProductConstants {
|
||||
|
||||
public static final String canUseForTopup = "canUseForTopup";
|
||||
|
||||
public static final String isEqualAmortizationParam = "isEqualAmortization";
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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"));
|
||||
|
||||
+18
-7
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+18
-3
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+21
-3
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+40
-3
@@ -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()
|
||||
|
||||
+4
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -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';
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user