From 392be3745cd2973ca14618036659ff57c0f2e86e Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Wed, 15 Aug 2012 14:17:00 +0100 Subject: [PATCH] calculate future value of fixed term deposit on opening or creation of deposit account.2 --- .../saving/domain/DepositAccount.java | 20 ++++-- .../FixedTermDepositInterestCalculator.java | 66 +++++++++++++++++++ .../service/DepositAccountAssembler.java | 10 +-- 3 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java index 0e94f0677..868632de3 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/DepositAccount.java @@ -118,14 +118,19 @@ public class DepositAccount extends AbstractAuditableCustom { public static DepositAccount openNew( final Client client, final DepositProduct product, - final String externalId, final Money deposit, final BigDecimal interestRate, final Integer termInMonths, + final String externalId, final Money deposit, final BigDecimal maturityInterestRate, final Integer tenureInMonths, final Integer interestCompoundedEvery, final PeriodFrequencyType interestCompoundedFrequencyPeriodType, final LocalDate commencementDate, final boolean renewalAllowed, - final boolean preClosureAllowed) { - return new DepositAccount(client, product, externalId, deposit, interestRate, termInMonths, - interestCompoundedEvery, interestCompoundedFrequencyPeriodType, commencementDate, renewalAllowed, preClosureAllowed); + final boolean preClosureAllowed, + final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator) { + + Money futureValueOnMaturity = fixedTermDepositInterestCalculator.calculateInterestOnMaturityFor(deposit, tenureInMonths, + maturityInterestRate, interestCompoundedEvery, interestCompoundedFrequencyPeriodType); + + return new DepositAccount(client, product, externalId, deposit, maturityInterestRate, tenureInMonths, + interestCompoundedEvery, interestCompoundedFrequencyPeriodType, commencementDate, renewalAllowed, preClosureAllowed, futureValueOnMaturity); } protected DepositAccount() { @@ -139,7 +144,8 @@ public class DepositAccount extends AbstractAuditableCustom { final PeriodFrequencyType interestCompoundedFrequencyPeriodType, final LocalDate commencementDate, final boolean renewalAllowed, - final boolean preClosureAllowed) { + final boolean preClosureAllowed, + final Money futureValueOnMaturity) { this.client = client; this.product = product; this.externalId = externalId; @@ -163,8 +169,8 @@ public class DepositAccount extends AbstractAuditableCustom { this.preClosureInterestRate = BigDecimal.ZERO; // derived fields - this.projectedInterestAccruedOnMaturity = BigDecimal.ZERO; - this.projectedTotalOnMaturity = BigDecimal.ZERO; + this.projectedInterestAccruedOnMaturity = futureValueOnMaturity.minus(deposit).getAmount(); + this.projectedTotalOnMaturity = futureValueOnMaturity.getAmount(); } public boolean isDeleted() { diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java new file mode 100644 index 000000000..96dbf98df --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/domain/FixedTermDepositInterestCalculator.java @@ -0,0 +1,66 @@ +package org.mifosng.platform.saving.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.PeriodFrequencyType; +import org.springframework.stereotype.Service; + +@Service +public class FixedTermDepositInterestCalculator { + + public Money calculateInterestOnMaturityFor( + final Money deposit, + final Integer tenureInMonths, + final BigDecimal maturityInterestRate, + final Integer interestCompoundedEvery, + final PeriodFrequencyType interestCompoundedFrequencyPeriodType) { + + // TODO - Can change 'interest compounding' fields into something simpler like interestCompoundedFrequency {None (Simple Interest), Monthly, Quarterly, Half-Yearly, Annually} + // - the 'interestCompoundedEvery' would then be inferred form the selected type. + // - the special case of 'simple interest' or 'no compounding' would use simple formula rather than 'future value' function. + + // FIXME - assuming only monthly compounding period for now + MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN); + Integer monthsInYear = 12; + + BigDecimal interestRateAsFraction = maturityInterestRate.divide(BigDecimal.valueOf(100), mc); + BigDecimal interestRateForOneMonth = interestRateAsFraction.divide(BigDecimal.valueOf(monthsInYear.doubleValue()), mc); + + BigDecimal ratePerCompoundingPeriod = interestRateForOneMonth.multiply(BigDecimal.valueOf(interestCompoundedEvery.doubleValue()), mc); + + Integer numberOfPeriods = tenureInMonths / interestCompoundedEvery; + + return fv(ratePerCompoundingPeriod, numberOfPeriods, deposit); + } + + private static Money fv(final BigDecimal ratePerCompoundingPeriod, final Integer numberOfPeriods, final Money presentValue) { + + double pmtPerPeriod = Double.valueOf("0.0"); + double pv = presentValue.negated().getAmount().doubleValue(); + + double futureValue = fv(ratePerCompoundingPeriod.doubleValue(), numberOfPeriods.doubleValue(), pmtPerPeriod, pv, false); + + return Money.of(presentValue.getCurrency(), BigDecimal.valueOf(futureValue)); + } + + /** + * 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) + */ + private static double fv(double ratePeriodCompoundingPeriod, double numberOfCompoundingPeriods, double pmtPerPeriod, double presentValue, boolean type) { + double retval = 0; + if (ratePeriodCompoundingPeriod == 0) { + retval = -1 * (presentValue + (numberOfCompoundingPeriods * pmtPerPeriod)); + } else { + double r1 = ratePeriodCompoundingPeriod + 1; + retval = ((1 - Math.pow(r1, numberOfCompoundingPeriods)) * (type ? r1 : 1) * pmtPerPeriod) / ratePeriodCompoundingPeriod - presentValue + * Math.pow(r1, numberOfCompoundingPeriods); + } + return retval; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java index 8af7977ec..399d14b3b 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/saving/service/DepositAccountAssembler.java @@ -10,6 +10,7 @@ import org.mifosng.platform.exceptions.ClientNotFoundException; import org.mifosng.platform.exceptions.DepositProductNotFoundException; import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.saving.domain.DepositAccount; +import org.mifosng.platform.saving.domain.FixedTermDepositInterestCalculator; import org.mifosng.platform.savingproduct.domain.DepositProduct; import org.mifosng.platform.savingproduct.domain.DepositProductRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -23,11 +24,14 @@ public class DepositAccountAssembler { private final ClientRepository clientRepository; private final DepositProductRepository depositProductRepository; + private final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator; @Autowired - public DepositAccountAssembler(final ClientRepository clientRepository, final DepositProductRepository depositProductRepository) { + public DepositAccountAssembler(final ClientRepository clientRepository, final DepositProductRepository depositProductRepository, + final FixedTermDepositInterestCalculator fixedTermDepositInterestCalculator) { this.clientRepository = clientRepository; this.depositProductRepository = depositProductRepository; + this.fixedTermDepositInterestCalculator = fixedTermDepositInterestCalculator; } public DepositAccount assembleFrom(final DepositAccountCommand command) { @@ -76,14 +80,12 @@ public class DepositAccountAssembler { } // end of details allowed to be overriden from product - // fixed-term interest calculator - DepositAccount account = DepositAccount.openNew(client, product, command.getExternalId(), deposit, maturityInterestRate, tenureInMonths, compoundingInterestEvery, compoundingInterestFrequency, command.getCommencementDate(), - renewalAllowed, preClosureAllowed); + renewalAllowed, preClosureAllowed, this.fixedTermDepositInterestCalculator); return account; }