/*
* Copyright (c) 2005-2011 Grameen Foundation USA
* All rights reserved.
*
* Licensed 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.
*
* See also http://www.apache.org/licenses/LICENSE-2.0.html for an
* explanation of the license and how it is applied.
*/
package org.mifos.accounts.loan.schedule.domain;
import static org.mifos.accounts.loan.schedule.utils.Utilities.isGreaterThanZero;
import static org.mifos.framework.util.helpers.NumberUtils.max;
import java.math.BigDecimal;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import org.mifos.framework.util.helpers.NumberUtils;
public class Installment implements Comparable<Installment> {
private Integer id;
private Date dueDate;
private InstallmentPayments previousPayments;
private InstallmentPayment currentPayment;
private Map<InstallmentComponent, BigDecimal> actualAmounts;
/**
* @deprecated This constructor is intended to be used from unit test builders. It does only partial initialization
* of the new instance.
*/
@Deprecated
Installment() {
actualAmounts = new LinkedHashMap<InstallmentComponent, BigDecimal>();
previousPayments = new InstallmentPayments();
resetCurrentPayment();
resetPaymentComponents();
}
public Installment(Integer id, Date dueDate, BigDecimal principal, BigDecimal interest, BigDecimal extraInterest,
BigDecimal fees, BigDecimal miscFees, BigDecimal penalty, BigDecimal miscPenalty) {
this();
this.id = id;
this.dueDate = dueDate;
actualAmounts.put(InstallmentComponent.PRINCIPAL, principal);
actualAmounts.put(InstallmentComponent.INTEREST, interest);
actualAmounts.put(InstallmentComponent.EXTRA_INTEREST, extraInterest);
actualAmounts.put(InstallmentComponent.FEES, fees);
actualAmounts.put(InstallmentComponent.MISC_FEES, miscFees);
actualAmounts.put(InstallmentComponent.PENALTY, penalty);
actualAmounts.put(InstallmentComponent.MISC_PENALTY, miscPenalty);
}
private void resetPaymentComponents() {
actualAmounts.put(InstallmentComponent.PRINCIPAL, BigDecimal.ZERO);
actualAmounts.put(InstallmentComponent.INTEREST, BigDecimal.ZERO);
actualAmounts.put(InstallmentComponent.EXTRA_INTEREST, BigDecimal.ZERO);
actualAmounts.put(InstallmentComponent.EFFECTIVE_INTEREST, BigDecimal.ZERO);
actualAmounts.put(InstallmentComponent.FEES, BigDecimal.ZERO);
actualAmounts.put(InstallmentComponent.MISC_FEES, BigDecimal.ZERO);
actualAmounts.put(InstallmentComponent.PENALTY, BigDecimal.ZERO);
actualAmounts.put(InstallmentComponent.MISC_PENALTY, BigDecimal.ZERO);
}
public Integer getId() {
return id;
}
public Date getDueDate() {
return dueDate;
}
public BigDecimal getPrincipal() {
return actualAmounts.get(InstallmentComponent.PRINCIPAL);
}
public BigDecimal getInterest() {
return actualAmounts.get(InstallmentComponent.INTEREST);
}
public BigDecimal getExtraInterest() {
return actualAmounts.get(InstallmentComponent.EXTRA_INTEREST);
}
public void setExtraInterest(BigDecimal extraInterest) {
actualAmounts.put(InstallmentComponent.EXTRA_INTEREST, extraInterest);
}
public BigDecimal getMiscPenalty() {
return actualAmounts.get(InstallmentComponent.MISC_PENALTY);
}
public BigDecimal getPenalty() {
return actualAmounts.get(InstallmentComponent.PENALTY);
}
public BigDecimal getMiscFees() {
return actualAmounts.get(InstallmentComponent.MISC_FEES);
}
public BigDecimal getFees() {
return actualAmounts.get(InstallmentComponent.FEES);
}
public BigDecimal getTotalDue() {
return getMiscPenaltyDue().add(getPenaltyDue()).add(getMiscFeesDue()).add(getFeesDue()).
add(getExtraInterestDue()).add(getInterestDue()).add(getPrincipalDue());
}
@Override
public int compareTo(Installment installment) {
return this.getId().compareTo(installment.getId());
}
public BigDecimal getMiscPenaltyDue() {
return getMiscPenalty().subtract(getMiscPenaltyPaid());
}
public BigDecimal getPenaltyDue() {
return getPenalty().subtract(getPenaltyPaid());
}
public BigDecimal getMiscFeesDue() {
return getMiscFees().subtract(getMiscFeesPaid());
}
public BigDecimal getFeesDue() {
return getFees().subtract(getFeesPaid());
}
public BigDecimal getInterestDue() {
return hasEffectiveInterest() ? getEffectiveInterest() : getInterest().subtract(getInterestPaid());
}
public BigDecimal getPrincipalDue() {
return getPrincipal().subtract(getPrincipalPaid());
}
public BigDecimal getExtraInterestDue() {
return getExtraInterest().subtract(getExtraInterestPaid());
}
public boolean isMiscPenaltyDue() {
return isGreaterThanZero(getMiscPenaltyDue());
}
public boolean isPenaltyDue() {
return isGreaterThanZero(getPenaltyDue());
}
public boolean isMiscFeesDue() {
return isGreaterThanZero(getMiscPenaltyDue());
}
public boolean isFeesDue() {
return isGreaterThanZero(getFeesDue());
}
public boolean isInterestDue() {
return isGreaterThanZero(getInterestDue());
}
public boolean isExtraInterestDue() {
return isGreaterThanZero(getExtraInterestDue());
}
public boolean isPrincipalDue() {
return isGreaterThanZero(getPrincipalDue());
}
public BigDecimal pay(BigDecimal amount, Date transactionDate) {
currentPayment.setPaidDate(transactionDate);
amount = payMiscPenalty(amount, currentPayment);
amount = payPenalty(amount, currentPayment);
amount = payMiscFees(amount, currentPayment);
amount = payFees(amount, currentPayment);
amount = payExtraInterest(amount, currentPayment);
amount = payInterest(amount, currentPayment);
amount = payPrincipal(amount, currentPayment);
recordCurrentPayment();
return amount;
}
public BigDecimal payInterest(BigDecimal amount, Date transactionDate) {
currentPayment.setPaidDate(transactionDate);
amount = payInterest(amount, currentPayment);
amount = payExtraInterest(amount, currentPayment);
recordCurrentPayment();
return amount;
}
private BigDecimal payMiscPenalty(BigDecimal amount, InstallmentPayment installmentPayment) {
BigDecimal payable = NumberUtils.min(amount, getMiscPenaltyDue());
installmentPayment.setMiscPenaltyPaid(installmentPayment.getMiscPenaltyPaid().add(payable));
return amount.subtract(payable);
}
private BigDecimal payPenalty(BigDecimal amount, InstallmentPayment installmentPayment) {
BigDecimal payable = NumberUtils.min(amount, getPenaltyDue());
installmentPayment.setPenaltyPaid(installmentPayment.getPenaltyPaid().add(payable));
return amount.subtract(payable);
}
private BigDecimal payMiscFees(BigDecimal amount, InstallmentPayment installmentPayment) {
BigDecimal payable = NumberUtils.min(amount, getMiscFeesDue());
installmentPayment.setMiscFeesPaid(installmentPayment.getMiscFeesPaid().add(payable));
return amount.subtract(payable);
}
private BigDecimal payFees(BigDecimal amount, InstallmentPayment installmentPayment) {
BigDecimal payable = NumberUtils.min(amount, getFeesDue());
installmentPayment.setFeesPaid(installmentPayment.getFeesPaid().add(payable));
return amount.subtract(payable);
}
private BigDecimal payInterest(BigDecimal amount, InstallmentPayment installmentPayment) {
BigDecimal payable = NumberUtils.min(amount, getInterestDue());
installmentPayment.setInterestPaid(installmentPayment.getInterestPaid().add(payable));
return amount.subtract(payable);
}
public BigDecimal payInterestDueTillDate(BigDecimal amount, Date transactionDate, BigDecimal interestDueTillDate) {
currentPayment.setPaidDate(transactionDate);
BigDecimal payable = NumberUtils.min(amount, interestDueTillDate);
currentPayment.setInterestPaid(currentPayment.getInterestPaid().add(payable));
return amount.subtract(payable);
}
private BigDecimal payExtraInterest(BigDecimal amount, InstallmentPayment installmentPayment) {
BigDecimal payable = NumberUtils.min(amount, getExtraInterestDue());
installmentPayment.setExtraInterestPaid(installmentPayment.getExtraInterestPaid().add(payable));
return amount.subtract(payable);
}
private BigDecimal payPrincipal(BigDecimal amount, InstallmentPayment installmentPayment) {
BigDecimal payable = NumberUtils.min(amount, getPrincipalDue());
installmentPayment.setPrincipalPaid(installmentPayment.getPrincipalPaid().add(payable));
return amount.subtract(payable);
}
public BigDecimal payPrincipal(BigDecimal amount, Date transactionDate) {
currentPayment.setPaidDate(transactionDate);
return payPrincipal(amount, currentPayment);
}
public BigDecimal payExtraInterest(BigDecimal amount, Date transactionDate) {
currentPayment.setPaidDate(transactionDate);
return payExtraInterest(amount, currentPayment);
}
public BigDecimal getPrincipalPaid() {
return previousPayments.getPrincipalPaid();
}
public boolean isAnyPrincipalPaid() {
return isGreaterThanZero(getPrincipalPaid());
}
public Date getRecentPrincipalPaidDate() {
return previousPayments.getRecentPrincipalPaidDate();
}
public void addExtraInterest(BigDecimal extraInterest) {
setExtraInterest(getExtraInterest().add(extraInterest));
}
public BigDecimal getInterestPaid() {
return previousPayments.getInterestPaid();
}
public BigDecimal getExtraInterestPaid() {
return previousPayments.getExtraInterestPaid();
}
public BigDecimal getMiscPenaltyPaid() {
return previousPayments.getMiscPenaltyPaid();
}
public BigDecimal getPenaltyPaid() {
return previousPayments.getPenaltyPaid();
}
public BigDecimal getMiscFeesPaid() {
return previousPayments.getMiscFeesPaid();
}
public BigDecimal getFeesPaid() {
return previousPayments.getFeesPaid();
}
public BigDecimal getEffectiveInterest() {
return actualAmounts.get(InstallmentComponent.EFFECTIVE_INTEREST);
}
void setEffectiveInterest(BigDecimal effectiveInterest) {
actualAmounts.put(InstallmentComponent.EFFECTIVE_INTEREST, effectiveInterest);
}
/**
* Assumption: Make payment should not payoff all outstanding principal as repay does
* When assumption is wrong, effective interest can be zero, which in turn corrupts
* applicable interest
*/
public boolean hasEffectiveInterest() {
return isGreaterThanZero(getEffectiveInterest());
}
public BigDecimal getApplicableInterest() {
return hasEffectiveInterest() ? getEffectiveInterest() : getInterest();
}
public void addPayment(InstallmentPayment installmentPayment){
previousPayments.addPayment(installmentPayment);
}
void setId(Integer id) {
this.id = id;
}
void setDueDate(Date dueDate) {
this.dueDate = dueDate;
}
/**
* @deprecated Use the corresponding setters for assigning installment components
*/
@Deprecated
void setAmount(InstallmentComponent installmentComponent, BigDecimal amount){
actualAmounts.put(installmentComponent, amount);
}
public InstallmentPayment getCurrentPayment() {
return currentPayment;
}
public void recordCurrentPayment() {
previousPayments.addPayment(currentPayment);
}
public void resetCurrentPayment() {
currentPayment = new InstallmentPayment();
}
public Date fromDateForOverdueComputation() {
return max(getDueDate(), getRecentPrincipalPaidDate());
}
public BigDecimal getCurrentPrincipalPaid() {
return currentPayment.getPrincipalPaid();
}
public BigDecimal getCurrentInterestPaid() {
return currentPayment.getInterestPaid();
}
public BigDecimal getCurrentExtraInterestPaid() {
return currentPayment.getExtraInterestPaid();
}
public BigDecimal getCurrentFeesPaid() {
return currentPayment.getFeesPaid();
}
public BigDecimal getCurrentMiscFeesPaid() {
return currentPayment.getMiscFeesPaid();
}
public BigDecimal getCurrentPenaltyPaid() {
return currentPayment.getPenaltyPaid();
}
public BigDecimal getCurrentMiscPenaltyPaid() {
return currentPayment.getMiscPenaltyPaid();
}
}