blob: f213681117a465ac078c013ed5e1a6ce726606c4 [file] [log] [blame]
/**
* 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 org.apache.fineract.portfolio.savings.domain.interest;
import java.math.BigDecimal;
import java.math.MathContext;
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.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);
}
}