WIP on support loan charges on loan schedule.
This commit is contained in:
@@ -270,14 +270,9 @@ public class LoansApiResource {
|
||||
|
||||
LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(null, jsonRequestBody);
|
||||
|
||||
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
|
||||
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
|
||||
|
||||
// for now just auto generating the loan schedule and setting support
|
||||
// for 'manual' loan schedule creation later.
|
||||
command.setLoanSchedule(loanSchedule);
|
||||
|
||||
if (is(commandParam, "calculateLoanSchedule")) {
|
||||
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
|
||||
NewLoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(calculateLoanScheduleCommand);
|
||||
return Response.ok().entity(loanSchedule).build();
|
||||
}
|
||||
|
||||
@@ -297,14 +292,13 @@ public class LoansApiResource {
|
||||
|
||||
final LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(loanId, jsonRequestBody);
|
||||
|
||||
final CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
|
||||
final LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
|
||||
|
||||
// for now just auto generating the loan schedule and setting support
|
||||
// for 'manual' loan schedule creation later.
|
||||
command.setLoanSchedule(loanSchedule);
|
||||
// command.setLoanSchedule(loanSchedule);
|
||||
|
||||
if (is(commandParam, "calculateLoanSchedule")) {
|
||||
final CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
|
||||
final LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
|
||||
return Response.ok().entity(loanSchedule).build();
|
||||
}
|
||||
|
||||
|
||||
-11
@@ -4,7 +4,6 @@ import java.math.BigDecimal;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
|
||||
/**
|
||||
* Immutable command for submitting new loan application.
|
||||
@@ -44,8 +43,6 @@ public class LoanApplicationCommand {
|
||||
|
||||
private final LoanChargeCommand[] charges;
|
||||
|
||||
private LoanSchedule loanSchedule;
|
||||
|
||||
private final Set<String> modifiedParameters;
|
||||
|
||||
public LoanApplicationCommand(
|
||||
@@ -104,14 +101,6 @@ public class LoanApplicationCommand {
|
||||
this.loanTermFrequency, this.loanTermFrequencyType,
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate);
|
||||
}
|
||||
|
||||
public LoanSchedule getLoanSchedule() {
|
||||
return loanSchedule;
|
||||
}
|
||||
|
||||
public void setLoanSchedule(LoanSchedule loanSchedule) {
|
||||
this.loanSchedule = loanSchedule;
|
||||
}
|
||||
|
||||
public Long getLoanId() {
|
||||
return loanId;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package org.mifosng.platform.api.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LoanSchedule implements Serializable {
|
||||
public class LoanSchedule {
|
||||
|
||||
private List<ScheduledLoanInstallment> scheduledLoanInstallments = new ArrayList<ScheduledLoanInstallment>();
|
||||
|
||||
@@ -17,10 +16,6 @@ public class LoanSchedule implements Serializable {
|
||||
return new CurrencyData(code, name, decimalPlaces, displaySymbol, nameCode);
|
||||
}
|
||||
|
||||
public LoanSchedule() {
|
||||
//
|
||||
}
|
||||
|
||||
public LoanSchedule(final List<ScheduledLoanInstallment> scheduledLoanInstallments) {
|
||||
this.scheduledLoanInstallments = scheduledLoanInstallments;
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,11 +1,10 @@
|
||||
package org.mifosng.platform.api.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
public class ScheduledLoanInstallment implements Serializable {
|
||||
public class ScheduledLoanInstallment {
|
||||
|
||||
private Integer installmentNumber;
|
||||
private LocalDate periodStart;
|
||||
|
||||
+8
-1
@@ -1,6 +1,7 @@
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
|
||||
@@ -8,6 +9,12 @@ public interface CalculationPlatformService {
|
||||
|
||||
LoanSchedule calculateLoanSchedule(CalculateLoanScheduleCommand command);
|
||||
|
||||
NewLoanScheduleData calculateLoanScheduleNew(CalculateLoanScheduleCommand command);
|
||||
|
||||
/**
|
||||
* Not used at present from application.
|
||||
*
|
||||
* was a requirement from creocore that got into early prototye.
|
||||
*/
|
||||
LoanPayoffReadModel calculatePayoffOn(Long valueOf, LocalDate payoffDate);
|
||||
|
||||
}
|
||||
|
||||
+43
-5
@@ -3,6 +3,7 @@ package org.mifosng.platform.loan.service;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
@@ -32,8 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CalculationPlatformServiceImpl implements
|
||||
CalculationPlatformService {
|
||||
public class CalculationPlatformServiceImpl implements CalculationPlatformService {
|
||||
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final LoanRepository loanRepository;
|
||||
@@ -41,17 +41,54 @@ public class CalculationPlatformServiceImpl implements
|
||||
private final ApplicationCurrencyRepository applicationCurrencyRepository;
|
||||
private final AprCalculator aprCalculator;
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler;
|
||||
|
||||
@Autowired
|
||||
public CalculationPlatformServiceImpl(final PlatformSecurityContext context,
|
||||
final LoanRepository loanRepository, final LoanProductRepository loanProductRepository,
|
||||
final ApplicationCurrencyRepository applicationCurrencyRepository, final AprCalculator aprCalculator) {
|
||||
public CalculationPlatformServiceImpl(
|
||||
final PlatformSecurityContext context,
|
||||
final LoanRepository loanRepository,
|
||||
final LoanProductRepository loanProductRepository,
|
||||
final ApplicationCurrencyRepository applicationCurrencyRepository,
|
||||
final AprCalculator aprCalculator,
|
||||
final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler) {
|
||||
this.context = context;
|
||||
this.loanRepository = loanRepository;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
||||
this.aprCalculator = aprCalculator;
|
||||
this.loanScheduleFactory = new DefaultLoanScheduleGeneratorFactory();
|
||||
this.loanProductRelatedDetailAssembler = loanProductRelatedDetailAssembler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NewLoanScheduleData calculateLoanScheduleNew(final CalculateLoanScheduleCommand command) {
|
||||
|
||||
context.authenticatedUser();
|
||||
|
||||
CalculateLoanScheduleCommandValidator validator = new CalculateLoanScheduleCommandValidator(command);
|
||||
validator.validate();
|
||||
|
||||
LoanProductRelatedDetail loanScheduleRelatedDetails = this.loanProductRelatedDetailAssembler.assembleFrom(command);
|
||||
|
||||
final Integer loanTermFrequency = command.getLoanTermFrequency();
|
||||
final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType());
|
||||
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
|
||||
|
||||
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod);
|
||||
|
||||
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(loanScheduleRelatedDetails.getCurrency().getCode());
|
||||
|
||||
CurrencyData currencyData = new CurrencyData(
|
||||
applicationCurrency.getCode(),
|
||||
applicationCurrency.getName(),
|
||||
loanScheduleRelatedDetails.getCurrency().getDigitsAfterDecimal(),
|
||||
applicationCurrency.getDisplaySymbol(),
|
||||
applicationCurrency.getNameCode());
|
||||
|
||||
return loanScheduleGenerator.generate(loanScheduleRelatedDetails,
|
||||
command.getExpectedDisbursementDate(),
|
||||
command.getRepaymentsStartingFromDate(),
|
||||
command.getInterestChargedFromDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,4 +181,5 @@ public class CalculationPlatformServiceImpl implements
|
||||
totalInterestOutstandingBasedOnExpectedMaturityDate,
|
||||
totalInterestOutstandingBasedOnPayoffDate, interestRebateOwed);
|
||||
}
|
||||
|
||||
}
|
||||
+5
-5
@@ -21,11 +21,11 @@ public class LoanApplicationCommandValidator {
|
||||
public void validate() {
|
||||
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
|
||||
if (command.getLoanSchedule() == null) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.submit.loan.loan.schedule.cannot.be.blank",
|
||||
"The parameter loanSchedule cannot be empty.", "loanSchedule");
|
||||
dataValidationErrors.add(error);
|
||||
}
|
||||
// if (command.getLoanSchedule() == null) {
|
||||
// ApiParameterError error = ApiParameterError.parameterError("validation.msg.submit.loan.loan.schedule.cannot.be.blank",
|
||||
// "The parameter loanSchedule cannot be empty.", "loanSchedule");
|
||||
// dataValidationErrors.add(error);
|
||||
// }
|
||||
|
||||
if (command.getSubmittedOnDate() == null) {
|
||||
ApiParameterError error = ApiParameterError.parameterError("validation.msg.loan.submitted.on.date.cannot.be.blank",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.mifosng.platform.loan.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -8,7 +9,6 @@ import java.util.Set;
|
||||
|
||||
import org.mifosng.platform.api.commands.LoanApplicationCommand;
|
||||
import org.mifosng.platform.api.commands.LoanChargeCommand;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.api.data.MoneyData;
|
||||
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
|
||||
import org.mifosng.platform.charge.domain.Charge;
|
||||
@@ -108,8 +108,9 @@ public class LoanAssembler {
|
||||
loan.setExternalId(command.getExternalId());
|
||||
|
||||
// TODO - user service to calculate loan schedule at this point
|
||||
LoanSchedule loanSchedule = command.getLoanSchedule();
|
||||
List<ScheduledLoanInstallment> loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments();
|
||||
// LoanSchedule loanSchedule = command.getLoanSchedule();
|
||||
List<ScheduledLoanInstallment> loanRepaymentSchedule = new ArrayList<ScheduledLoanInstallment>();
|
||||
// loanSchedule.getScheduledLoanInstallments();
|
||||
|
||||
for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) {
|
||||
|
||||
|
||||
+10
@@ -4,6 +4,7 @@ import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
|
||||
@@ -16,4 +17,13 @@ public interface AmortizationLoanScheduleGenerator {
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
List<LocalDate> scheduledDates);
|
||||
|
||||
NewLoanScheduleData generate(
|
||||
LoanProductRelatedDetail loanScheduleInfo,
|
||||
LocalDate disbursementDate,
|
||||
LocalDate firstRepaymentDate,
|
||||
LocalDate interestCalculatedFrom,
|
||||
BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
List<LocalDate> scheduledDates);
|
||||
|
||||
}
|
||||
|
||||
+29
-129
@@ -4,6 +4,7 @@ import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.loan.domain.AmortizationMethod;
|
||||
@@ -33,6 +34,34 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
|
||||
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
|
||||
private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory();
|
||||
|
||||
@Override
|
||||
public NewLoanScheduleData generate(
|
||||
final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final LocalDate disbursementDate,
|
||||
final LocalDate firstRepaymentDate,
|
||||
final LocalDate interestCalculatedFrom) {
|
||||
|
||||
// 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);
|
||||
|
||||
// 2. determine the 'periodic' interest rate based on the 'repayment periods' so we can use
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo);
|
||||
|
||||
// Determine with 'amortisation' approach to use
|
||||
AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo.getAmortizationMethod());
|
||||
|
||||
return generator.generate(
|
||||
loanScheduleInfo,
|
||||
disbursementDate,
|
||||
firstRepaymentDate,
|
||||
interestCalculatedFrom,
|
||||
periodInterestRateForRepaymentPeriod,
|
||||
idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
scheduledDates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedule generate(
|
||||
final LoanProductRelatedDetail loanScheduleInfo,
|
||||
@@ -57,133 +86,4 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
|
||||
return generator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData,
|
||||
periodInterestRateForRepaymentPeriod, idealDisbursementDateBasedOnFirstRepaymentDate, scheduledDates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Future value of an amount given the number of payments, rate, amount of
|
||||
* individual payment, present value and boolean value indicating whether
|
||||
* payments are due at the beginning of period (false => payments are due at
|
||||
* end of period)
|
||||
*
|
||||
* @param r
|
||||
* rate
|
||||
* @param n
|
||||
* num of periods
|
||||
* @param y
|
||||
* pmt per period
|
||||
* @param p
|
||||
* future value
|
||||
* @param t
|
||||
* type (true=pmt at end of period, false=pmt at begining of
|
||||
* period)
|
||||
*/
|
||||
public static double fv(double r, double n, double y, double p, boolean t) {
|
||||
double retval = 0;
|
||||
if (r == 0) {
|
||||
retval = -1 * (p + (n * y));
|
||||
} else {
|
||||
double r1 = r + 1;
|
||||
retval = ((1 - Math.pow(r1, n)) * (t ? r1 : 1) * y) / r - p
|
||||
* Math.pow(r1, n);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Present value of an amount given the number of future payments, rate,
|
||||
* amount of individual payment, future value and boolean value indicating
|
||||
* whether payments are due at the beginning of period (false => payments
|
||||
* are due at end of period)
|
||||
*
|
||||
* @param r
|
||||
* @param n
|
||||
* @param y
|
||||
* @param f
|
||||
* @param t
|
||||
*/
|
||||
public static double pv(double r, double n, double y, double f, boolean t) {
|
||||
double retval = 0;
|
||||
if (r == 0) {
|
||||
retval = -1 * ((n * y) + f);
|
||||
} else {
|
||||
double r1 = r + 1;
|
||||
retval = (((1 - Math.pow(r1, n)) / r) * (t ? r1 : 1) * y - f)
|
||||
/ Math.pow(r1, n);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates the Net Present Value of a principal amount given the discount
|
||||
* rate and a sequence of cash flows (supplied as an array). If the amounts
|
||||
* are income the value should be positive, else if they are payments and
|
||||
* not income, the value should be negative.
|
||||
*
|
||||
* @param r
|
||||
* @param cfs
|
||||
* cashflow amounts
|
||||
*/
|
||||
public static double npv(double r, double[] cfs) {
|
||||
double npv = 0;
|
||||
double r1 = r + 1;
|
||||
double trate = r1;
|
||||
for (int i = 0, iSize = cfs.length; i < iSize; i++) {
|
||||
npv += cfs[i] / trate;
|
||||
trate *= r1;
|
||||
}
|
||||
return npv;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param r
|
||||
* @param y
|
||||
* @param p
|
||||
* @param f
|
||||
* @param t
|
||||
*/
|
||||
public static double nper(double r, double y, double p, double f, boolean t) {
|
||||
double retval = 0;
|
||||
if (r == 0) {
|
||||
retval = -1 * (f + p) / y;
|
||||
} else {
|
||||
double r1 = r + 1;
|
||||
double ryr = (t ? r1 : 1) * y / r;
|
||||
double a1 = ((ryr - f) < 0) ? Math.log(f - ryr) : Math.log(ryr - f);
|
||||
double a2 = ((ryr - f) < 0) ? Math.log(-p - ryr) : Math
|
||||
.log(p + ryr);
|
||||
double a3 = Math.log(r1);
|
||||
retval = (a1 - a2) / a3;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
+105
@@ -6,8 +6,10 @@ import java.util.List;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
|
||||
import org.mifosng.platform.api.data.MoneyData;
|
||||
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
|
||||
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||
@@ -23,6 +25,109 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
private final PmtCalculator pmtCalculator = new PmtCalculator();
|
||||
|
||||
@Override
|
||||
public NewLoanScheduleData generate(
|
||||
final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final LocalDate disbursementDate,
|
||||
final LocalDate firstRepaymentDate,
|
||||
final LocalDate interestCalculatedFrom,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
final List<LocalDate> scheduledDates) {
|
||||
|
||||
// 1. add disbursements to loan schedule
|
||||
|
||||
// 2.
|
||||
List<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
|
||||
|
||||
// determine 'total payment' for each repayment based on pmt function (and hence the total due overall)
|
||||
final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
|
||||
final Money totalDuePerInstallment = this.pmtCalculator.calculatePaymentForOnePeriodFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
|
||||
final Money totalRepaymentDueForLoanTerm = this.pmtCalculator.calculateTotalRepaymentFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
|
||||
|
||||
Money totalInterestDue = totalRepaymentDueForLoanTerm.minus(loanScheduleInfo.getPrincipal());
|
||||
Money outstandingBalance = loanScheduleInfo.getPrincipal();
|
||||
Money totalPrincipal = Money.zero(monetaryCurrency);
|
||||
Money totalInterest = Money.zero(monetaryCurrency);
|
||||
|
||||
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator.calculateRepaymentPeriodAsAFractionOfDays(
|
||||
loanScheduleInfo.getRepaymentPeriodFrequencyType(),
|
||||
loanScheduleInfo.getRepayEvery(),
|
||||
interestCalculatedFrom,
|
||||
scheduledDates,
|
||||
idealDisbursementDateBasedOnFirstRepaymentDate);
|
||||
|
||||
LocalDate startDate = disbursementDate;
|
||||
int installmentNumber = 1;
|
||||
for (LocalDate scheduledDueDate : scheduledDates) {
|
||||
|
||||
// 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 = 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 = interestForInstallment.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
||||
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
|
||||
totalInterestDue = totalInterestDue.minus(graceOnInterestForRepaymentPeriod);
|
||||
interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf("0");
|
||||
}
|
||||
|
||||
totalPrincipal = totalPrincipal.plus(principalForInstallment);
|
||||
totalInterest = totalInterest.plus(interestForInstallment);
|
||||
|
||||
if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) {
|
||||
Money principalDifference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
|
||||
if (principalDifference.isLessThanZero()) {
|
||||
principalForInstallment = principalForInstallment.plus(principalDifference.abs());
|
||||
} else if (principalDifference.isGreaterThanZero()) {
|
||||
principalForInstallment = principalForInstallment.minus(principalDifference.abs());
|
||||
}
|
||||
|
||||
final Money interestDifference = totalInterest.minus(totalInterestDue);
|
||||
if (interestDifference.isLessThanZero()) {
|
||||
interestForInstallment = interestForInstallment.plus(interestDifference.abs());
|
||||
} else if (interestDifference.isGreaterThanZero()) {
|
||||
interestForInstallment = interestForInstallment.minus(interestDifference.abs());
|
||||
}
|
||||
}
|
||||
|
||||
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
|
||||
|
||||
outstandingBalance = outstandingBalance.minus(principalForInstallment);
|
||||
|
||||
// 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());
|
||||
|
||||
// ScheduledLoanInstallment installment = new ScheduledLoanInstallment(
|
||||
// Integer.valueOf(installmentNumber), startDate,
|
||||
// scheduledDueDate, principalPerInstallmentValue,
|
||||
// interestPerInstallmentValue,
|
||||
// totalInstallmentDueValue,
|
||||
// outstandingBalanceValue);
|
||||
|
||||
LoanSchedulePeriodData installment = new LoanSchedulePeriodData();
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
startDate = scheduledDueDate;
|
||||
|
||||
installmentNumber++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final LocalDate disbursementDate,
|
||||
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
package org.mifosng.platform.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.api.data.MoneyData;
|
||||
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
|
||||
import org.mifosng.platform.currency.domain.MonetaryCurrency;
|
||||
import org.mifosng.platform.currency.domain.Money;
|
||||
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
|
||||
|
||||
public class EqualInstallmentsWithIrregularRepaymentScheduleAmortizationLoanScheduleGenerator implements AmortizationLoanScheduleGenerator {
|
||||
|
||||
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
private final PmtCalculator pmtCalculator = new PmtCalculator();
|
||||
|
||||
@Override
|
||||
public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final LocalDate disbursementDate,
|
||||
final LocalDate firstRepaymentDate,
|
||||
final LocalDate interestCalculatedFrom,
|
||||
final CurrencyData currencyData,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
final List<LocalDate> scheduledDates) {
|
||||
|
||||
List<ScheduledLoanInstallment> scheduledLoanInstallments = new ArrayList<ScheduledLoanInstallment>();
|
||||
|
||||
// 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 = this.pmtCalculator.calculatePaymentForOnePeriodFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
|
||||
final Money totalRepaymentDueForLoanTerm = this.pmtCalculator.calculateTotalRepaymentFrom(loanScheduleInfo, periodInterestRateForRepaymentPeriod, monetaryCurrency);
|
||||
|
||||
// REVIEW. calculate total interest due based on loan term settings rather than inferring loan term from regular repayment schedule
|
||||
// final BigDecimal periodInterestRateForLoanTermPeriod = this.periodicInterestRateCalculator.calculateFrom(loanTermFrequencyType, loanScheduleInfo.getAnnualNominalInterestRate());
|
||||
// final Money totalRepaymentDueForLoanTerm = this.pmtCalculator.calculateTotalRepaymentFrom(loanScheduleInfo.getPrincipal(), loanTermFrequency, periodInterestRateForLoanTermPeriod, monetaryCurrency);
|
||||
|
||||
// totalDuePerInstallment = totalRepaymentDueForLoanTerm.dividedBy(loanScheduleInfo.getNumberOfRepayments(), RoundingMode.HALF_EVEN);
|
||||
|
||||
Money totalInterestDue = totalRepaymentDueForLoanTerm.minus(loanScheduleInfo.getPrincipal());
|
||||
Money outstandingBalance = loanScheduleInfo.getPrincipal();
|
||||
Money totalPrincipal = Money.zero(monetaryCurrency);
|
||||
Money totalInterest = Money.zero(monetaryCurrency);
|
||||
|
||||
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator.calculateRepaymentPeriodAsAFractionOfDays(loanScheduleInfo.getRepaymentPeriodFrequencyType(),
|
||||
loanScheduleInfo.getRepayEvery(), interestCalculatedFrom, scheduledDates, idealDisbursementDateBasedOnFirstRepaymentDate);
|
||||
|
||||
LocalDate startDate = disbursementDate;
|
||||
int installmentNumber = 1;
|
||||
for (LocalDate scheduledDueDate : scheduledDates) {
|
||||
|
||||
// number of days from startDate to this scheduledDate
|
||||
int daysInPeriod = Days.daysBetween(startDate.toDateMidnight().toDateTime(), scheduledDueDate.toDateMidnight().toDateTime()).getDays();
|
||||
|
||||
// TODO - total interest for loan is calculated using loan term settings (loanTermFrequency & loanTermFrequencyType)
|
||||
// - its possible that the 'repyament schedule' is irregular in which case the interest for the installment might not be the periodic interest rate
|
||||
// - but infact somethink like twice it e.g. 4 month installment followed by 2 month installment followed by 1 month installment
|
||||
// - the below doesnt really support highly irregular loan schedules and this case needs to be looked at.
|
||||
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 = 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 = interestForInstallment.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
||||
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
|
||||
totalInterestDue = totalInterestDue.minus(graceOnInterestForRepaymentPeriod);
|
||||
interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf("0");
|
||||
}
|
||||
|
||||
totalPrincipal = totalPrincipal.plus(principalForInstallment);
|
||||
totalInterest = totalInterest.plus(interestForInstallment);
|
||||
|
||||
if (installmentNumber == loanScheduleInfo.getNumberOfRepayments()) {
|
||||
Money principalDifference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
|
||||
if (principalDifference.isLessThanZero()) {
|
||||
principalForInstallment = principalForInstallment.plus(principalDifference.abs());
|
||||
} else if (principalDifference.isGreaterThanZero()) {
|
||||
principalForInstallment = principalForInstallment.minus(principalDifference.abs());
|
||||
}
|
||||
|
||||
final Money interestDifference = totalInterest.minus(totalInterestDue);
|
||||
if (interestDifference.isLessThanZero()) {
|
||||
interestForInstallment = interestForInstallment.plus(interestDifference.abs());
|
||||
} else if (interestDifference.isGreaterThanZero()) {
|
||||
interestForInstallment = interestForInstallment.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 = principalForInstallment.plus(interestForInstallment);
|
||||
|
||||
outstandingBalance = outstandingBalance.minus(principalForInstallment);
|
||||
|
||||
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());
|
||||
|
||||
ScheduledLoanInstallment installment = new ScheduledLoanInstallment(
|
||||
Integer.valueOf(installmentNumber), startDate,
|
||||
scheduledDueDate, principalPerInstallmentValue,
|
||||
interestPerInstallmentValue,
|
||||
totalInstallmentDueValue,
|
||||
outstandingBalanceValue);
|
||||
|
||||
scheduledLoanInstallments.add(installment);
|
||||
|
||||
startDate = scheduledDueDate;
|
||||
|
||||
installmentNumber++;
|
||||
}
|
||||
|
||||
return new LoanSchedule(scheduledLoanInstallments);
|
||||
}
|
||||
}
|
||||
+13
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.api.data.MoneyData;
|
||||
@@ -19,6 +20,18 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
|
||||
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
@Override
|
||||
public NewLoanScheduleData generate(
|
||||
LoanProductRelatedDetail loanScheduleInfo,
|
||||
LocalDate disbursementDate, LocalDate firstRepaymentDate,
|
||||
LocalDate interestCalculatedFrom,
|
||||
BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
List<LocalDate> scheduledDates) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final LocalDate disbursementDate,
|
||||
|
||||
+10
-1
@@ -7,6 +7,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.api.data.MoneyData;
|
||||
@@ -19,7 +20,15 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
|
||||
@Override
|
||||
public NewLoanScheduleData generate(
|
||||
LoanProductRelatedDetail loanScheduleInfo,
|
||||
LocalDate disbursementDate, LocalDate firstRepaymentDate,
|
||||
LocalDate interestCalculatedFrom) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedule generate(
|
||||
final LoanProductRelatedDetail loanScheduleInfo,
|
||||
|
||||
+4
-1
@@ -1,6 +1,7 @@
|
||||
package org.mifosng.platform.loanschedule.domain;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosng.platform.api.NewLoanScheduleData;
|
||||
import org.mifosng.platform.api.data.CurrencyData;
|
||||
import org.mifosng.platform.api.data.LoanSchedule;
|
||||
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
|
||||
@@ -14,4 +15,6 @@ public interface LoanScheduleGenerator {
|
||||
LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom,
|
||||
CurrencyData currencyData);
|
||||
|
||||
}
|
||||
NewLoanScheduleData generate(LoanProductRelatedDetail loanScheduleInfo, LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom);
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.mifosng.platform.loanschedule.domain;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PmtTest {
|
||||
|
||||
@Test
|
||||
public void shouldCalculatePmt() {
|
||||
|
||||
double interestRateFraction = Double.valueOf("0.0175");
|
||||
|
||||
double numberOfPayments = Double.valueOf("12");
|
||||
double principal = Double.valueOf("-144300");
|
||||
double futureValue = Double.valueOf("0");
|
||||
boolean type = false;
|
||||
|
||||
double payment = DecliningBalanceMethodLoanScheduleGenerator.pmt(interestRateFraction, numberOfPayments, principal, futureValue, type);
|
||||
|
||||
assertThat(payment, is(Double.valueOf("13436.317553939027")));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user