squash changes in interest-calculation-extension branch
This commit is contained in:
+38
-58
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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(),
|
||||
|
||||
+26
-33
@@ -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());
|
||||
}
|
||||
|
||||
+32
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
+32
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
-107
@@ -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);
|
||||
}
|
||||
}
|
||||
+42
-57
@@ -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());
|
||||
|
||||
+7
-27
@@ -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
-2
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+67
@@ -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;
|
||||
}
|
||||
}
|
||||
+72
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user