com.gst.portfolio.savings.domain.interest.EndOfDayBalance.java Source code

Java tutorial

Introduction

Here is the source code for com.gst.portfolio.savings.domain.interest.EndOfDayBalance.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.gst.portfolio.savings.domain.interest;

import java.math.BigDecimal;
import java.math.MathContext;

import com.gst.infrastructure.core.domain.LocalDateInterval;
import com.gst.organisation.monetary.domain.Money;
import com.gst.organisation.monetary.domain.MoneyHelper;
import org.joda.time.LocalDate;

public class EndOfDayBalance {

    private final LocalDate date;
    private final Money openingBalance;
    private final Money endOfDayBalance;
    private final int numberOfDays;

    public static EndOfDayBalance from(final LocalDate date, final Money openingBalance,
            final Money endOfDayBalance, final int numberOfDays) {
        return new EndOfDayBalance(date, openingBalance, endOfDayBalance, numberOfDays);
    }

    public EndOfDayBalance(final LocalDate date, final Money openingBalance, final Money endOfDayBalance,
            final int numberOfDays) {
        this.date = date;
        this.openingBalance = openingBalance;
        this.endOfDayBalance = endOfDayBalance;
        this.numberOfDays = numberOfDays;
    }

    public LocalDate date() {
        return this.date;
    }

    public Money closingBalance() {
        return this.endOfDayBalance;
    }

    public BigDecimal cumulativeBalance(final BigDecimal interestToCompound) {
        final BigDecimal daysAsBigDecimal = BigDecimal.valueOf(this.numberOfDays);
        final BigDecimal realBalanceForInterestCalculation = this.endOfDayBalance.getAmount()
                .add(interestToCompound);
        return realBalanceForInterestCalculation.multiply(daysAsBigDecimal, MathContext.DECIMAL64).setScale(9,
                MoneyHelper.getRoundingMode());
    }

    public BigDecimal calculateInterestOnBalance(final BigDecimal interestToCompound,
            final BigDecimal interestRateAsFraction, final long daysInYear,
            final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction,
            final BigDecimal minOverdraftForInterestCalculation) {

        BigDecimal interest = BigDecimal.ZERO.setScale(9, MoneyHelper.getRoundingMode());
        final BigDecimal realBalanceForInterestCalculation = this.endOfDayBalance.getAmount()
                .add(interestToCompound);
        if (realBalanceForInterestCalculation.compareTo(BigDecimal.ZERO) >= 0) {
            if (realBalanceForInterestCalculation.compareTo(minBalanceForInterestCalculation) >= 0) {
                final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear),
                        MathContext.DECIMAL64);
                final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand,
                        MathContext.DECIMAL64);
                final BigDecimal periodicInterestRate = dailyInterestRate
                        .multiply(BigDecimal.valueOf(this.numberOfDays), MathContext.DECIMAL64);
                interest = realBalanceForInterestCalculation.multiply(periodicInterestRate, MathContext.DECIMAL64)
                        .setScale(9, MoneyHelper.getRoundingMode());
            }
        } else {
            if (realBalanceForInterestCalculation.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
                final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear),
                        MathContext.DECIMAL64);
                final BigDecimal dailyInterestRate = overdraftInterestRateAsFraction.multiply(multiplicand,
                        MathContext.DECIMAL64);
                final BigDecimal periodicInterestRate = dailyInterestRate
                        .multiply(BigDecimal.valueOf(this.numberOfDays), MathContext.DECIMAL64);
                interest = realBalanceForInterestCalculation.multiply(periodicInterestRate, MathContext.DECIMAL64)
                        .setScale(9, MoneyHelper.getRoundingMode());
            }
        }
        return interest;
    }

    /**
     * Future Value (FV) = PV x (1+r)^n
     * 
     * Interest = FV - PV PV = Principal or the Account Balance r = rate per
     * compounding period (so for daily, r = nominalInterestRateAsFraction x
     * 1/365 n = number of periods rate is compounded
     */
    public BigDecimal calculateInterestOnBalanceAndInterest(final BigDecimal interestToCompound,
            final BigDecimal interestRateAsFraction, final long daysInYear,
            final BigDecimal minBalanceForInterestCalculation, final BigDecimal overdraftInterestRateAsFraction,
            final BigDecimal minOverdraftForInterestCalculation) {
        final BigDecimal multiplicand = BigDecimal.ONE.divide(BigDecimal.valueOf(daysInYear),
                MathContext.DECIMAL64);

        final BigDecimal presentValue = this.endOfDayBalance.getAmount().add(interestToCompound);
        BigDecimal futureValue = presentValue.setScale(9, MoneyHelper.getRoundingMode());

        if (presentValue.compareTo(BigDecimal.ZERO) >= 0) {
            if (presentValue.compareTo(minBalanceForInterestCalculation) >= 0) {
                final BigDecimal r = interestRateAsFraction.multiply(multiplicand);

                final BigDecimal interestRateForCompoundingPeriodPlusOne = BigDecimal.ONE.add(r);

                final double interestRateForCompoundingPeriodPowered = Math.pow(
                        interestRateForCompoundingPeriodPlusOne.doubleValue(),
                        Integer.valueOf(this.numberOfDays).doubleValue());
                futureValue = presentValue.multiply(BigDecimal.valueOf(interestRateForCompoundingPeriodPowered),
                        MathContext.DECIMAL64).setScale(9, MoneyHelper.getRoundingMode());
            }
        } else {
            if (presentValue.compareTo(minOverdraftForInterestCalculation.negate()) < 0) {
                final BigDecimal r = overdraftInterestRateAsFraction.multiply(multiplicand);

                final BigDecimal interestRateForCompoundingPeriodPlusOne = BigDecimal.ONE.add(r);

                final double interestRateForCompoundingPeriodPowered = Math.pow(
                        interestRateForCompoundingPeriodPlusOne.doubleValue(),
                        Integer.valueOf(this.numberOfDays).doubleValue());
                futureValue = presentValue.multiply(BigDecimal.valueOf(interestRateForCompoundingPeriodPowered),
                        MathContext.DECIMAL64).setScale(9, MoneyHelper.getRoundingMode());
            }
        }

        return futureValue.subtract(presentValue);
    }

    /**
     * @param compoundingPeriodInterval
     * @param upToInterestCalculationDate
     *            : For calculating maturity details in advance
     *            upToInterestCalculationDate will be maturity date else it will
     *            be DateUtils.getLocalDateOfTenant().
     * @return
     */
    public EndOfDayBalance upTo(final LocalDateInterval compoundingPeriodInterval,
            final LocalDate upToInterestCalculationDate) {

        Money startingBalance = this.openingBalance;
        LocalDate balanceStartDate = this.date;

        LocalDate oldBalanceEndDate = this.date.plusDays(this.numberOfDays - 1);

        int daysOfBalance = this.numberOfDays;

        if (this.date.isBefore(compoundingPeriodInterval.startDate())) {
            balanceStartDate = compoundingPeriodInterval.startDate();
            startingBalance = this.endOfDayBalance;
            final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate,
                    oldBalanceEndDate);
            daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate();
        }

        LocalDate balanceEndDate = balanceStartDate.plusDays(daysOfBalance - 1);
        if (balanceEndDate.isAfter(compoundingPeriodInterval.endDate())) {
            balanceEndDate = compoundingPeriodInterval.endDate();
            final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate,
                    balanceEndDate);
            daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate();
        }
        if (balanceEndDate.isAfter(upToInterestCalculationDate)) {
            balanceEndDate = upToInterestCalculationDate;
            final LocalDateInterval balancePeriodInterval = LocalDateInterval.create(balanceStartDate,
                    balanceEndDate);
            daysOfBalance = balancePeriodInterval.daysInPeriodInclusiveOfEndDate();
        }

        return new EndOfDayBalance(balanceStartDate, startingBalance, this.endOfDayBalance, daysOfBalance);
    }

    public boolean contains(final LocalDateInterval compoundingPeriodInterval) {

        final LocalDate balanceUpToDate = this.date.plusDays(this.numberOfDays - 1);

        final LocalDateInterval balanceInterval = LocalDateInterval.create(this.date, balanceUpToDate);
        return balanceInterval.containsPortionOf(compoundingPeriodInterval);
    }

    public Integer getNumberOfDays() {
        return Integer.valueOf(this.numberOfDays);
    }
}