diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java index ece790d28..d4da97522 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoansApiResource.java @@ -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(); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanApplicationCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanApplicationCommand.java index 2cb679212..cb5fccefd 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanApplicationCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/LoanApplicationCommand.java @@ -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 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; diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanSchedule.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanSchedule.java index 3557ee652..9a4bbb0e8 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanSchedule.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanSchedule.java @@ -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 scheduledLoanInstallments = new ArrayList(); @@ -17,10 +16,6 @@ public class LoanSchedule implements Serializable { return new CurrencyData(code, name, decimalPlaces, displaySymbol, nameCode); } - public LoanSchedule() { - // - } - public LoanSchedule(final List scheduledLoanInstallments) { this.scheduledLoanInstallments = scheduledLoanInstallments; } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ScheduledLoanInstallment.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ScheduledLoanInstallment.java index 277721ceb..ab4e54396 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ScheduledLoanInstallment.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ScheduledLoanInstallment.java @@ -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; diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformService.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformService.java index d2d75845f..569466174 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformService.java @@ -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); - } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformServiceImpl.java index 4f316d081..155018f11 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/CalculationPlatformServiceImpl.java @@ -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); } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java index df7ad2131..5abd22dc0 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanApplicationCommandValidator.java @@ -21,11 +21,11 @@ public class LoanApplicationCommandValidator { public void validate() { List dataValidationErrors = new ArrayList(); - 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", diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java index ca4467580..6f9d5a43e 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanAssembler.java @@ -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 loanRepaymentSchedule = loanSchedule.getScheduledLoanInstallments(); +// LoanSchedule loanSchedule = command.getLoanSchedule(); + List loanRepaymentSchedule = new ArrayList(); +// loanSchedule.getScheduledLoanInstallments(); for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/AmortizationLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/AmortizationLoanScheduleGenerator.java index ed45f7a43..036f6cc31 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/AmortizationLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/AmortizationLoanScheduleGenerator.java @@ -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 scheduledDates); + NewLoanScheduleData generate( + LoanProductRelatedDetail loanScheduleInfo, + LocalDate disbursementDate, + LocalDate firstRepaymentDate, + LocalDate interestCalculatedFrom, + BigDecimal periodInterestRateForRepaymentPeriod, + LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, + List scheduledDates); + } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java index a336c9f0d..046ff1609 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/DecliningBalanceMethodLoanScheduleGenerator.java @@ -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 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; - } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualInstallmentsAmortizationLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualInstallmentsAmortizationLoanScheduleGenerator.java index 2b4203738..849923626 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualInstallmentsAmortizationLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualInstallmentsAmortizationLoanScheduleGenerator.java @@ -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 scheduledDates) { + + // 1. add disbursements to loan schedule + + // 2. + List periods = new ArrayList(); + + // 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, diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualInstallmentsWithIrregularRepaymentScheduleAmortizationLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualInstallmentsWithIrregularRepaymentScheduleAmortizationLoanScheduleGenerator.java deleted file mode 100644 index 500e6bfc7..000000000 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualInstallmentsWithIrregularRepaymentScheduleAmortizationLoanScheduleGenerator.java +++ /dev/null @@ -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 scheduledDates) { - - List scheduledLoanInstallments = new ArrayList(); - - // 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); - } -} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualPrincipalAmortizationLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualPrincipalAmortizationLoanScheduleGenerator.java index fc2a32c0f..f179ef084 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualPrincipalAmortizationLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/EqualPrincipalAmortizationLoanScheduleGenerator.java @@ -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 scheduledDates) { + // TODO Auto-generated method stub + return null; + } + @Override public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo, final LocalDate disbursementDate, diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/FlatMethodLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/FlatMethodLoanScheduleGenerator.java index a87f1155c..3636ca5af 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/FlatMethodLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/FlatMethodLoanScheduleGenerator.java @@ -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, diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/LoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/LoanScheduleGenerator.java index 6259caa9c..c1439e7aa 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/LoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loanschedule/domain/LoanScheduleGenerator.java @@ -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); + +} \ No newline at end of file diff --git a/mifosng-provider/src/test/java/org/mifosng/platform/loanschedule/domain/PmtTest.java b/mifosng-provider/src/test/java/org/mifosng/platform/loanschedule/domain/PmtTest.java deleted file mode 100644 index ec8569532..000000000 --- a/mifosng-provider/src/test/java/org/mifosng/platform/loanschedule/domain/PmtTest.java +++ /dev/null @@ -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"))); - } -}