squash changes in interest-calculation-extension branch

This commit is contained in:
Keith Woodlock
2012-05-01 17:31:32 +01:00
parent ff120f925c
commit d3d40cabe0
37 changed files with 1639 additions and 406 deletions
@@ -12,12 +12,14 @@ import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.currency.domain.ApplicationCurrencyRepository;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
import org.mifosng.platform.loan.domain.InterestMethod;
import org.mifosng.platform.loan.domain.Loan;
import org.mifosng.platform.loan.domain.LoanPayoffSummary;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.LoanRepository;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.loanschedule.domain.AprCalculator;
import org.mifosng.platform.loanschedule.domain.DefaultLoanScheduleGeneratorFactory;
import org.mifosng.platform.loanschedule.domain.LoanScheduleGenerator;
import org.mifosng.platform.loanschedule.domain.LoanScheduleGeneratorFactory;
@@ -31,11 +33,13 @@ public class CalculationPlatformServiceImpl implements
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final LoanRepository loanRepository;
private final ApplicationCurrencyRepository applicationCurrencyRepository;
private final AprCalculator aprCalculator;
@Autowired
public CalculationPlatformServiceImpl(final LoanRepository loanRepository, final ApplicationCurrencyRepository applicationCurrencyRepository) {
public CalculationPlatformServiceImpl(final LoanRepository loanRepository, final ApplicationCurrencyRepository applicationCurrencyRepository, final AprCalculator aprCalculator) {
this.loanRepository = loanRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.aprCalculator = aprCalculator;
this.loanScheduleFactory = new DefaultLoanScheduleGeneratorFactory();
}
@@ -45,70 +49,46 @@ public class CalculationPlatformServiceImpl implements
CalculateLoanScheduleCommandValidator validator = new CalculateLoanScheduleCommandValidator(command);
validator.validate();
// assemble 'loan data' into 'domain' object [LoanProductRelatedDetail]
final BigDecimal principalAmount = BigDecimal.valueOf(command.getPrincipal().doubleValue());
final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(command.getInterestRatePerPeriod().doubleValue());
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod());
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequency());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod());
final boolean flexibleRepaymentSchedule = command.isFlexibleRepaymentSchedule();
final boolean interestRebateAllowed = command.isInterestRebateAllowed();
final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, defaultNominalInterestRatePerPeriod);
final MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal());
// in arrerars tolerance isnt relevant when auto-calculating a loan schedule
final BigDecimal inArrearsTolerance = BigDecimal.ZERO;
LoanProductRelatedDetail loanScheduleInfo = new LoanProductRelatedDetail(currency, principalAmount,
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod,
inArrearsTolerance, flexibleRepaymentSchedule, interestRebateAllowed);
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod);
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(command.getCurrencyCode());
CurrencyData currencyData = new CurrencyData(applicationCurrency.getCode(), applicationCurrency.getName(), command.getDigitsAfterDecimal(),
applicationCurrency.getDisplaySymbol(), applicationCurrency.getNameCode());
MonetaryCurrency currency = new MonetaryCurrency(command.getCurrencyCode(), command.getDigitsAfterDecimal());
final BigDecimal principalAmount = BigDecimal.valueOf(command.getPrincipal()
.doubleValue());
final BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(command
.getInterestRatePerPeriod().doubleValue());
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType
.fromInt(command.getRepaymentFrequency());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod());
final boolean flexibleRepaymentSchedule = command.isFlexibleRepaymentSchedule();
final boolean interestRebateAllowed = command.isInterestRebateAllowed();
// apr calculator
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO;
switch (interestPeriodFrequencyType) {
case DAYS:
break;
case WEEKS:
defaultAnnualNominalInterestRate = command
.getInterestRatePerPeriod()
.multiply(BigDecimal.valueOf(52));
break;
case MONTHS:
defaultAnnualNominalInterestRate = command
.getInterestRatePerPeriod()
.multiply(BigDecimal.valueOf(12));
break;
case YEARS:
defaultAnnualNominalInterestRate = command
.getInterestRatePerPeriod().multiply(BigDecimal.valueOf(1));
break;
case INVALID:
break;
}
LoanProductRelatedDetail loanScheduleInfo = new LoanProductRelatedDetail(currency, principalAmount,
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, BigDecimal.ZERO, flexibleRepaymentSchedule, interestRebateAllowed);
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory
.create(interestMethod, amortizationMethod);
return loanScheduleGenerator.generate(loanScheduleInfo, new LocalDate(
command.getExpectedDisbursementDate()), command
.getRepaymentsStartingFromDate(), command.getInterestCalculatedFromDate(), currencyData);
return loanScheduleGenerator.generate(loanScheduleInfo, command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate(),
command.getInterestCalculatedFromDate(), currencyData);
}
@Override
public LoanPayoffReadModel calculatePayoffOn(final Long loanId,
final LocalDate payoffDate) {
public LoanPayoffReadModel calculatePayoffOn(final Long loanId, final LocalDate payoffDate) {
// use jdbc approach to calculating payoff if possible.
// TODO - use jdbc approach to calculating payoff if possible.
final Loan loan = this.loanRepository.findOne(loanId);
final LoanPayoffSummary payoffSummary = loan.getPayoffSummaryOn(payoffDate);
@@ -37,6 +37,7 @@ import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.ClientNotAuthenticatedException;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine;
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
import org.mifosng.platform.loan.domain.InterestMethod;
import org.mifosng.platform.loan.domain.Loan;
import org.mifosng.platform.loan.domain.LoanBuilder;
@@ -167,6 +168,7 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
}
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod());
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType
@@ -177,7 +179,7 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
final boolean interestRebateAllowed = command.isInterestRebateAllowed();
LoanProductRelatedDetail loanRepaymentScheduleDetail = new LoanProductRelatedDetail(currency, command.getPrincipal(),
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsToleranceAmount(), flexibleRepaymentSchedule, interestRebateAllowed);
LoanSchedule loanSchedule = command.getLoanSchedule();
@@ -460,11 +462,12 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
Number interestRatePerPeriod = interestRatePerYear;
Integer interestRatePeriodFrequency = PeriodFrequencyType.YEARS.getValue();
Integer interestMethod = matchingProduct.getInterestMethod().getValue();
Integer interestCalculationPeriodMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue();
CalculateLoanScheduleCommand calculateLoanScheduleCommand = new CalculateLoanScheduleCommand(currencyCode, digitsAfterDecimal, principal,
interestRatePerPeriod,
interestRatePeriodFrequency,
interestMethod,
interestMethod, interestCalculationPeriodMethod,
repaymentEvery,
repaymentFrequency, numberOfRepayments, amortizationMethod, flexibleRepaymentSchedule,
matchingProduct.isInterestRebateAllowed(), expectedDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
@@ -474,7 +477,7 @@ public class ImportPlatformServiceImpl implements ImportPlatformService {
SubmitLoanApplicationCommand parsedNewLoanDetails = new SubmitLoanApplicationCommand(matchingClient.getId(), matchingProduct.getId(), submittedOnDate, noNoteComment,
expectedDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate, loanSchedule,
currencyCode, digitsAfterDecimal, principal,
interestRatePerPeriod, interestRatePeriodFrequency, interestMethod,
interestRatePerPeriod, interestRatePeriodFrequency, interestMethod, interestCalculationPeriodMethod,
repaymentEvery, repaymentFrequency, numberOfRepayments, amortizationMethod, toleranceAmount.getAmount(),
flexibleRepaymentSchedule, matchingProduct.isInterestRebateAllowed());
@@ -53,6 +53,8 @@ public interface ReadPlatformService {
List<EnumOptionReadModel> retrieveLoanAmortizationMethodOptions();
List<EnumOptionReadModel> retrieveLoanInterestMethodOptions();
List<EnumOptionReadModel> retrieveLoanInterestRateCalculatedInPeriodOptions();
List<EnumOptionReadModel> retrieveRepaymentFrequencyOptions();
@@ -749,7 +749,7 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
return "lp.id as id, lp.name as name, lp.description as description, lp.flexible_repayment_schedule as isFlexible, lp.interest_rebate as isInterestRebateAllowed, "
+ "lp.principal_amount as principal, lp.currency_code as currencyCode, lp.currency_digits as currencyDigits, "
+ "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, "
+ "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, "
+ "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,"
+ "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, "
+ "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
+ "lp.created_date as createdon, lp.lastmodified_date as modifiedon "
@@ -786,6 +786,7 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
BigDecimal annualInterestRate = rs
.getBigDecimal("annualInterestRate");
int interestMethod = rs.getInt("interestMethod");
int interestCalculationInPeriodMethod = rs.getInt("interestCalculationInPeriodMethod");
int repaidEvery = rs.getInt("repaidEvery");
int repaymentFrequency = rs.getInt("repaymentPeriodFrequency");
@@ -798,7 +799,8 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
return new LoanProductData(id, name, description, isFlexible,
isInterestRebateAllowed, principalMoney,
interestRatePerPeriod, interestRatePeriod,
annualInterestRate, interestMethod, repaidEvery,
annualInterestRate, interestMethod, interestCalculationInPeriodMethod,
repaidEvery,
repaymentFrequency, numberOfRepayments, amortizationMethod,
toleranceMoney, createdOn, lastModifedOn);
}
@@ -818,7 +820,8 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
@Override
public LoanProductData retrieveLoanProduct(final Long loanProductId) {
List<CurrencyData> allowedCurrencies = retrieveAllowedCurrencies();
// TODO - switch back to retrieve all allowed organisation currencies
List<CurrencyData> allowedCurrencies = retrieveAllPlatformCurrencies();
LoanProductMapper rm = new LoanProductMapper(allowedCurrencies);
String sql = "select " + rm.loanProductSchema() + " where lp.id = ?";
@@ -838,12 +841,14 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
List<EnumOptionReadModel> possibleAmortizationOptions = retrieveLoanAmortizationMethodOptions();
List<EnumOptionReadModel> possibleInterestOptions = retrieveLoanInterestMethodOptions();
List<EnumOptionReadModel> possibleInterestRateCalculatedInPeriodOptions = retrieveLoanInterestRateCalculatedInPeriodOptions();
List<EnumOptionReadModel> repaymentFrequencyOptions = retrieveRepaymentFrequencyOptions();
List<EnumOptionReadModel> interestFrequencyOptions = retrieveInterestFrequencyOptions();
productData.setPossibleCurrencies(possibleCurrencies);
productData.setPossibleAmortizationOptions(possibleAmortizationOptions);
productData.setPossibleInterestOptions(possibleInterestOptions);
productData.setPossibleInterestRateCalculatedInPeriodOptions(possibleInterestRateCalculatedInPeriodOptions);
productData.setRepaymentFrequencyOptions(repaymentFrequencyOptions);
productData.setInterestFrequencyOptions(interestFrequencyOptions);
}
@@ -866,6 +871,7 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
productData.setInterestMethod(Integer.valueOf(0));
productData.setRepaymentPeriodFrequency(2);
productData.setInterestRatePeriod(2);
productData.setInterestRateCalculatedInPeriod(1);
productData.setRepaidEvery(1);
productData.setNumberOfRepayments(0);
@@ -942,7 +948,18 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
return allowedRepaymentScheduleCalculationMethods;
}
@Override
public List<EnumOptionReadModel> retrieveLoanInterestRateCalculatedInPeriodOptions() {
EnumOptionReadModel option1 = new EnumOptionReadModel("Daily", Long.valueOf(0));
EnumOptionReadModel option2 = new EnumOptionReadModel("Same as repayment period", Long.valueOf(1));
List<EnumOptionReadModel> allowedOptions = Arrays.asList(option1,option2);
return allowedOptions;
}
@Override
public List<EnumOptionReadModel> retrieveRepaymentFrequencyOptions() {
EnumOptionReadModel frequency1 = new EnumOptionReadModel("Days",
@@ -958,14 +975,12 @@ public class ReadPlatformServiceImpl implements ReadPlatformService {
@Override
public List<EnumOptionReadModel> retrieveInterestFrequencyOptions() {
EnumOptionReadModel perWeek = new EnumOptionReadModel("Per week",
PeriodFrequencyType.WEEKS.getValue().longValue());
// support for monthly and annual percentage rate (MPR) and (APR)
EnumOptionReadModel perMonth = new EnumOptionReadModel("Per month",
PeriodFrequencyType.MONTHS.getValue().longValue());
EnumOptionReadModel perYear = new EnumOptionReadModel("Per year",
PeriodFrequencyType.YEARS.getValue().longValue());
List<EnumOptionReadModel> repaymentFrequencyOptions = Arrays.asList(
perWeek, perMonth, perYear);
List<EnumOptionReadModel> repaymentFrequencyOptions = Arrays.asList(perMonth, perYear);
return repaymentFrequencyOptions;
}
@@ -40,7 +40,8 @@ public class SubmitLoanApplicationCommandValidator {
command.getCurrencyCode(), command.getDigitsAfterDecimal(),
command.getPrincipal(), command.getInterestRatePerPeriod(),
command.getInterestRateFrequencyMethod(),
command.getInterestMethod(), command.getRepaymentEvery(),
command.getInterestMethod(), command.getInterestCalculationPeriodMethod(),
command.getRepaymentEvery(),
command.getRepaymentFrequency(),
command.getNumberOfRepayments(),
command.getAmortizationMethod(),
@@ -54,6 +54,7 @@ import org.mifosng.platform.infrastructure.PlatformUser;
import org.mifosng.platform.infrastructure.UsernameAlreadyExistsException;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.DefaultLoanLifecycleStateMachine;
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
import org.mifosng.platform.loan.domain.InterestMethod;
import org.mifosng.platform.loan.domain.Loan;
import org.mifosng.platform.loan.domain.LoanBuilder;
@@ -447,8 +448,8 @@ public class WritePlatformServiceJpaRepositoryImpl implements
validator.validateForCreate(command);
// assemble LoanProduct from data
InterestMethod interestMethod = InterestMethod
.fromInt(command.getInterestMethod());
InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod());
AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationMethod());
@@ -480,7 +481,7 @@ public class WritePlatformServiceJpaRepositoryImpl implements
LoanProduct loanproduct = new LoanProduct(currentUser.getOrganisation(), command.getName(), command.getDescription(),
currency, command.getPrincipal(),
command.getInterestRatePerPeriod(), interestFrequencyType, annualInterestRate, interestMethod,
command.getInterestRatePerPeriod(), interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod,
command.getRepaymentEvery(), repaymentFrequencyType, command.getNumberOfRepayments(), amortizationMethod, command.getInArrearsToleranceAmount(),
command.isFlexibleRepaymentSchedule(), command.isInterestRebateAllowed());
@@ -652,7 +653,8 @@ public class WritePlatformServiceJpaRepositoryImpl implements
break;
}
final InterestMethod interestMethod = InterestMethod.DECLINING_BALANCE;
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestMethod());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod());
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType
@@ -663,7 +665,8 @@ public class WritePlatformServiceJpaRepositoryImpl implements
final boolean interestRebateAllowed = command.isInterestRebateAllowed();
LoanProductRelatedDetail loanRepaymentScheduleDetail = new LoanProductRelatedDetail(currency,
command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
command.getPrincipal(), defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, command.getInArrearsToleranceAmount(),
flexibleRepaymentSchedule, interestRebateAllowed);
@@ -890,41 +893,32 @@ public class WritePlatformServiceJpaRepositoryImpl implements
if (loan.isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
LocalDate repaymentsStartingFromDate = loan
.getExpectedFirstRepaymentOnDate();
LocalDate repaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
LocalDate interestCalculatedFromDate = loan.getInterestCalculatedFromDate();
Number principalAsDecimal = loan.getLoanRepaymentScheduleDetail()
.getPrincipal().getAmount();
String currencyCode = loan.getLoanRepaymentScheduleDetail()
.getPrincipal().getCurrencyCode();
int currencyDigits = loan.getLoanRepaymentScheduleDetail()
.getPrincipal().getCurrencyDigitsAfterDecimal();
Number principalAsDecimal = loan.getLoanRepaymentScheduleDetail().getPrincipal().getAmount();
String currencyCode = loan.getLoanRepaymentScheduleDetail().getPrincipal().getCurrencyCode();
int currencyDigits = loan.getLoanRepaymentScheduleDetail().getPrincipal().getCurrencyDigitsAfterDecimal();
Number interestRatePerYear = loan.getLoanRepaymentScheduleDetail()
.getAnnualNominalInterestRate();
Integer numberOfInstallments = loan
.getLoanRepaymentScheduleDetail().getNumberOfRepayments();
Number interestRatePerYear = loan.getLoanRepaymentScheduleDetail().getAnnualNominalInterestRate();
Integer numberOfInstallments = loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments();
Integer repaidEvery = loan.getLoanRepaymentScheduleDetail()
.getRepayEvery();
Integer selectedRepaymentFrequency = loan
.getLoanRepaymentScheduleDetail()
.getRepaymentPeriodFrequencyType().getValue();
Integer selectedRepaymentSchedule = loan
.getLoanRepaymentScheduleDetail()
.getAmortizationMethod().getValue();
boolean flexibleRepaymentSchedule = loan
.isFlexibleRepaymentSchedule();
Integer repaidEvery = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
Integer selectedRepaymentFrequency = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
Integer selectedRepaymentSchedule = loan.getLoanRepaymentScheduleDetail().getAmortizationMethod().getValue();
boolean flexibleRepaymentSchedule = loan.isFlexibleRepaymentSchedule();
// use annual percentage rate to re-calculate loan schedule for late disbursement
Number interestRatePerPeriod = interestRatePerYear;
Integer interestRateFrequencyMethod = PeriodFrequencyType.YEARS.getValue();
Integer interestMethod = InterestMethod.DECLINING_BALANCE.getValue();
boolean interestRebateAllowed = false;
Integer interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod().getValue();
Integer interestCalculationInPeriod = loan.getLoanRepaymentScheduleDetail().getInterestCalculationPeriodMethod().getValue();
boolean interestRebateAllowed = loan.getLoanRepaymentScheduleDetail().isInterestRebateAllowed();
CalculateLoanScheduleCommand calculateCommand = new CalculateLoanScheduleCommand(currencyCode, currencyDigits, principalAsDecimal,
interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, repaidEvery, selectedRepaymentFrequency, numberOfInstallments,
interestRatePerPeriod, interestRateFrequencyMethod, interestMethod, interestCalculationInPeriod,
repaidEvery, selectedRepaymentFrequency, numberOfInstallments,
selectedRepaymentSchedule, flexibleRepaymentSchedule, interestRebateAllowed, actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand);
@@ -951,8 +945,7 @@ public class WritePlatformServiceJpaRepositoryImpl implements
interest.getAmount());
modifiedLoanRepaymentSchedule.add(installment);
}
loan.disburseWithModifiedRepaymentSchedule(disbursedOn, comment,
modifiedLoanRepaymentSchedule, defaultLoanLifecycleStateMachine());
loan.disburseWithModifiedRepaymentSchedule(disbursedOn, comment, modifiedLoanRepaymentSchedule, defaultLoanLifecycleStateMachine());
} else {
loan.disburse(disbursedOn, defaultLoanLifecycleStateMachine());
}
@@ -0,0 +1,32 @@
package org.mifosng.platform.loan.domain;
public enum InterestCalculationPeriodMethod {
DAILY(0), SAME_AS_REPAYMENT_PERIOD(1), INVALID(2);
private final Integer value;
private InterestCalculationPeriodMethod(final Integer value) {
this.value = value;
}
public Integer getValue() {
return this.value;
}
public static InterestCalculationPeriodMethod fromInt(final Integer selectedMethod) {
InterestCalculationPeriodMethod repaymentMethod = null;
switch (selectedMethod) {
case 0:
repaymentMethod = InterestCalculationPeriodMethod.DAILY;
break;
case 1:
repaymentMethod = InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD;
break;
default:
repaymentMethod = InterestCalculationPeriodMethod.INVALID;
break;
}
return repaymentMethod;
}
}
@@ -50,7 +50,8 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
}
public LoanProduct(final Organisation organisation, final String name, final String description, final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType, final BigDecimal defaultAnnualNominalInterestRate,
final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod,
final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments, final AmortizationMethod amortizationMethod,
final BigDecimal inArrearsTolerance,
final boolean flexibleRepaymentSchedule,
@@ -64,7 +65,8 @@ public class LoanProduct extends AbstractAuditableCustom<AppUser, Long> {
}
this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency,
defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod,
defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance, flexibleRepaymentSchedule, interestRebateAllowed);
}
@@ -39,6 +39,10 @@ public class LoanProductRelatedDetail implements
@Enumerated(EnumType.ORDINAL)
@Column(name = "interest_method_enum", nullable = false)
private InterestMethod interestMethod;
@Enumerated(EnumType.ORDINAL)
@Column(name = "interest_calculated_in_period_enum", nullable = false)
private InterestCalculationPeriodMethod interestCalculationPeriodMethod;
@Column(name = "repay_every", nullable = false)
private Integer repayEvery;
@@ -85,7 +89,9 @@ public class LoanProductRelatedDetail implements
final BigDecimal defaultNominalInterestRatePerPeriod,
final PeriodFrequencyType interestPeriodFrequencyType,
final BigDecimal defaultAnnualNominalInterestRate,
final InterestMethod interestMethod, final Integer repayEvery,
final InterestMethod interestMethod,
final InterestCalculationPeriodMethod interestCalculationPeriodMethod,
final Integer repayEvery,
final PeriodFrequencyType repaymentFrequencyType,
final Integer defaultNumberOfRepayments,
final AmortizationMethod amortizationMethod,
@@ -98,6 +104,7 @@ public class LoanProductRelatedDetail implements
this.interestPeriodFrequencyType = interestPeriodFrequencyType;
this.annualNominalInterestRate = defaultAnnualNominalInterestRate;
this.interestMethod = interestMethod;
this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
this.repayEvery = repayEvery;
this.repaymentPeriodFrequencyType = repaymentFrequencyType;
this.numberOfRepayments = defaultNumberOfRepayments;
@@ -135,6 +142,10 @@ public class LoanProductRelatedDetail implements
return interestMethod;
}
public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() {
return interestCalculationPeriodMethod;
}
@Override
public Integer getRepayEvery() {
return repayEvery;
@@ -175,5 +186,6 @@ public class LoanProductRelatedDetail implements
this.nominalInterestRatePerPeriod = command.getInterestRatePerPeriod();
this.interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyMethod());
this.interestMethod = InterestMethod.fromInt(command.getInterestMethod());
this.interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodMethod());
}
}
@@ -0,0 +1,32 @@
package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal;
import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.springframework.stereotype.Component;
@Component
public class AprCalculator {
public BigDecimal calculateFrom(PeriodFrequencyType interestPeriodFrequencyType, BigDecimal interestRatePerPeriod) {
BigDecimal defaultAnnualNominalInterestRate = BigDecimal.ZERO;
switch (interestPeriodFrequencyType) {
case DAYS:
break;
case WEEKS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(52));
break;
case MONTHS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(12));
break;
case YEARS:
defaultAnnualNominalInterestRate = interestRatePerPeriod.multiply(BigDecimal.valueOf(1));
break;
case INVALID:
break;
}
return defaultAnnualNominalInterestRate;
}
}
@@ -1,107 +0,0 @@
package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosng.data.CurrencyData;
import org.mifosng.data.LoanSchedule;
import org.mifosng.data.MoneyData;
import org.mifosng.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.NoAuthorizationException;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public class DecliningBalanceLoanScheduleGenerator implements
LoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
@Override
public LoanSchedule generate(
final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate, final LocalDate firstRepaymentDate,
LocalDate interestCalculatedFrom, CurrencyData currencyData) {
if (firstRepaymentDate != null
&& disbursementDate.isAfter(firstRepaymentDate)) {
throw new NoAuthorizationException(
"cannot have disbursement date after first repayment date.");
}
List<ScheduledLoanInstallment> scheduledLoanInstallments = new ArrayList<ScheduledLoanInstallment>();
List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(
loanScheduleInfo, disbursementDate, firstRepaymentDate);
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Money principalPerInstallment = loanScheduleInfo.getPrincipal()
.dividedBy(loanScheduleInfo.getNumberOfRepayments(),
RoundingMode.HALF_EVEN);
BigDecimal paymentPeriodsInYear = BigDecimal
.valueOf(this.paymentPeriodsInOneYearCalculator
.calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
BigDecimal periodicInterestRate = loanScheduleInfo
.getAnnualNominalInterestRate()
.divide(paymentPeriodsInYear, mc)
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
.multiply(BigDecimal.valueOf(loanScheduleInfo.getRepayEvery()));
Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(outstandingBalance.getCurrency());
LocalDate startDate = disbursementDate;
int installmentNumber = 1;
for (LocalDate scheduledDueDate : scheduledDates) {
totalPrincipal = totalPrincipal.plus(principalPerInstallment);
Money interestPerInstallment = outstandingBalance
.multiplyRetainScale(periodicInterestRate,
RoundingMode.HALF_EVEN);
if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) {
Money difference = totalPrincipal.minus(loanScheduleInfo
.getPrincipal());
if (difference.isLessThanZero()) {
principalPerInstallment = principalPerInstallment
.plus(difference.abs());
} else if (difference.isGreaterThanZero()) {
principalPerInstallment = principalPerInstallment
.minus(difference.abs());
}
}
Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment);
outstandingBalance = outstandingBalance
.minus(principalPerInstallment);
MoneyData principalPerInstallmentValue = MoneyData.of(currencyData, principalPerInstallment.getAmount());
MoneyData interestPerInstallmentValue = MoneyData.of(currencyData, interestPerInstallment.getAmount());
MoneyData totalInstallmentDueValue = MoneyData.of(currencyData, totalInstallmentDue.getAmount());
MoneyData outstandingBalanceValue = MoneyData.of(currencyData, outstandingBalance.getAmount());
ScheduledLoanInstallment installment = new ScheduledLoanInstallment(
Integer.valueOf(installmentNumber), startDate,
scheduledDueDate, principalPerInstallmentValue,
interestPerInstallmentValue,
totalInstallmentDueValue,
outstandingBalanceValue);
scheduledLoanInstallments.add(installment);
startDate = scheduledDueDate;
installmentNumber++;
}
return new LoanSchedule(scheduledLoanInstallments);
}
}
@@ -1,11 +1,10 @@
package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosng.data.CurrencyData;
import org.mifosng.data.LoanSchedule;
@@ -13,14 +12,17 @@ import org.mifosng.data.MoneyData;
import org.mifosng.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public class DecliningBalanceEqualInstallmentsLoanScheduleGenerator implements
public class DecliningBalanceMethodLoanScheduleGenerator implements
LoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
private final PmtCalculator pmtCalculator = new PmtCalculator();
@Override
public LoanSchedule generate(
final LoanProductRelatedDetail loanScheduleInfo,
@@ -30,93 +32,76 @@ public class DecliningBalanceEqualInstallmentsLoanScheduleGenerator implements
List<ScheduledLoanInstallment> scheduledLoanInstallments = new ArrayList<ScheduledLoanInstallment>();
List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(
loanScheduleInfo, disbursementDate, firstRepaymentDate);
// 1. generate valid set of 'due dates' based on some of the 'loan attributes'
List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate);
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(loanScheduleInfo, scheduledDates);
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
BigDecimal paymentPeriodsInYear = BigDecimal
.valueOf(this.paymentPeriodsInOneYearCalculator.calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
BigDecimal periodicInterestRate = loanScheduleInfo
.getAnnualNominalInterestRate()
.divide(paymentPeriodsInYear, mc)
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
.multiply(BigDecimal.valueOf(loanScheduleInfo.getRepayEvery()));
double interestRateFraction = periodicInterestRate.doubleValue();
double futureValue = 0;
double numberOfPeriods = loanScheduleInfo.getNumberOfRepayments().doubleValue();
double principal = loanScheduleInfo.getPrincipal().getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principal, futureValue, false);
double totalRepayment = paymentPerInstallment * numberOfPeriods;
// 2. determine the 'periodic' interest rate based on the 'repayment periods' so we can use
final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo);
// 3. determine 'total payment' for each repayment based on pmt function (and hence the total due overall)
final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
Money totalDuePerInstallment = Money.of(monetaryCurrency, BigDecimal.valueOf(paymentPerInstallment));
Money totalDue = Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
final Money totalDuePerInstallment = this.pmtCalculator.calculatePaymentForOnePeriodFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
final Money totalDue = this.pmtCalculator.calculateTotalRepaymentFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
Money totalInterestDue = totalDue.minus(loanScheduleInfo.getPrincipal());
Money outstandingBalance = loanScheduleInfo.getPrincipal();
Money totalPrincipal = Money.zero(monetaryCurrency);
Money totalInterest = Money.zero(monetaryCurrency);
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(loanScheduleInfo, scheduledDates);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator.calculateRepaymentPeriodAsAFractionOfDays(loanScheduleInfo.getRepaymentPeriodFrequencyType(),
loanScheduleInfo.getRepayEvery(), interestCalculatedFrom, scheduledDates, idealDisbursementDateBasedOnFirstRepaymentDate);
LocalDate startDate = disbursementDate;
int installmentNumber = 1;
for (LocalDate scheduledDueDate : scheduledDates) {
Money interestPerInstallment = outstandingBalance.multiplyRetainScale(periodicInterestRate, RoundingMode.HALF_EVEN);
Money principalPerInstallment = totalDuePerInstallment.minus(interestPerInstallment);
// number of days from startDate to this scheduledDate
int daysInPeriod = Days.daysBetween(startDate.toDateMidnight().toDateTime(), scheduledDueDate.toDateMidnight().toDateTime()).getDays();
Money interestForInstallment = this.periodicInterestRateCalculator.calculateInterestOn(outstandingBalance, periodInterestRateForRepaymentPeriod, daysInPeriod, loanScheduleInfo);
Money principalForInstallment = this.periodicInterestRateCalculator.calculatePrincipalOn(totalDuePerInstallment, interestForInstallment, loanScheduleInfo);
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
Money graceOnInterestForRepaymentPeriod = interestPerInstallment;
interestPerInstallment = interestPerInstallment.minus(graceOnInterestForRepaymentPeriod);
Money graceOnInterestForRepaymentPeriod = interestForInstallment;
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
totalInterestDue = totalInterestDue.minus(graceOnInterestForRepaymentPeriod);
interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction - Integer.valueOf(1).doubleValue();
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25") && interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
Money graceOnInterestForRepaymentPeriod = interestPerInstallment.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
interestPerInstallment = interestPerInstallment.minus(graceOnInterestForRepaymentPeriod);
Money graceOnInterestForRepaymentPeriod = interestForInstallment.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
totalInterestDue = totalInterestDue.minus(graceOnInterestForRepaymentPeriod);
interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf("0");
}
totalPrincipal = totalPrincipal.plus(principalPerInstallment);
totalInterest = totalInterest.plus(interestPerInstallment);
totalPrincipal = totalPrincipal.plus(principalForInstallment);
totalInterest = totalInterest.plus(interestForInstallment);
if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) {
Money principalDifference = totalPrincipal.minus(loanScheduleInfo
.getPrincipal());
Money principalDifference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
if (principalDifference.isLessThanZero()) {
principalPerInstallment = principalPerInstallment
.plus(principalDifference.abs());
principalForInstallment = principalForInstallment.plus(principalDifference.abs());
} else if (principalDifference.isGreaterThanZero()) {
principalPerInstallment = principalPerInstallment
.minus(principalDifference.abs());
principalForInstallment = principalForInstallment.minus(principalDifference.abs());
}
Money interestDifference = totalInterest.minus(totalInterestDue);
if (interestDifference.isLessThanZero()) {
interestPerInstallment = interestPerInstallment
.plus(interestDifference.abs());
} else if (interestDifference.isGreaterThanZero()) {
interestPerInstallment = interestPerInstallment
.minus(interestDifference.abs());
if (loanScheduleInfo.getAmortizationMethod().equals(
AmortizationMethod.EQUAL_INSTALLMENTS)) {
Money interestDifference = totalInterest.minus(totalInterestDue);
if (interestDifference.isLessThanZero()) {
interestForInstallment = interestForInstallment.plus(interestDifference.abs());
} else if (interestDifference.isGreaterThanZero()) {
interestForInstallment = interestForInstallment.minus(interestDifference.abs());
}
}
}
Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment);
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
outstandingBalance = outstandingBalance.minus(principalPerInstallment);
outstandingBalance = outstandingBalance.minus(principalForInstallment);
MoneyData principalPerInstallmentValue = MoneyData.of(currencyData, principalPerInstallment.getAmount());
MoneyData interestPerInstallmentValue = MoneyData.of(currencyData, interestPerInstallment.getAmount());
MoneyData principalPerInstallmentValue = MoneyData.of(currencyData, principalForInstallment.getAmount());
MoneyData interestPerInstallmentValue = MoneyData.of(currencyData, interestForInstallment.getAmount());
MoneyData totalInstallmentDueValue = MoneyData.of(currencyData, totalInstallmentDue.getAmount());
MoneyData outstandingBalanceValue = MoneyData.of(currencyData, outstandingBalance.getAmount());
@@ -1,46 +1,26 @@
package org.mifosng.platform.loanschedule.domain;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.InterestMethod;
public class DefaultLoanScheduleGeneratorFactory implements
LoanScheduleGeneratorFactory {
@Override
public LoanScheduleGenerator create(final InterestMethod interestMethod,
AmortizationMethod amortizationMethod) {
public LoanScheduleGenerator create(final InterestMethod interestMethod) {
LoanScheduleGenerator loanScheduleGenerator = null;
switch (amortizationMethod) {
case EQUAL_PRINCIPAL:
switch (interestMethod) {
case FLAT:
loanScheduleGenerator = new FlatLoanScheduleGenerator();
break;
case DECLINING_BALANCE:
loanScheduleGenerator = new DecliningBalanceLoanScheduleGenerator();
break;
case INVALID:
break;
}
switch (interestMethod) {
case FLAT:
loanScheduleGenerator = new FlatLoanScheduleGenerator();
break;
case EQUAL_INSTALLMENTS:
switch (interestMethod) {
case FLAT:
loanScheduleGenerator = new FlatLoanScheduleGenerator();
break;
case DECLINING_BALANCE:
loanScheduleGenerator = new DecliningBalanceEqualInstallmentsLoanScheduleGenerator();
break;
case INVALID:
break;
}
case DECLINING_BALANCE:
loanScheduleGenerator = new DecliningBalanceMethodLoanScheduleGenerator();
break;
case INVALID:
break;
}
return loanScheduleGenerator;
}
}
@@ -1,10 +1,9 @@
package org.mifosng.platform.loanschedule.domain;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.InterestMethod;
public interface LoanScheduleGeneratorFactory {
LoanScheduleGenerator create(InterestMethod interestMethod, AmortizationMethod amortizationMethod);
LoanScheduleGenerator create(InterestMethod interestMethod);
}
@@ -0,0 +1,67 @@
package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public class PeriodicInterestRateCalculator {
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
public BigDecimal calculateFrom(LoanProductRelatedDetail loanScheduleInfo) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Integer paymentPeriodsInOneYear = this.paymentPeriodsInOneYearCalculator.calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType());
BigDecimal divisor = BigDecimal.valueOf(paymentPeriodsInOneYear * 100);
BigDecimal numberOfPeriods = BigDecimal.valueOf(loanScheduleInfo.getRepayEvery());
return loanScheduleInfo.getAnnualNominalInterestRate().divide(divisor, mc).multiply(numberOfPeriods);
}
public Money calculateInterestOn(Money outstandingBalance, BigDecimal periodInterestRateForRepaymentPeriod,
int daysInPeriod,
LoanProductRelatedDetail loanScheduleInfo) {
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
Money interestDue = Money.zero(outstandingBalance.getCurrency());
switch (loanScheduleInfo.getInterestCalculationPeriodMethod()) {
case DAILY:
BigDecimal dailyInterestRate = loanScheduleInfo.getAnnualNominalInterestRate()
.divide(BigDecimal.valueOf(Long.valueOf(365)), mc)
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
.multiply(BigDecimal.valueOf(loanScheduleInfo.getRepayEvery()));
BigDecimal equivalentInterestRateForPeriod = dailyInterestRate.multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod)));
interestDue = outstandingBalance.multiplyRetainScale(equivalentInterestRateForPeriod, RoundingMode.HALF_EVEN);
break;
default:
interestDue = outstandingBalance.multiplyRetainScale(periodInterestRateForRepaymentPeriod, RoundingMode.HALF_EVEN);
break;
}
return interestDue;
}
public Money calculatePrincipalOn(Money totalDuePerInstallment, Money interestForInstallment, LoanProductRelatedDetail loanScheduleInfo) {
Money principalDue = Money.zero(totalDuePerInstallment.getCurrency());
switch (loanScheduleInfo.getAmortizationMethod()) {
case EQUAL_PRINCIPAL:
principalDue = loanScheduleInfo.getPrincipal().dividedBy(loanScheduleInfo.getNumberOfRepayments(), RoundingMode.HALF_EVEN);
break;
case EQUAL_INSTALLMENTS:
principalDue = totalDuePerInstallment.minus(interestForInstallment);
break;
}
return principalDue;
}
}
@@ -0,0 +1,72 @@
package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public class PmtCalculator {
public Money calculatePaymentForOnePeriodFrom(LoanProductRelatedDetail loanScheduleInfo, BigDecimal periodInterestRateForRepaymentPeriod, MonetaryCurrency monetaryCurrency) {
// TODO - check if going to doubles is a problem
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
double futureValue = 0;
double numberOfPeriods = loanScheduleInfo.getNumberOfRepayments().doubleValue();
double principal = loanScheduleInfo.getPrincipal().getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
// work out the total payment (principal + interest components) for each installment due.
// use the period type and interest as expressed e.g. 2% per month or 24% per year even is 'daily' interest calculation is selected
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principal, futureValue, false);
return Money.of(monetaryCurrency, BigDecimal.valueOf(paymentPerInstallment));
}
public Money calculateTotalRepaymentFrom(LoanProductRelatedDetail loanScheduleInfo, BigDecimal periodInterestRateForRepaymentPeriod, MonetaryCurrency monetaryCurrency) {
// TODO - check if going to doubles is a problem
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
double futureValue = 0;
double numberOfPeriods = loanScheduleInfo.getNumberOfRepayments().doubleValue();
double principal = loanScheduleInfo.getPrincipal().getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
// work out the total payment (principal + interest components) for each installment due.
// use the period type and interest as expressed e.g. 2% per month or 24% per year even is 'daily' interest calculation is selected
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principal, futureValue, false);
double totalRepayment = paymentPerInstallment * loanScheduleInfo.getNumberOfRepayments();
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
}
/**
* PMT calculates a fixed monthly payment to be paid by borrower every 'period' to ensure loan is paid off in full (with interest).
*
* This monthly payment c depends upon
* the monthly interest rate r (expressed as a fraction, not a percentage,
* i.e., divide the quoted yearly percentage rate by 100 and by 12 to obtain
* the monthly interest rate), the number of monthly payments N called the
* loan's term, and the amount borrowed P known as the loan's principal; c
* is given by the formula:
*
* c = (r / (1 - (1 + r)^-N))P
*
* @param interestRateFraction
* @param numberOfPayments
* @param principal
* @param futureValue
* @param type
*/
private static double pmt(double interestRateFraction, double numberOfPayments, double principal, double futureValue, boolean type) {
double payment = 0;
if (interestRateFraction == 0) {
payment = -1 * (futureValue + principal) / numberOfPayments;
} else {
double r1 = interestRateFraction + 1;
payment = (futureValue + principal * Math.pow(r1, numberOfPayments)) * interestRateFraction
/ ((type ? r1 : 1) * (1 - Math.pow(r1, numberOfPayments)));
}
return payment;
}
}