package org.mifos.clientportfolio.newloan.domain; import java.util.List; import java.util.Set; import org.mifos.accounts.business.AccountActionDateEntity; import org.mifos.accounts.business.AccountFeesActionDetailEntity; import org.mifos.accounts.loan.business.LoanScheduleEntity; import org.mifos.accounts.loan.business.RepaymentTotals; import org.mifos.accounts.productdefinition.util.helpers.GraceType; import org.mifos.application.master.business.MifosCurrency; import org.mifos.framework.util.helpers.Money; import org.mifos.framework.util.helpers.MoneyUtils; public class DefaultLoanScheduleRounderHelper implements LoanScheduleRounderHelper { @Override public RepaymentTotals calculateInitialTotals_v2( List<LoanScheduleEntity> unroundedLoanSchedules, Money loanAmount, List<LoanScheduleEntity> allInstallments) { RepaymentTotals totals = new RepaymentTotals(loanAmount.getCurrency()); Money exactTotalInterestDue = new Money(loanAmount.getCurrency(), "0"); Money exactTotalAccountFeesDue = new Money(loanAmount.getCurrency(), "0"); Money exactTotalMiscFeesDue = new Money(loanAmount.getCurrency(), "0"); Money exactTotalMiscPenaltiesDue = new Money(loanAmount.getCurrency(), "0"); // principal due = loan amount less any payments on principal Money exactTotalPrincipalDue = loanAmount; for (AccountActionDateEntity e : allInstallments) { LoanScheduleEntity installment = (LoanScheduleEntity) e; exactTotalPrincipalDue = exactTotalPrincipalDue.subtract(installment.getPrincipalPaid()); } for (Object element : unroundedLoanSchedules) { LoanScheduleEntity currentInstallment = (LoanScheduleEntity) element; exactTotalInterestDue = exactTotalInterestDue.add(currentInstallment.getInterestDue()); exactTotalAccountFeesDue = exactTotalAccountFeesDue.add(currentInstallment.getTotalFeesDue()); exactTotalMiscFeesDue = exactTotalMiscFeesDue.add(currentInstallment.getMiscFeeDue()); exactTotalMiscPenaltiesDue = exactTotalMiscPenaltiesDue.add(currentInstallment.getMiscPenaltyDue()); } Money exactTotalPaymentsDue = exactTotalInterestDue.add(exactTotalAccountFeesDue).add(exactTotalMiscFeesDue) .add(exactTotalMiscPenaltiesDue).add(exactTotalPrincipalDue); totals.setRoundedPaymentsDue(MoneyUtils.finalRound(exactTotalPaymentsDue)); totals.setRoundedAccountFeesDue(MoneyUtils.currencyRound(exactTotalAccountFeesDue)); totals.setRoundedMiscFeesDue(MoneyUtils.currencyRound(exactTotalMiscFeesDue)); totals.setRoundedMiscPenaltiesDue(MoneyUtils.currencyRound(exactTotalMiscPenaltiesDue)); totals.setRoundedPrincipalDue(exactTotalPrincipalDue); // Adjust interest to account for rounding discrepancies totals.setRoundedInterestDue(totals.getRoundedPaymentsDue().subtract(totals.getRoundedAccountFeesDue()) .subtract(totals.getRoundedMiscFeesDue()).subtract(totals.getRoundedPenaltiesDue()).subtract( totals.getRoundedMiscPenaltiesDue()).subtract(totals.getRoundedPrincipalDue())); return totals; } @Override public boolean isGraceInstallment_v2(int installmentNum, GraceType graceType, Short gracePeriodDuration) { return graceType.equals(GraceType.PRINCIPALONLYGRACE) && installmentNum <= gracePeriodDuration; } @Override public void updateRunningTotals_v2(final RepaymentTotals totals, final LoanScheduleEntity currentInstallment) { totals.setRunningPayments(totals.getRunningPayments().add(currentInstallment.getTotalPaymentDue())); totals.setRunningPrincipal(totals.getRunningPrincipal().add(currentInstallment.getPrincipalDue())); totals.setRunningAccountFees(totals.getRunningAccountFees().add(currentInstallment.getTotalFeesDue())); totals.setRunningMiscFees(totals.getRunningMiscFees().add(currentInstallment.getMiscFeeDue())); totals.setRunningPenalties(totals.getRunningPenalties().add(currentInstallment.getPenaltyDue())); } /** * See JavaDoc comment for applyRounding_v2. TODO: handle fees */ @Override public void roundAndAdjustLastInstallment_v2(final LoanScheduleEntity lastInstallment, final RepaymentTotals totals) { roundInstallmentAccountFeesDue_v2(lastInstallment); Money installmentPayment = MoneyUtils.finalRound(totals.getRoundedPaymentsDue().subtract(totals.getRunningPayments())); lastInstallment.setPrincipal(MoneyUtils.currencyRound(totals.getRoundedPrincipalDue().subtract(totals.getRunningPrincipal()))); adjustLastInstallmentFees_v2(lastInstallment, totals, installmentPayment.getCurrency()); lastInstallment.setInterest(MoneyUtils.currencyRound(installmentPayment.subtract( lastInstallment.getPrincipalDue()).subtract(lastInstallment.getTotalFeeDueWithMiscFeeDue()).subtract( lastInstallment.getPenaltyDue()))); } /** * adjust the first fee in the installment's set of fees */ private void adjustLastInstallmentFees_v2(final LoanScheduleEntity lastInstallment, final RepaymentTotals totals, MifosCurrency mifosCurrency) { Set<AccountFeesActionDetailEntity> feeDetails = lastInstallment.getAccountFeesActionDetails(); if (!(feeDetails == null) && !feeDetails.isEmpty()) { Money lastInstallmentFeeSum = new Money(mifosCurrency); for (AccountFeesActionDetailEntity e : feeDetails) { lastInstallmentFeeSum = lastInstallmentFeeSum.add(e.getFeeAmount()); } for (Object element : feeDetails) { AccountFeesActionDetailEntity e = (AccountFeesActionDetailEntity) element; e.adjustFeeAmount(totals.getRoundedAccountFeesDue().subtract(totals.getRunningAccountFees()).subtract( lastInstallmentFeeSum)); // just adjust the first fee return; } } } /** * See Javadoc comment for method applyRounding() for business rules for rounding and adjusting all installments but * the last. LoanScheduleEntity does not store the total payment due, directly, but it is the sum of principal, * interest, and non-miscellaneous fees. * <p> * * how to set rounded fee for installment?????? This is what I want to do: currentInstallment.setFee * (currencyRound_v2 (currentInstallment.getFee)); * * Then I want to adjust principal, but need to extract the rounded fee, like this: * currentInstallment.setPrincipal(installmentRoundedTotalPayment .subtract (currentInstallment.getInterest() * .subtract (currentInstallment.getFee()); */ @Override public void roundAndAdjustButLastNonGraceInstallment_v2(final LoanScheduleEntity installment) { Money roundedTotalInstallmentPaymentDue = MoneyUtils.initialRound(installment.getTotalPaymentDue()); roundInstallmentAccountFeesDue_v2(installment); installment.setInterest(MoneyUtils.currencyRound(installment.getInterest())); // TODO: above comment applies to principal installment.setPrincipal(roundedTotalInstallmentPaymentDue.subtract(installment.getInterestDue()).subtract( installment.getTotalFeeDueWithMiscFeeDue()).subtract(installment.getPenaltyDue()).add( installment.getPrincipalPaid())); } @Override public void roundAndAdjustNonGraceInstallmentForDecliningEPI_v2(final LoanScheduleEntity installment) { Money roundedTotalInstallmentPaymentDue = MoneyUtils.initialRound(installment.getTotalPaymentDue()); roundInstallmentAccountFeesDue_v2(installment); installment.setPrincipal(MoneyUtils.currencyRound(installment.getPrincipal())); // TODO: above comment applies to principal installment.setInterest(roundedTotalInstallmentPaymentDue.subtract(installment.getPrincipalDue()).subtract( installment.getTotalFeeDueWithMiscFeeDue()).subtract(installment.getPenaltyDue())); } /** * For principal-only grace installments, adjust the interest to account for rounding discrepancies. */ @Override public LoanScheduleEntity roundAndAdjustGraceInstallment_v2(final LoanScheduleEntity installment) { Money roundedInstallmentTotalPaymentDue = MoneyUtils.initialRound(installment.getTotalPaymentDue()); LoanScheduleEntity roundedInstallment = roundInstallmentAccountFeesDue_v2(installment); roundedInstallment.setInterest(roundedInstallmentTotalPaymentDue.subtract(roundedInstallment.getTotalFeeDueWithMiscFeeDue()) .subtract(roundedInstallment.getPenaltyDue())); return roundedInstallment; } private LoanScheduleEntity roundInstallmentAccountFeesDue_v2(final LoanScheduleEntity installment) { for (Object element : installment.getAccountFeesActionDetails()) { AccountFeesActionDetailEntity e = (AccountFeesActionDetailEntity) element; e.roundFeeAmount(MoneyUtils.currencyRound(e.getFeeDue().add(e.getFeeAmountPaid()))); } return installment; } }