/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.entities.accounts.loans;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Calendar;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.utils.Amount;
import nl.strohalm.cyclos.utils.DateHelper;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
/**
* Contains parameters on TransferTypes to configure loans
* @author luis
*/
public class LoanParameters implements Serializable, Cloneable {
private static final long serialVersionUID = -2308200961589222771L;
private Loan.Type type;
private Integer repaymentDays;
private TransferType repaymentType;
private BigDecimal monthlyInterest;
private TransferType monthlyInterestRepaymentType;
private Amount grantFee;
private TransferType grantFeeRepaymentType;
private Amount expirationFee;
private TransferType expirationFeeRepaymentType;
private BigDecimal expirationDailyInterest;
private TransferType expirationDailyInterestRepaymentType;
private TransferType originalTransferType;
/**
* Calculates the grant fee to the given amount
* @return 0 if no grant fee, the applied grant fee otherwise (ie: if grant fee = 3%, returns 30 for amount = 1000)
*/
public BigDecimal calculateGrantFee(final BigDecimal amount) {
if (grantFee == null || grantFee.getValue() == null || amount.compareTo(BigDecimal.ZERO) != 1) {
return BigDecimal.ZERO;
}
return grantFee.apply(amount);
}
/**
* Calculates the total loan value for the given parameters
*/
public BigDecimal calculateLoanTotal(final BigDecimal amount, final int paymentCount, final Calendar grantDate, final Calendar firstExpirationDate, final MathContext mathContext) {
final BigDecimal grantFee = calculateGrantFee(amount);
final BigDecimal monthlyInterests = calculateMonthlyInterests(amount, paymentCount, grantDate, firstExpirationDate, mathContext);
return amount.add(grantFee).add(monthlyInterests);
}
/**
* Calculates the monthly interests for the given parameters
* @return 0 if no monthly interests, or the applied interests otherwise (ie: if monthly interests = 1%, paymentCount = 1 and delay = 0, returns
* 10 for amount = 1000)
*/
public BigDecimal calculateMonthlyInterests(final BigDecimal amount, final int paymentCount, Calendar grantDate, Calendar firstExpirationDate, final MathContext mathContext) {
if (monthlyInterest == null || amount.compareTo(BigDecimal.ZERO) != 1 || paymentCount < 1) {
return BigDecimal.ZERO;
}
// Calculate the delay
final Calendar now = Calendar.getInstance();
grantDate = grantDate == null ? now : grantDate;
firstExpirationDate = firstExpirationDate == null ? (Calendar) now.clone() : firstExpirationDate;
final Calendar shouldBeFirstExpiration = (Calendar) grantDate.clone();
shouldBeFirstExpiration.add(Calendar.MONTH, 1);
int delay = DateHelper.daysBetween(shouldBeFirstExpiration, firstExpirationDate);
if (delay < 0) {
delay = 0;
}
final BigDecimal grantFee = calculateGrantFee(amount);
final BigDecimal baseAmount = amount.add(grantFee);
final BigDecimal interests = monthlyInterest.divide(new BigDecimal(100), mathContext);
final BigDecimal numerator = new BigDecimal(Math.pow(1 + interests.doubleValue(), paymentCount + delay / 30F)).multiply(interests);
final BigDecimal denominator = new BigDecimal(Math.pow(1 + interests.doubleValue(), paymentCount) - 1);
final BigDecimal paymentAmount = baseAmount.multiply(numerator).divide(denominator, mathContext);
final BigDecimal totalAmount = paymentAmount.multiply(new BigDecimal(paymentCount));
return totalAmount.subtract(baseAmount);
}
/**
* Calculates the expiration fee, if any, for the given loan payment parameters
* @return 0 if no expiration fee, the applied expiration fee otherwise (ie: if expiration fee = 3%, returns 3 for amount = 100)
*/
public BigDecimal calculatePaymentExpirationFee(final BigDecimal amount, final int diff) {
if (expirationFee != null && expirationFee.getValue() != null) {
if (diff > 0) {
return expirationFee.apply(amount);
}
}
return BigDecimal.ZERO;
}
/**
* Calculates the expiration daily interest, if any, for the given loan payment parameters
* @return 0 if no interest, the applied interest otherwise (ie: if expiration daily interest fee = 1%, on 5 days after expiration, returns 1 for
* amount = 100)
*/
public BigDecimal calculatePaymentExpirationInterest(final BigDecimal amount, final int diff, final MathContext mathContext) {
if (expirationDailyInterest != null) {
if (diff > 0) {
return expirationDailyInterest.multiply(amount).multiply(new BigDecimal(diff)).divide(new BigDecimal(100), mathContext);
}
}
return BigDecimal.ZERO;
}
/**
* Calculates the total amount of a loan payment on the given date
*/
public BigDecimal calculatePaymentTotal(final BigDecimal amount, final Calendar date, final Calendar expirationDate, final MathContext mathContext) {
final int diff = DateHelper.daysBetween(date, expirationDate);
return amount.add(calculatePaymentExpirationFee(amount, diff)).add(calculatePaymentExpirationInterest(amount, diff, mathContext));
}
@Override
public LoanParameters clone() {
try {
return (LoanParameters) super.clone();
} catch (final CloneNotSupportedException e) {
return null;
}
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof LoanParameters)) {
return false;
}
final LoanParameters lp = (LoanParameters) obj;
final EqualsBuilder eb = new EqualsBuilder();
eb.append(type, lp.type);
eb.append(repaymentType, lp.repaymentType);
eb.append(grantFee, lp.grantFee);
eb.append(monthlyInterest, lp.monthlyInterest);
eb.append(expirationFee, lp.expirationFee);
eb.append(expirationDailyInterest, lp.expirationDailyInterest);
eb.append(repaymentDays, lp.repaymentDays);
return eb.isEquals();
}
public BigDecimal getExpirationDailyInterest() {
return expirationDailyInterest;
}
public Amount getExpirationDailyInterestAmount() {
return expirationDailyInterest == null ? null : Amount.percentage(expirationDailyInterest);
}
public TransferType getExpirationDailyInterestRepaymentType() {
return expirationDailyInterestRepaymentType;
}
public Amount getExpirationFee() {
return expirationFee;
}
public TransferType getExpirationFeeRepaymentType() {
return expirationFeeRepaymentType;
}
public Amount getGrantFee() {
return grantFee;
}
public TransferType getGrantFeeRepaymentType() {
return grantFeeRepaymentType;
}
public BigDecimal getMonthlyInterest() {
return monthlyInterest;
}
public Amount getMonthlyInterestAmount() {
return monthlyInterest == null ? null : Amount.percentage(monthlyInterest);
}
public TransferType getMonthlyInterestRepaymentType() {
return monthlyInterestRepaymentType;
}
public TransferType getOriginalTransferType() {
return originalTransferType;
}
public Integer getRepaymentDays() {
return repaymentDays;
}
public TransferType getRepaymentType() {
return repaymentType;
}
public Loan.Type getType() {
return type;
}
@Override
public int hashCode() {
final HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(type);
hcb.append(repaymentType);
hcb.append(grantFee);
hcb.append(monthlyInterest);
hcb.append(expirationFee);
hcb.append(expirationDailyInterest);
hcb.append(repaymentDays);
return hcb.toHashCode();
}
public void setExpirationDailyInterest(final BigDecimal expirationDailyInterests) {
expirationDailyInterest = expirationDailyInterests;
}
public void setExpirationDailyInterestRepaymentType(final TransferType expirationDailyInterestRepaymentType) {
this.expirationDailyInterestRepaymentType = expirationDailyInterestRepaymentType;
}
public void setExpirationFee(final Amount expirationFee) {
this.expirationFee = expirationFee;
}
public void setExpirationFeeRepaymentType(final TransferType expirationFeeRepaymentType) {
this.expirationFeeRepaymentType = expirationFeeRepaymentType;
}
public void setGrantFee(final Amount grantFee) {
this.grantFee = grantFee;
}
public void setGrantFeeRepaymentType(final TransferType grantFeeRepaymentType) {
this.grantFeeRepaymentType = grantFeeRepaymentType;
}
public void setMonthlyInterest(final BigDecimal monthlyInterests) {
monthlyInterest = monthlyInterests;
}
public void setMonthlyInterestRepaymentType(final TransferType monthlyInterestRepaymentType) {
this.monthlyInterestRepaymentType = monthlyInterestRepaymentType;
}
public void setOriginalTransferType(final TransferType originalTransferType) {
this.originalTransferType = originalTransferType;
}
public void setRepaymentDays(final Integer repaymentDays) {
this.repaymentDays = repaymentDays;
}
public void setRepaymentType(final TransferType repaymentType) {
this.repaymentType = repaymentType;
}
public void setType(final Loan.Type type) {
this.type = type;
}
}