package org.mifos.clientportfolio.newloan.domain; import static org.mifos.accounts.loan.util.helpers.LoanConstants.PRORATE_RULE; import java.util.ArrayList; import java.util.List; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.mifos.accounts.loan.util.helpers.InstallmentPrincipalAndInterest; import org.mifos.accounts.productdefinition.util.helpers.GraceType; import org.mifos.config.persistence.ConfigurationPersistence; import org.mifos.framework.util.helpers.Money; import org.mifos.framework.util.helpers.MoneyUtils; public class DecliningBalancePrincipalWithInterestGenerator implements PrincipalWithInterestGenerator { @Override public List<InstallmentPrincipalAndInterest> generateEqualInstallments(LoanInterestCalculationDetails loanInterestCalculationDetails) { GraceType graceType = loanInterestCalculationDetails.getGraceType(); Integer gracePeriodDuration = loanInterestCalculationDetails.getGracePeriodDuration(); Money loanAmount = loanInterestCalculationDetails.getLoanAmount(); Integer numberOfInstallments = loanInterestCalculationDetails.getNumberOfInstallments(); Double interestFractionalRatePerInstallment = loanInterestCalculationDetails.getInterestFractionalRatePerInstallment(); Double interestRate = loanInterestCalculationDetails.getInterestRate(); int prorateValue = 0; List<InstallmentPrincipalAndInterest> lstInstallmntPricplIntrst = null; LocalDate disbursalDateWithLocalDate=loanInterestCalculationDetails.getDisbursementDate(); DateTime disbursalDate=disbursalDateWithLocalDate.toDateTimeAtStartOfDay(); List<DateTime> dates=loanInterestCalculationDetails.getLoanScheduleDates(); if(dates.size() > 1){ //check whether loanscheduledDates are there DateTime firstRepaymentDay=dates.get(0); long differenceOfTwoDatesinMilliseconds=(firstRepaymentDay.toDate().getTime()-disbursalDate.toDate().getTime()); long noOfDays=differenceOfTwoDatesinMilliseconds/(1000*60*60*24); int noOfDaysBetweenFirstRepaymentDayAndDisbursalDate=(int)noOfDays; DateTime secondRepaymentDay=dates.get(1); long duration=(secondRepaymentDay.toDate().getTime()-firstRepaymentDay.toDate().getTime())/(1000*60*60*24); int noOfDaysInOneSchedule=(int)duration; prorateValue = new ConfigurationPersistence().getConfigurationValueInteger(PRORATE_RULE); if (prorateValue==1) lstInstallmntPricplIntrst =allDecliningInstallments_v2(loanAmount, numberOfInstallments, graceType, gracePeriodDuration, interestFractionalRatePerInstallment, interestRate,noOfDaysBetweenFirstRepaymentDayAndDisbursalDate,noOfDaysInOneSchedule); } if (prorateValue != 1){ lstInstallmntPricplIntrst =allDecliningInstallments_v2(loanAmount, numberOfInstallments, graceType, gracePeriodDuration, interestFractionalRatePerInstallment, interestRate); } return lstInstallmntPricplIntrst; } /** * Generate declining-interest installment variants based on the type of grace period. * <ul> * <li>If grace period is none, or applies to both principal and interest, the loan calculations are the same. * <li>If grace period is for principal only, don't add new installments. The first grace installments are * interest-only, and principal is paid off with the remaining installments. * </ul> */ private List<InstallmentPrincipalAndInterest> allDecliningInstallments_v2(Money loanAmount, Integer numberOfInstallments, GraceType graceType, Integer gracePeriodDuration, Double interestFractionalRatePerInstallment, Double interestRate) { List<InstallmentPrincipalAndInterest> emiInstallments = new ArrayList<InstallmentPrincipalAndInterest>(); if (graceType == GraceType.NONE || graceType == GraceType.GRACEONALLREPAYMENTS) { Money paymentPerPeriod = getPaymentPerPeriodForDecliningInterest_v2(numberOfInstallments, interestRate, loanAmount, interestFractionalRatePerInstallment); emiInstallments = generateDecliningInstallmentsNoGrace_v2(numberOfInstallments, loanAmount, interestFractionalRatePerInstallment, paymentPerPeriod); } else { // getGraceType() == GraceType.PRINCIPALONLYGRACE which is disabled. emiInstallments = generateDecliningInstallmentsInterestOnly_v2(loanAmount, gracePeriodDuration, interestFractionalRatePerInstallment); int nonGraceInstallments = (numberOfInstallments - gracePeriodDuration); Money paymentPerPeriod = getPaymentPerPeriodForDecliningInterest_v2(nonGraceInstallments, interestRate, loanAmount, interestFractionalRatePerInstallment); emiInstallments.addAll(generateDecliningInstallmentsNoGrace_v2(nonGraceInstallments, loanAmount, interestFractionalRatePerInstallment, paymentPerPeriod)); } return emiInstallments; } //for M5193 private List<InstallmentPrincipalAndInterest> allDecliningInstallments_v2(Money loanAmount, Integer numberOfInstallments, GraceType graceType, Integer gracePeriodDuration, Double interestFractionalRatePerInstallment, Double interestRate,Integer days,Integer durationInDays) { List<InstallmentPrincipalAndInterest> emiInstallments = new ArrayList<InstallmentPrincipalAndInterest>(); if (graceType == GraceType.NONE || graceType == GraceType.GRACEONALLREPAYMENTS) { Money paymentPerPeriod = getPaymentPerPeriodForDecliningInterest_v2(numberOfInstallments, interestRate, loanAmount, interestFractionalRatePerInstallment); emiInstallments = generateDecliningInstallmentsNoGrace_v2(numberOfInstallments, loanAmount, interestFractionalRatePerInstallment, paymentPerPeriod); } else { // getGraceType() == GraceType.PRINCIPALONLYGRACE which is disabled. emiInstallments = generateDecliningInstallmentsInterestOnly_v2(loanAmount, gracePeriodDuration, interestFractionalRatePerInstallment,days,durationInDays); int nonGraceInstallments = (numberOfInstallments - gracePeriodDuration); Money paymentPerPeriod = getPaymentPerPeriodForDecliningInterest_v2(nonGraceInstallments, interestRate, loanAmount, interestFractionalRatePerInstallment); emiInstallments.addAll(generateDecliningInstallmentsNoGrace_v2(nonGraceInstallments, loanAmount, interestFractionalRatePerInstallment, paymentPerPeriod)); } return emiInstallments; } /* * Calculates equal payments per period for fixed payment, declining-interest loan type. Uses formula from * http://confluence.mifos.org :9090/display/Main/Declining+Balance+Example+Calcs The formula is copied here: EMI = * P * i / [1- (1+i)^-n] where p = principal (amount of loan) i = rate of interest per installment period as a * decimal (not percent) n = no. of installments * * Translated into program variables and method calls: * * paymentPerPeriod = interestFractionalRatePerPeriod * getLoanAmount() / ( 1 - (1 + * interestFractionalRatePerPeriod) ^ (-getNoOfInstallments())) * * NOTE: Use double here, not BigDecimal, to calculate the factor that getLoanAmount() is multiplied by. Since * calculations all involve small quantities, 64-bit precision is sufficient. It is is more accurate to use * floating-point, for quantities of small magnitude (say for very small interest rates) * * NOTE: These calculations do not take into account EPI or grace period adjustments. */ private Money getPaymentPerPeriodForDecliningInterest_v2(final int numInstallments, final Double interestRate, final Money loanAmount, Double interestFractionalRatePerInstallment) { double factor = 0.0; if (interestRate == 0.0) { Money paymentPerPeriod = loanAmount.divide(numInstallments); return paymentPerPeriod; } factor = interestFractionalRatePerInstallment / (1.0 - Math.pow(1.0 + interestFractionalRatePerInstallment, -numInstallments)); Money paymentPerPeriod = loanAmount.multiply(factor); return paymentPerPeriod; } /** * Generate interest-only payments for the duration of the grace period. Interest paid is on the outstanding * balance, which during the grace period is the entire principal amount. */ private List<InstallmentPrincipalAndInterest> generateDecliningInstallmentsInterestOnly_v2(Money loanAmount, Integer gracePeriodDuration, Double interestFractionalRatePerInstallment) { List<InstallmentPrincipalAndInterest> emiInstallments = new ArrayList<InstallmentPrincipalAndInterest>(); Money zero = MoneyUtils.zero(loanAmount.getCurrency()); for (int i = 0; i < gracePeriodDuration; i++) { InstallmentPrincipalAndInterest installment = new InstallmentPrincipalAndInterest(zero, loanAmount.multiply(interestFractionalRatePerInstallment)); emiInstallments.add(installment); } return emiInstallments; } //for M5193 private List<InstallmentPrincipalAndInterest> generateDecliningInstallmentsInterestOnly_v2(Money loanAmount, Integer gracePeriodDuration, Double interestFractionalRatePerInstallment,Integer days,Integer durationInDays) { List<InstallmentPrincipalAndInterest> emiInstallments = new ArrayList<InstallmentPrincipalAndInterest>(); Money zero = MoneyUtils.zero(loanAmount.getCurrency()); for (int i = 0; i < gracePeriodDuration; i++) { if (i<1) { int noOfDaysBetweenDisbursaldateAndNextMeetingDate=days; InstallmentPrincipalAndInterest installment = new InstallmentPrincipalAndInterest(zero, loanAmount.multiply(interestFractionalRatePerInstallment,noOfDaysBetweenDisbursaldateAndNextMeetingDate,durationInDays)); emiInstallments.add(installment); } else{ InstallmentPrincipalAndInterest installment = new InstallmentPrincipalAndInterest(zero, loanAmount.multiply(interestFractionalRatePerInstallment)); emiInstallments.add(installment); } } return emiInstallments; } /** * Return the list if payment installments for declining interest method, for the number of installments specified. */ private List<InstallmentPrincipalAndInterest> generateDecliningInstallmentsNoGrace_v2(final int numberOfInstallments, Money loanAmount, Double interestFractionalRatePerInstallment, Money paymentPerPeriod) { List<InstallmentPrincipalAndInterest> emiInstallments = new ArrayList<InstallmentPrincipalAndInterest>(); Money decliningPrincipalBalance = loanAmount; for (int i = 0; i < numberOfInstallments; i++) { Money interestThisPeriod = decliningPrincipalBalance.multiply(interestFractionalRatePerInstallment); Money principalThisPeriod = paymentPerPeriod.subtract(interestThisPeriod); InstallmentPrincipalAndInterest installment = new InstallmentPrincipalAndInterest(principalThisPeriod, interestThisPeriod); emiInstallments.add(installment); decliningPrincipalBalance = decliningPrincipalBalance.subtract(principalThisPeriod); } return emiInstallments; } }