Merge branch 'TestCases' of git://github.com/bharathc27/mifosx into bharathc27-TestCases
Conflicts: api-docs/apiLive.htm mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java
This commit is contained in:
+17
-11
@@ -74,21 +74,27 @@ public class Money implements Comparable<Money> {
|
||||
// round monetary amounts into multiplesof say 20/50.
|
||||
if (inMultiplesOf != null && this.currencyDigitsAfterDecimal == 0 && inMultiplesOf > 0 && amountScaled.doubleValue() > 0) {
|
||||
final double existingVal = amountScaled.doubleValue();
|
||||
final double ceilingOfValue = ceiling(existingVal, inMultiplesOf);
|
||||
final double floorOfValue = floor(existingVal, inMultiplesOf);
|
||||
|
||||
final double floorDiff = existingVal - floorOfValue;
|
||||
final double ceilDiff = ceilingOfValue - existingVal;
|
||||
|
||||
if (ceilDiff > floorDiff) {
|
||||
amountScaled = BigDecimal.valueOf(floorOfValue);
|
||||
} else {
|
||||
amountScaled = BigDecimal.valueOf(ceilingOfValue);
|
||||
}
|
||||
amountScaled = BigDecimal.valueOf(roundToMultiplesOf(existingVal, inMultiplesOf));
|
||||
}
|
||||
this.amount = amountScaled.setScale(this.currencyDigitsAfterDecimal, RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
public static double roundToMultiplesOf(final double existingVal, final Integer inMultiplesOf) {
|
||||
double amountScaled = existingVal;
|
||||
final double ceilingOfValue = ceiling(existingVal, inMultiplesOf);
|
||||
final double floorOfValue = floor(existingVal, inMultiplesOf);
|
||||
|
||||
final double floorDiff = existingVal - floorOfValue;
|
||||
final double ceilDiff = ceilingOfValue - existingVal;
|
||||
|
||||
if (ceilDiff > floorDiff) {
|
||||
amountScaled = floorOfValue;
|
||||
} else {
|
||||
amountScaled = ceilingOfValue;
|
||||
}
|
||||
return amountScaled;
|
||||
}
|
||||
|
||||
public static double ceiling(final double n, final double s) {
|
||||
double c;
|
||||
|
||||
|
||||
+81
-58
@@ -152,6 +152,8 @@ public class LoanAccountData {
|
||||
|
||||
private final Boolean multiDisburseLoan;
|
||||
|
||||
private final Boolean canDefineInstalmentAmount;
|
||||
|
||||
private final BigDecimal fixedEmiAmount;
|
||||
|
||||
private final BigDecimal maxOutstandingLoanBalance;
|
||||
@@ -263,6 +265,7 @@ public class LoanAccountData {
|
||||
final PortfolioAccountData linkedAccount = null;
|
||||
final Collection<DisbursementData> disbursementData = null;
|
||||
final Boolean multiDisburseLoan = null;
|
||||
final Boolean canDefineInstalmentAmount = null;
|
||||
final BigDecimal fixedEmiAmount = null;
|
||||
final BigDecimal maxOutstandingLoanBalance = null;
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations = null;
|
||||
@@ -293,10 +296,10 @@ public class LoanAccountData {
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
|
||||
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement,
|
||||
paidInAdvance);
|
||||
disbursementData, multiDisburseLoan, canDefineInstalmentAmount, fixedEmiAmount, maxOutstandingLoanBalance,
|
||||
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
||||
createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,6 +385,7 @@ public class LoanAccountData {
|
||||
final PortfolioAccountData linkedAccount = null;
|
||||
final Collection<DisbursementData> disbursementData = null;
|
||||
final Boolean multiDisburseLoan = null;
|
||||
final Boolean canDefineInstalmentAmount = null;
|
||||
final BigDecimal fixedEmiAmount = null;
|
||||
final BigDecimal maxOutstandingLoanBalance = null;
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations = null;
|
||||
@@ -412,10 +416,10 @@ public class LoanAccountData {
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
|
||||
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement,
|
||||
paidInAdvance);
|
||||
disbursementData, multiDisburseLoan, canDefineInstalmentAmount, fixedEmiAmount, maxOutstandingLoanBalance,
|
||||
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
|
||||
createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
|
||||
@@ -436,10 +440,11 @@ public class LoanAccountData {
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstalmentAmount, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,6 +531,7 @@ public class LoanAccountData {
|
||||
final PortfolioAccountData linkedAccount = null;
|
||||
final Collection<DisbursementData> disbursementData = null;
|
||||
final Boolean multiDisburseLoan = null;
|
||||
final Boolean canDefineInstalmentAmount = null;
|
||||
final BigDecimal fixedEmiAmount = null;
|
||||
final BigDecimal maxOutstandingBalance = null;
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations = null;
|
||||
@@ -556,9 +562,10 @@ public class LoanAccountData {
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance, emiAmountVariations, memberVariations, product,
|
||||
inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
disbursementData, multiDisburseLoan, canDefineInstalmentAmount, fixedEmiAmount, maxOutstandingBalance, emiAmountVariations,
|
||||
memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement,
|
||||
paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
|
||||
@@ -579,10 +586,11 @@ public class LoanAccountData {
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstalmentAmount, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
|
||||
@@ -715,10 +723,11 @@ public class LoanAccountData {
|
||||
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions,
|
||||
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, product.getMultiDisburseLoan(), fixedEmi, product.getOutstandingLoanBalance(), emiAmountVariations,
|
||||
memberVariations, product, inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA,
|
||||
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
|
||||
product.toLoanInterestRecalculationData(), originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
disbursementData, product.getMultiDisburseLoan(), product.canDefineInstalmentAmount(), fixedEmi,
|
||||
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears,
|
||||
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(),
|
||||
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
|
||||
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
||||
@@ -769,11 +778,11 @@ public class LoanAccountData {
|
||||
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
|
||||
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
|
||||
loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,
|
||||
acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), acc.fixedEmiAmount,
|
||||
product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears,
|
||||
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(),
|
||||
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(),
|
||||
product.canDefineInstalmentAmount(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations,
|
||||
acc.memberVariations, product, acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA,
|
||||
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
|
||||
product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -795,9 +804,9 @@ public class LoanAccountData {
|
||||
final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline,
|
||||
final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged,
|
||||
final Boolean syncDisbursementWithMeeting, final Integer loancounter, final Integer loanProductCounter,
|
||||
final Boolean multiDisburseLoan, final BigDecimal fixedEmiAmont, final BigDecimal outstandingLoanBalance,
|
||||
final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType,
|
||||
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
final Boolean multiDisburseLoan, Boolean canDefineInstalmentAmount, final BigDecimal fixedEmiAmont,
|
||||
final BigDecimal outstandingLoanBalance, final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA,
|
||||
final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement) {
|
||||
|
||||
final LoanScheduleData repaymentSchedule = null;
|
||||
@@ -847,9 +856,10 @@ public class LoanAccountData {
|
||||
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions,
|
||||
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance, emiAmountVariations, memberVariations, product,
|
||||
inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
disbursementData, multiDisburseLoan, canDefineInstalmentAmount, fixedEmiAmont, outstandingLoanBalance, emiAmountVariations,
|
||||
memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement,
|
||||
paidInAdvance);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -889,9 +899,10 @@ public class LoanAccountData {
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, emiAmountVariations,
|
||||
acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType,
|
||||
acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||
disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstalmentAmount, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement, paidInAdvance);
|
||||
}
|
||||
|
||||
@@ -924,10 +935,11 @@ public class LoanAccountData {
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstalmentAmount, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
|
||||
@@ -984,10 +996,11 @@ public class LoanAccountData {
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstalmentAmount, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
|
||||
@@ -1010,10 +1023,11 @@ public class LoanAccountData {
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData,
|
||||
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstalmentAmount, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
|
||||
@@ -1034,10 +1048,11 @@ public class LoanAccountData {
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstalmentAmount, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
|
||||
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance);
|
||||
}
|
||||
|
||||
private LoanAccountData(
|
||||
@@ -1092,11 +1107,11 @@ public class LoanAccountData {
|
||||
final Boolean syncDisbursementWithMeeting, final Integer loanCounter, final Integer loanProductCounter,
|
||||
final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
|
||||
final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
|
||||
final Boolean multiDisburseLoan, BigDecimal fixedEmiAmount, final BigDecimal maxOutstandingLoanBalance,
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations, final Map<Long, LoanBorrowerCycleData> memberVariations,
|
||||
final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing,
|
||||
final Collection<ChargeData> overdueCharges, final Boolean isNPA, final EnumOptionData daysInMonthType,
|
||||
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
final Boolean multiDisburseLoan, final Boolean canDefineInstalmentAmount, BigDecimal fixedEmiAmount,
|
||||
final BigDecimal maxOutstandingLoanBalance, final Collection<LoanTermVariationsData> emiAmountVariations,
|
||||
final Map<Long, LoanBorrowerCycleData> memberVariations, final LoanProductData product, final Boolean inArrears,
|
||||
final Integer graceOnArrearsAgeing, final Collection<ChargeData> overdueCharges, final Boolean isNPA,
|
||||
final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
|
||||
final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance) {
|
||||
this.id = id;
|
||||
@@ -1219,6 +1234,14 @@ public class LoanAccountData {
|
||||
this.accountLinkingOptions = accountLinkingOptions;
|
||||
this.disbursementDetails = disbursementDetails;
|
||||
this.multiDisburseLoan = multiDisburseLoan;
|
||||
|
||||
Boolean canDefineEMIAmount = canDefineInstalmentAmount;
|
||||
if (canDefineInstalmentAmount == null) {
|
||||
canDefineEMIAmount = multiDisburseLoan;
|
||||
} else if (multiDisburseLoan != null) {
|
||||
canDefineEMIAmount = canDefineInstalmentAmount || multiDisburseLoan;
|
||||
}
|
||||
this.canDefineInstalmentAmount = canDefineEMIAmount;
|
||||
this.fixedEmiAmount = fixedEmiAmount;
|
||||
this.maxOutstandingLoanBalance = maxOutstandingLoanBalance;
|
||||
|
||||
|
||||
+22
-12
@@ -24,7 +24,6 @@ import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
@@ -1388,9 +1387,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
if (loanProduct.isMultiDisburseLoan()) {
|
||||
updateDisbursementDetails(command, actualChanges);
|
||||
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) {
|
||||
this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
|
||||
}
|
||||
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
|
||||
this.maxOutstandingLoanBalance)) {
|
||||
this.maxOutstandingLoanBalance = command
|
||||
@@ -1407,6 +1403,15 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
} else {
|
||||
this.disbursementDetails.clear();
|
||||
}
|
||||
|
||||
if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstalmentAmount()) {
|
||||
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount)) {
|
||||
this.fixedEmiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
|
||||
actualChanges.put(LoanApiConstants.emiAmountParameterName, this.fixedEmiAmount);
|
||||
actualChanges.put("recalculateLoanSchedule", true);
|
||||
}
|
||||
} else {
|
||||
this.fixedEmiAmount = null;
|
||||
}
|
||||
|
||||
@@ -2066,10 +2071,15 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
|
||||
final LocalDate actualDisbursementDate, BigDecimal emiAmount, final AppUser currentUser) {
|
||||
boolean isEmiAmountChanged = false;
|
||||
if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
||||
actualDisbursementDate.toDate(), emiAmount, this);
|
||||
this.loanTermVariations.add(loanVariationTerms);
|
||||
if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.canDefineInstalmentAmount()) && emiAmount != null
|
||||
&& emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
|
||||
if (this.loanProduct.isMultiDisburseLoan()) {
|
||||
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
|
||||
actualDisbursementDate.toDate(), emiAmount, this);
|
||||
this.loanTermVariations.add(loanVariationTerms);
|
||||
} else {
|
||||
this.fixedEmiAmount = emiAmount;
|
||||
}
|
||||
isEmiAmountChanged = true;
|
||||
}
|
||||
|
||||
@@ -2306,7 +2316,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(),
|
||||
this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData,
|
||||
this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment());
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstalmentAmountInMultiplesOf());
|
||||
|
||||
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
|
||||
scheduleGeneratorDTO.getHolidayDetailDTO());
|
||||
@@ -4640,7 +4650,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
|
||||
loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails,
|
||||
calendarInstanceForInterestRecalculation, recalculationFrequencyType,
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment());
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstalmentAmountInMultiplesOf());
|
||||
return loanApplicationTerms;
|
||||
}
|
||||
|
||||
@@ -4684,7 +4694,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null,
|
||||
getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount,
|
||||
disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment());
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstalmentAmountInMultiplesOf());
|
||||
|
||||
installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(),
|
||||
LocalDate.now(), getInterestChargedFromDate(), loanApplicationTerms, mc, charges());
|
||||
@@ -4886,7 +4896,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
|
||||
inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData,
|
||||
maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate,
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment());
|
||||
this.loanProduct.getPrincipalThresholdForLastInstalment(), this.loanProduct.getInstalmentAmountInMultiplesOf());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-20
@@ -117,7 +117,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
|
||||
}
|
||||
Money reducePrincipal = totalCumulativePrincipal.zero();
|
||||
Money fixedEmiAmount = totalCumulativePrincipal.zero();
|
||||
int daysCalcForInstallmentNumber = 0;
|
||||
LocalDate scheduleStartDateAsPerFrequency = periodStartDate;
|
||||
while (!outstandingBalance.isZero()) {
|
||||
@@ -191,7 +190,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
BigDecimal disburseAmt = BigDecimal.ZERO;
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate);
|
||||
fixedEmiAmount = fixedEmiAmount.zero().plus(loanApplicationTerms.getFixedEmiAmount());
|
||||
final Collection<DisbursementData> disbursementDatas = new ArrayList<>();
|
||||
LocalDate tillDate = scheduledDueDate;
|
||||
if (prepayDate != null && prepayDate.isBefore(tillDate)) {
|
||||
@@ -233,16 +231,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
if (!loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
loanApplicationTerms.setFixedEmiAmount(null);
|
||||
}
|
||||
fixedEmiAmount = fixedEmiAmount.zero();
|
||||
outstandingBalance = outstandingBalance.minus(reducePrincipal);
|
||||
balanceForcalculation = balanceForcalculation.minus(reducePrincipal);
|
||||
totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal);
|
||||
reducePrincipal = reducePrincipal.zero();
|
||||
break;
|
||||
case REDUCE_NUMBER_OF_INSTALLMENTS:
|
||||
if (fixedEmiAmount.isGreaterThanZero()) {
|
||||
loanApplicationTerms.setFixedEmiAmount(fixedEmiAmount.getAmount());
|
||||
}
|
||||
outstandingBalance = outstandingBalance.minus(reducePrincipal);
|
||||
balanceForcalculation = balanceForcalculation.minus(reducePrincipal);
|
||||
totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal);
|
||||
@@ -385,14 +379,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
// Exclude principal portion for interest only installments
|
||||
if (!recalculatedInterestComponent) {
|
||||
principalForThisPeriod = principalInterestForThisPeriod.principal();
|
||||
if (diffAmt != null && !diffAmt.isEmpty() && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
|
||||
Money principalToBeAdjust = principalForThisPeriod.zero();
|
||||
if (fixedEmiAmount.isZero()) {
|
||||
principalToBeAdjust = principalInterestForThisPeriod.interest().minus(interestForThisinstallment);
|
||||
|
||||
} else {
|
||||
principalToBeAdjust = fixedEmiAmount.minus(principalForThisPeriod).minus(interestForThisinstallment);
|
||||
}
|
||||
if (((diffAmt != null && !diffAmt.isEmpty()) || interestToBeAdded.compareTo(BigDecimal.ZERO) == 1)
|
||||
&& loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) {
|
||||
Money principalToBeAdjust = Money.of(currency, loanApplicationTerms.getFixedEmiAmount()).minus(principalForThisPeriod)
|
||||
.minus(interestForThisinstallment);
|
||||
principalForThisPeriod = principalForThisPeriod.plus(principalToBeAdjust);
|
||||
principalForThisPeriod = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisPeriod,
|
||||
totalCumulativePrincipal.plus(principalForThisPeriod), periodNumber);
|
||||
@@ -407,9 +397,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
}
|
||||
}
|
||||
|
||||
if (fixedEmiAmount.isZero() && !recalculatedInterestComponent) {
|
||||
fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment);
|
||||
}
|
||||
reducePrincipal = reducePrincipal.minus(reducePrincipalForCurrentInstallment);
|
||||
|
||||
if (principalForThisPeriod.isGreaterThan(reducePrincipalForCurrentInstallment)) {
|
||||
@@ -543,6 +530,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
}
|
||||
}
|
||||
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
|
||||
return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
|
||||
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
|
||||
totalOutstanding);
|
||||
@@ -794,12 +783,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
Money principalDue = Money.of(currency, period.principalDue());
|
||||
Money interestDue = Money.of(currency, period.interestDue());
|
||||
|
||||
if(principalDue.isZero() && interestDue.isZero()) {
|
||||
|
||||
if (principalDue.isZero() && interestDue.isZero()) {
|
||||
period.updateFeeChargesDue(Money.zero(currency));
|
||||
period.updatePenaltyChargesDue(Money.zero(currency));
|
||||
}
|
||||
|
||||
|
||||
Money feeChargesDue = Money.of(currency, period.feeChargesDue());
|
||||
Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue());
|
||||
|
||||
|
||||
+32
-23
@@ -112,6 +112,8 @@ public final class LoanApplicationTerms {
|
||||
|
||||
private BigDecimal fixedEmiAmount;
|
||||
|
||||
private final BigDecimal actualFixedEmiAmount;
|
||||
|
||||
private final BigDecimal maxOutstandingBalance;
|
||||
|
||||
private final List<LoanTermVariationsData> emiAmountVariations;
|
||||
@@ -133,6 +135,7 @@ public final class LoanApplicationTerms {
|
||||
private final RecalculationFrequencyType recalculationFrequencyType;
|
||||
|
||||
private final BigDecimal principalThresholdForLastInstalment;
|
||||
private final Integer instalmentAmountInMultiplesOf;
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
|
||||
@@ -147,7 +150,7 @@ public final class LoanApplicationTerms {
|
||||
final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
|
||||
final List<LoanTermVariationsData> emiAmountVariations, final Integer graceOnArrearsAgeing,
|
||||
final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
BigDecimal principalThresholdForLastInstalment) {
|
||||
BigDecimal principalThresholdForLastInstalment, Integer instalmentAmountInMultiplesOf) {
|
||||
|
||||
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
|
||||
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
|
||||
@@ -160,7 +163,7 @@ public final class LoanApplicationTerms {
|
||||
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
|
||||
disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance,
|
||||
recalculationFrequencyType, principalThresholdForLastInstalment);
|
||||
recalculationFrequencyType, principalThresholdForLastInstalment, instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
|
||||
@@ -170,7 +173,7 @@ public final class LoanApplicationTerms {
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount,
|
||||
final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
|
||||
final List<LoanTermVariationsData> emiAmountVariations, final LocalDate interestChargedFromDate,
|
||||
final BigDecimal principalThresholdForLastInstalment) {
|
||||
final BigDecimal principalThresholdForLastInstalment, Integer instalmentAmountInMultiplesOf) {
|
||||
|
||||
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
|
||||
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
|
||||
@@ -206,7 +209,7 @@ public final class LoanApplicationTerms {
|
||||
multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations,
|
||||
loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType,
|
||||
principalThresholdForLastInstalment);
|
||||
principalThresholdForLastInstalment, instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
|
||||
@@ -216,7 +219,8 @@ public final class LoanApplicationTerms {
|
||||
final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
|
||||
final List<LoanTermVariationsData> emiAmountVariations, final LocalDate interestChargedFromDate,
|
||||
final LoanInterestRecalculationDetails interestRecalculationDetails, final CalendarInstance restCalendarInstance,
|
||||
final RecalculationFrequencyType recalculationFrequencyType, final BigDecimal principalThresholdForLastInstalment) {
|
||||
final RecalculationFrequencyType recalculationFrequencyType, final BigDecimal principalThresholdForLastInstalment,
|
||||
Integer instalmentAmountInMultiplesOf) {
|
||||
|
||||
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
|
||||
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
|
||||
@@ -254,7 +258,7 @@ public final class LoanApplicationTerms {
|
||||
disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(),
|
||||
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
|
||||
interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType,
|
||||
principalThresholdForLastInstalment);
|
||||
principalThresholdForLastInstalment, instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||
@@ -272,7 +276,8 @@ public final class LoanApplicationTerms {
|
||||
applicationTerms.graceOnArrearsAgeing, applicationTerms.daysInMonthType, applicationTerms.daysInYearType,
|
||||
applicationTerms.interestRecalculationEnabled, applicationTerms.rescheduleStrategyMethod,
|
||||
applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance,
|
||||
applicationTerms.recalculationFrequencyType, applicationTerms.principalThresholdForLastInstalment);
|
||||
applicationTerms.recalculationFrequencyType, applicationTerms.principalThresholdForLastInstalment,
|
||||
applicationTerms.instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
@@ -290,7 +295,7 @@ public final class LoanApplicationTerms {
|
||||
final boolean isInterestRecalculationEnabled, final LoanRescheduleStrategyMethod rescheduleStrategyMethod,
|
||||
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod,
|
||||
final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType,
|
||||
final BigDecimal principalThresholdForLastInstalment) {
|
||||
final BigDecimal principalThresholdForLastInstalment, final Integer instalmentAmountInMultiplesOf) {
|
||||
this.currency = currency;
|
||||
this.loanTermFrequency = loanTermFrequency;
|
||||
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
|
||||
@@ -320,6 +325,7 @@ public final class LoanApplicationTerms {
|
||||
this.inArrearsTolerance = inArrearsTolerance;
|
||||
this.multiDisburseLoan = multiDisburseLoan;
|
||||
this.fixedEmiAmount = emiAmount;
|
||||
this.actualFixedEmiAmount = emiAmount;
|
||||
this.disbursementDatas = disbursementDatas;
|
||||
this.maxOutstandingBalance = maxOutstandingBalance;
|
||||
this.emiAmountVariations = emiAmountVariations;
|
||||
@@ -332,6 +338,7 @@ public final class LoanApplicationTerms {
|
||||
this.restCalendarInstance = restCalendarInstance;
|
||||
this.recalculationFrequencyType = recalculationFrequencyType;
|
||||
this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
|
||||
this.instalmentAmountInMultiplesOf = instalmentAmountInMultiplesOf;
|
||||
}
|
||||
|
||||
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
|
||||
@@ -517,15 +524,6 @@ public final class LoanApplicationTerms {
|
||||
return periodNumber == numberOfRepayments;
|
||||
}
|
||||
|
||||
private Money calculateTotalDueForEqualInstallmentLoan(final BigDecimal periodicInterestRate) {
|
||||
|
||||
final int periodsElapsed = 0;
|
||||
final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed);
|
||||
double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments;
|
||||
|
||||
return Money.of(this.principal.getCurrency(), BigDecimal.valueOf(totalRepayment));
|
||||
}
|
||||
|
||||
/**
|
||||
* general method to calculate totalInterestDue discounting any grace
|
||||
* settings
|
||||
@@ -810,14 +808,21 @@ public final class LoanApplicationTerms {
|
||||
|
||||
private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) {
|
||||
|
||||
if (fixedEmiAmount != null) { return fixedEmiAmount.doubleValue(); }
|
||||
final double futureValue = 0;
|
||||
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
if (this.fixedEmiAmount == null) {
|
||||
final double futureValue = 0;
|
||||
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
final Integer periodsRemaining = this.numberOfRepayments - periodsElapsed;
|
||||
final Integer periodsRemaining = this.numberOfRepayments - periodsElapsed;
|
||||
|
||||
return FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(), principalDouble, futureValue,
|
||||
false);
|
||||
double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(),
|
||||
principalDouble, futureValue, false);
|
||||
|
||||
if (this.instalmentAmountInMultiplesOf != null) {
|
||||
installmentAmount = Money.roundToMultiplesOf(installmentAmount, this.instalmentAmountInMultiplesOf);
|
||||
}
|
||||
setFixedEmiAmount(BigDecimal.valueOf(installmentAmount));
|
||||
}
|
||||
return this.fixedEmiAmount.doubleValue();
|
||||
}
|
||||
|
||||
private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
@@ -1023,6 +1028,10 @@ public final class LoanApplicationTerms {
|
||||
this.fixedEmiAmount = fixedEmiAmount;
|
||||
}
|
||||
|
||||
public void resetFixedEmiAmount() {
|
||||
this.fixedEmiAmount = this.actualFixedEmiAmount;
|
||||
}
|
||||
|
||||
public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() {
|
||||
return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT;
|
||||
}
|
||||
|
||||
+7
-5
@@ -199,8 +199,8 @@ public class LoanScheduleAssembler {
|
||||
}
|
||||
|
||||
/*
|
||||
* If it is JLG loan/Group Loan synched with a meeting, then make sure first
|
||||
* repayment falls on meeting date
|
||||
* If it is JLG loan/Group Loan synched with a meeting, then make sure
|
||||
* first repayment falls on meeting date
|
||||
*/
|
||||
if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) {
|
||||
validateRepaymentsStartDateWithMeetingDates(calculatedRepaymentsStartingFromDate, calendar);
|
||||
@@ -249,8 +249,9 @@ public class LoanScheduleAssembler {
|
||||
final DaysInYearType daysInYearType = loanProduct.fetchDaysInYearType();
|
||||
|
||||
final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
|
||||
|
||||
|
||||
final BigDecimal principalThresholdForLastInstalment = loanProduct.getPrincipalThresholdForLastInstalment();
|
||||
final Integer instalmentAmountInMultiplesOf = loanProduct.getInstalmentAmountInMultiplesOf();
|
||||
|
||||
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
|
||||
@@ -258,7 +259,8 @@ public class LoanScheduleAssembler {
|
||||
principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
|
||||
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney,
|
||||
loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData,
|
||||
graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, principalThresholdForLastInstalment);
|
||||
graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, principalThresholdForLastInstalment,
|
||||
instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
private List<DisbursementData> fetchDisbursementData(final JsonObject command) {
|
||||
@@ -444,7 +446,7 @@ public class LoanScheduleAssembler {
|
||||
final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = expectedDisbursementDate
|
||||
.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||
|
||||
if ((loanType.isJLGAccount()|| loanType.isGroupAccount()) && calendar != null) {
|
||||
if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) {
|
||||
|
||||
final LocalDate refernceDateForCalculatingFirstRepaymentDate = expectedDisbursementDate;
|
||||
derivedFirstRepayment = deriveFirstRepaymentDateForJLGLoans(repaymentEvery, expectedDisbursementDate,
|
||||
|
||||
+5
-5
@@ -95,19 +95,19 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
|
||||
* TODO: Vishwas, this is probably not required, test and remove the
|
||||
* same
|
||||
**/
|
||||
final Long productId = this.fromJsonHelper.extractLongNamed("productId", query.parsedJson());
|
||||
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
|
||||
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
||||
|
||||
if (validateParams) {
|
||||
boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();
|
||||
this.loanApiJsonDeserializer.validateForCreate(query.json(), isMeetingMandatoryForJLGLoans);
|
||||
this.loanApiJsonDeserializer.validateForCreate(query.json(), isMeetingMandatoryForJLGLoans, loanProduct);
|
||||
}
|
||||
this.fromApiJsonDeserializer.validate(query.json());
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
|
||||
|
||||
final Long productId = this.fromJsonHelper.extractLongNamed("productId", query.parsedJson());
|
||||
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
|
||||
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
||||
|
||||
if (loanProduct.useBorrowerCycle()) {
|
||||
final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", query.parsedJson());
|
||||
final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", query.parsedJson());
|
||||
|
||||
+13
-2
@@ -21,6 +21,7 @@ import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException;
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
|
||||
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
|
||||
@@ -78,7 +79,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
this.apiJsonHelper = apiJsonHelper;
|
||||
}
|
||||
|
||||
public void validateForCreate(final String json, final boolean isMeetingMandatoryForJLGLoans) {
|
||||
public void validateForCreate(final String json, final boolean isMeetingMandatoryForJLGLoans, final LoanProduct loanProduct) {
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
@@ -362,6 +363,11 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)) {
|
||||
if (!(loanProduct.canDefineInstalmentAmount() || loanProduct.isMultiDisburseLoan())) {
|
||||
List<String> unsupportedParameterList = new ArrayList<>();
|
||||
unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
|
||||
throw new UnsupportedParameterException(unsupportedParameterList);
|
||||
}
|
||||
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
|
||||
@@ -377,7 +383,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
|
||||
public void validateForModify(final String json) {
|
||||
public void validateForModify(final String json, final LoanProduct loanProduct) {
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
@@ -705,6 +711,11 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.emiAmountParameterName, element)) {
|
||||
if (!(loanProduct.canDefineInstalmentAmount() || loanProduct.isMultiDisburseLoan())) {
|
||||
List<String> unsupportedParameterList = new ArrayList<>();
|
||||
unsupportedParameterList.add(LoanApiConstants.emiAmountParameterName);
|
||||
throw new UnsupportedParameterException(unsupportedParameterList);
|
||||
}
|
||||
final BigDecimal emiAnount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAnount).ignoreIfNull().positiveAmount();
|
||||
|
||||
+6
-7
@@ -194,15 +194,15 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
try {
|
||||
final AppUser currentUser = getAppUserIfPresent();
|
||||
boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();
|
||||
final Long productId = this.fromJsonHelper.extractLongNamed("productId", command.parsedJson());
|
||||
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
|
||||
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
||||
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json(), isMeetingMandatoryForJLGLoans);
|
||||
this.fromApiJsonDeserializer.validateForCreate(command.json(), isMeetingMandatoryForJLGLoans, loanProduct);
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
|
||||
|
||||
final Long productId = this.fromJsonHelper.extractLongNamed("productId", command.parsedJson());
|
||||
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
|
||||
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
|
||||
if (loanProduct.useBorrowerCycle()) {
|
||||
final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", command.parsedJson());
|
||||
final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson());
|
||||
@@ -377,10 +377,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
|
||||
try {
|
||||
AppUser currentUser = getAppUserIfPresent();
|
||||
|
||||
this.fromApiJsonDeserializer.validateForModify(command.json());
|
||||
|
||||
final Loan existingLoanApplication = retrieveLoanBy(loanId);
|
||||
this.fromApiJsonDeserializer.validateForModify(command.json(), existingLoanApplication.loanProduct());
|
||||
|
||||
checkClientOrGroupActive(existingLoanApplication);
|
||||
|
||||
if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
|
||||
|
||||
+3
-1
@@ -199,11 +199,13 @@ public class LoanAssembler {
|
||||
final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr);
|
||||
Set<LoanDisbursementDetails> disbursementDetails = null;
|
||||
BigDecimal fixedEmiAmount = null;
|
||||
if (loanProduct.isMultiDisburseLoan() || loanProduct.canDefineInstalmentAmount()) {
|
||||
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
|
||||
}
|
||||
BigDecimal maxOutstandingLoanBalance = null;
|
||||
if (loanProduct.isMultiDisburseLoan()) {
|
||||
disbursementDetails = fetchDisbursementData(element.getAsJsonObject());
|
||||
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
|
||||
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.emiAmountParameterName, element, locale);
|
||||
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
|
||||
element, locale);
|
||||
if (disbursementDetails.isEmpty()) {
|
||||
|
||||
+5
-3
@@ -501,6 +501,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
+ " l.loan_type_enum as loanType, l.loanpurpose_cv_id as loanPurposeId, cv.code_value as loanPurposeName,"
|
||||
+ " lp.id as loanProductId, lp.name as loanProductName, lp.description as loanProductDescription,"
|
||||
+ " lp.allow_multiple_disbursals as multiDisburseLoan,"
|
||||
+ " lp.can_define_fixed_emi_amount as canDefineInstalmentAmount,"
|
||||
+ " c.id as clientId, c.display_name as clientName, c.office_id as clientOfficeId,"
|
||||
+ " g.id as groupId, g.display_name as groupName,"
|
||||
+ " g.office_id as groupOfficeId, g.staff_id As groupStaffId , g.parent_id as groupParentId, "
|
||||
@@ -630,6 +631,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final String loanProductName = rs.getString("loanProductName");
|
||||
final String loanProductDescription = rs.getString("loanProductDescription");
|
||||
final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan");
|
||||
final Boolean canDefineInstalmentAmount = rs.getBoolean("canDefineInstalmentAmount");
|
||||
final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance");
|
||||
|
||||
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
|
||||
@@ -835,9 +837,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
|
||||
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged,
|
||||
syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, fixedEmiAmount,
|
||||
outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement);
|
||||
syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstalmentAmount,
|
||||
fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -85,4 +85,8 @@ public interface LoanProductConstants {
|
||||
public static final String principalThresholdForLastInstalmentParamName = "principalThresholdForLastInstalment";
|
||||
public static final BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_MULTI_DISBURSE_LOAN = BigDecimal.valueOf(50);
|
||||
public static final BigDecimal DEFAULT_PRINCIPAL_THRESHOLD_FOR_SINGLE_DISBURSE_LOAN = BigDecimal.valueOf(0);
|
||||
// Fixed installment configuration related
|
||||
public static final String canDefineEmiAmountParamName = "canDefineInstalmentAmount";
|
||||
public static final String instalmentAmountInMultiplesOfParamName = "instalmentAmountInMultiplesOf";
|
||||
|
||||
}
|
||||
|
||||
+25
-4
@@ -84,6 +84,8 @@ public class LoanProductData {
|
||||
private final boolean isInterestRecalculationEnabled;
|
||||
private final LoanProductInterestRecalculationData interestRecalculationData;
|
||||
private final Integer minimumDaysBetweenDisbursalAndFirstRepayment;
|
||||
private final boolean canDefineInstalmentAmount;
|
||||
private final Integer instalmentAmountInMultiplesOf;
|
||||
|
||||
// charges
|
||||
private final Collection<ChargeData> charges;
|
||||
@@ -197,6 +199,8 @@ public class LoanProductData {
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanProductInterestRecalculationData interestRecalculationData = null;
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null;
|
||||
final boolean canDefineInstalmentAmount = false;
|
||||
final Integer instalmentAmountInMultiplesOf = null;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -207,7 +211,8 @@ public class LoanProductData {
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
|
||||
principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstalmentAmount,
|
||||
instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
|
||||
@@ -265,6 +270,8 @@ public class LoanProductData {
|
||||
final LoanProductGuaranteeData productGuaranteeData = null;
|
||||
final BigDecimal principalThresholdForLastInstalment = null;
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
|
||||
final boolean canDefineInstalmentAmount = false;
|
||||
final Integer instalmentAmountInMultiplesOf = null;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -275,7 +282,8 @@ public class LoanProductData {
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
|
||||
principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstalmentAmount,
|
||||
instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
|
||||
@@ -340,6 +348,8 @@ public class LoanProductData {
|
||||
final LoanProductGuaranteeData productGuaranteeData = null;
|
||||
final BigDecimal principalThresholdForLastInstalment = null;
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false;
|
||||
final boolean canDefineInstalmentAmount = false;
|
||||
final Integer instalmentAmountInMultiplesOf = null;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
@@ -350,7 +360,8 @@ public class LoanProductData {
|
||||
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
|
||||
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
|
||||
holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion,
|
||||
canDefineInstalmentAmount, instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
|
||||
@@ -383,7 +394,8 @@ public class LoanProductData {
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds,
|
||||
final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstalment,
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion) {
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean canDefineInstalmentAmount,
|
||||
Integer instalmentAmountInMultiplesOf) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.shortName = shortName;
|
||||
@@ -465,6 +477,9 @@ public class LoanProductData {
|
||||
this.rescheduleStrategyTypeOptions = null;
|
||||
this.interestRecalculationFrequencyTypeOptions = null;
|
||||
|
||||
this.canDefineInstalmentAmount = canDefineInstalmentAmount;
|
||||
this.instalmentAmountInMultiplesOf = instalmentAmountInMultiplesOf;
|
||||
|
||||
}
|
||||
|
||||
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
|
||||
@@ -575,6 +590,8 @@ public class LoanProductData {
|
||||
this.interestRecalculationFrequencyTypeOptions = interestRecalculationFrequencyTypeOptions;
|
||||
}
|
||||
|
||||
this.canDefineInstalmentAmount = productData.canDefineInstalmentAmount;
|
||||
this.instalmentAmountInMultiplesOf = productData.instalmentAmountInMultiplesOf;
|
||||
}
|
||||
|
||||
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
|
||||
@@ -824,4 +841,8 @@ public class LoanProductData {
|
||||
if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyInterval(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean canDefineInstalmentAmount() {
|
||||
return this.canDefineInstalmentAmount;
|
||||
}
|
||||
}
|
||||
+35
-2
@@ -142,6 +142,12 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
@Column(name = "account_moves_out_of_npa_only_on_arrears_completion")
|
||||
private boolean accountMovesOutOfNPAOnlyOnArrearsCompletion;
|
||||
|
||||
@Column(name = "can_define_fixed_emi_amount")
|
||||
private boolean canDefineInstalmentAmount;
|
||||
|
||||
@Column(name = "instalment_amount_in_multiples_of", nullable = true)
|
||||
private Integer instalmentAmountInMultiplesOf;
|
||||
|
||||
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
|
||||
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
|
||||
|
||||
@@ -240,6 +246,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
}
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = command
|
||||
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName);
|
||||
final boolean canDefineEmiAmount = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.canDefineEmiAmountParamName);
|
||||
final Integer instalmentAmountInMultiplesOf = command
|
||||
.integerValueOfParameterNamed(LoanProductConstants.instalmentAmountInMultiplesOfParamName);
|
||||
|
||||
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
|
||||
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
|
||||
@@ -250,7 +259,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment,
|
||||
holdGuarantorFunds, loanProductGuaranteeDetails, principalThresholdForLastInstalment,
|
||||
accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineEmiAmount, instalmentAmountInMultiplesOf);
|
||||
|
||||
}
|
||||
|
||||
@@ -476,7 +485,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
final LoanProductInterestRecalculationDetails productInterestRecalculationDetails,
|
||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
|
||||
final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final BigDecimal principalThresholdForLastInstalment,
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion) {
|
||||
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, final boolean canDefineEmiAmount,
|
||||
final Integer instalmentAmountInMultiplesOf) {
|
||||
this.fund = fund;
|
||||
this.transactionProcessingStrategy = transactionProcessingStrategy;
|
||||
this.name = name.trim();
|
||||
@@ -530,6 +540,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
this.loanProductGuaranteeDetails = loanProductGuaranteeDetails;
|
||||
this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
|
||||
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion;
|
||||
this.canDefineInstalmentAmount = canDefineEmiAmount;
|
||||
this.instalmentAmountInMultiplesOf = instalmentAmountInMultiplesOf;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
@@ -771,6 +783,19 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
actualChanges.put(LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, newValue);
|
||||
this.accountMovesOutOfNPAOnlyOnArrearsCompletion = newValue;
|
||||
}
|
||||
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.canDefineEmiAmountParamName, this.canDefineInstalmentAmount)) {
|
||||
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.canDefineEmiAmountParamName);
|
||||
actualChanges.put(LoanProductConstants.canDefineEmiAmountParamName, newValue);
|
||||
this.canDefineInstalmentAmount = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamedWithNullCheck(LoanProductConstants.instalmentAmountInMultiplesOfParamName,
|
||||
this.instalmentAmountInMultiplesOf)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.instalmentAmountInMultiplesOfParamName);
|
||||
actualChanges.put(LoanProductConstants.instalmentAmountInMultiplesOfParamName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.instalmentAmountInMultiplesOf = newValue;
|
||||
}
|
||||
|
||||
return actualChanges;
|
||||
}
|
||||
@@ -1036,4 +1061,12 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
return isBasedOnOriginalSchedule;
|
||||
}
|
||||
|
||||
public boolean canDefineInstalmentAmount() {
|
||||
return this.canDefineInstalmentAmount;
|
||||
}
|
||||
|
||||
public Integer getInstalmentAmountInMultiplesOf() {
|
||||
return this.instalmentAmountInMultiplesOf;
|
||||
}
|
||||
|
||||
}
|
||||
+28
-1
@@ -75,7 +75,8 @@ public final class LoanProductDataValidator {
|
||||
LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName,
|
||||
LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName,
|
||||
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, LoanProductConstants.principalThresholdForLastInstalmentParamName,
|
||||
LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName));
|
||||
LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, LoanProductConstants.canDefineEmiAmountParamName,
|
||||
LoanProductConstants.instalmentAmountInMultiplesOfParamName));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@@ -338,6 +339,19 @@ public final class LoanProductDataValidator {
|
||||
LoanProductConstants.principalThresholdForLastInstalmentParamName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.principalThresholdForLastInstalmentParamName)
|
||||
.value(principalThresholdForLastInstalment).notLessThanMin(BigDecimal.ZERO).notGreaterThanMax(BigDecimal.valueOf(100));
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.canDefineEmiAmountParamName, element)) {
|
||||
final Boolean canDefineInstalmentAmount = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
LoanProductConstants.canDefineEmiAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.canDefineEmiAmountParamName).value(canDefineInstalmentAmount)
|
||||
.isOneOfTheseValues(true, false);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.instalmentAmountInMultiplesOfParamName, element)) {
|
||||
final Integer instalmentAmountInMultiplesOf = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
|
||||
LoanProductConstants.instalmentAmountInMultiplesOfParamName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.instalmentAmountInMultiplesOfParamName)
|
||||
.value(instalmentAmountInMultiplesOf).ignoreIfNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
// accounting related data validation
|
||||
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
|
||||
@@ -782,6 +796,19 @@ public final class LoanProductDataValidator {
|
||||
.value(principalThresholdForLastInstalment).notNull().notLessThanMin(BigDecimal.ZERO)
|
||||
.notGreaterThanMax(BigDecimal.valueOf(100));
|
||||
}
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.canDefineEmiAmountParamName, element)) {
|
||||
final Boolean canDefineInstalmentAmount = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
LoanProductConstants.canDefineEmiAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.canDefineEmiAmountParamName).value(canDefineInstalmentAmount)
|
||||
.isOneOfTheseValues(true, false);
|
||||
}
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.instalmentAmountInMultiplesOfParamName, element)) {
|
||||
final Integer instalmentAmountInMultiplesOf = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(
|
||||
LoanProductConstants.instalmentAmountInMultiplesOfParamName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.instalmentAmountInMultiplesOfParamName)
|
||||
.value(instalmentAmountInMultiplesOf).ignoreIfNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).ignoreIfNull().inMinMaxRange(1, 4);
|
||||
|
||||
+5
-2
@@ -160,6 +160,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
+ "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, "
|
||||
+ "lp.allow_multiple_disbursals as multiDisburseLoan, lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as outstandingLoanBalance, "
|
||||
+ "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, "
|
||||
+ "lp.can_define_fixed_emi_amount as canDefineInstalmentAmount, lp.instalment_amount_in_multiples_of as instalmentAmountInMultiplesOf, "
|
||||
+ "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, "
|
||||
+ "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
|
||||
+ "lpr.rest_freqency_date as restFrequencyDate, lpr.arrears_based_on_original_schedule as isArrearsBasedOnOriginalSchedule, "
|
||||
@@ -276,6 +277,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(daysInMonth);
|
||||
final int daysInYear = JdbcSupport.getInteger(rs, "daysInYear");
|
||||
final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(daysInYear);
|
||||
final Integer instalmentAmountInMultiplesOf = JdbcSupport.getInteger(rs, "instalmentAmountInMultiplesOf");
|
||||
final boolean canDefineInstalmentAmount = rs.getBoolean("canDefineInstalmentAmount");
|
||||
final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled");
|
||||
|
||||
LoanProductInterestRecalculationData interestRecalculationData = null;
|
||||
@@ -322,9 +325,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData,
|
||||
principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion);
|
||||
principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstalmentAmount,
|
||||
instalmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class LoanProductLookupMapper implements RowMapper<LoanProductData> {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `m_product_loan`
|
||||
ADD COLUMN `can_define_fixed_emi_amount` TINYINT(1) NOT NULL DEFAULT '0',
|
||||
ADD COLUMN `instalment_amount_in_multiples_of` DECIMAL(19,6) NULL DEFAULT NULL;
|
||||
Reference in New Issue
Block a user