/*
* 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.business;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mifos.framework.TestUtils.RUPEE;
import static org.mifos.framework.TestUtils.getDate;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Date;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mifos.accounts.business.AccountFeesActionDetailEntity;
import org.mifos.accounts.business.AccountFeesEntity;
import org.mifos.accounts.business.AccountPaymentEntity;
import org.mifos.accounts.business.AccountTrxnEntity;
import org.mifos.accounts.fees.business.FeeBO;
import org.mifos.accounts.loan.persistance.LegacyLoanDao;
import org.mifos.accounts.loan.schedule.domain.Installment;
import org.mifos.accounts.loan.schedule.domain.InstallmentBuilder;
import org.mifos.accounts.util.helpers.AccountActionTypes;
import org.mifos.accounts.util.helpers.PaymentStatus;
import org.mifos.config.business.service.ConfigurationBusinessService;
import org.mifos.customers.personnel.business.PersonnelBO;
import org.mifos.framework.TestUtils;
import org.mifos.framework.util.helpers.Money;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class LoanScheduleEntityTest {
@Mock
private LoanBO loanBO;
@Mock
private PersonnelBO personnel;
@Mock
private AccountPaymentEntity accountPayment;
@Mock
private LegacyLoanDao legacyLoanDao;
@Mock
private AccountPaymentEntity accountPaymentEntity;
@Mock
private ConfigurationBusinessService configService;
private LoanScheduleEntity loanScheduleEntity;
private Date paymentDate;
@Before
public void setUp() {
loanScheduleEntity = new LoanScheduleEntity();
paymentDate = TestUtils.getDate(12, 1, 2010);
}
@Test
public void shouldRecordPaymentOnPayOff() {
loanScheduleEntity = new LoanScheduleEntity() {
@Override
public Money getTotalDueWithFees() {
return Money.zero(RUPEE);
}
};
loanScheduleEntity.recordPayment(paymentDate);
assertEquals(loanScheduleEntity.getPaymentDate(), paymentDate);
assertEquals(loanScheduleEntity.getPaymentStatus(), PaymentStatus.PAID.getValue());
}
@Test
public void shouldRecordPaymentOnPartPay() {
loanScheduleEntity = new LoanScheduleEntity() {
@Override
public Money getTotalDueWithFees() {
return new Money(RUPEE, 10.0);
}
};
loanScheduleEntity.recordPayment(paymentDate);
assertEquals(loanScheduleEntity.getPaymentDate(), paymentDate);
assertEquals(loanScheduleEntity.getPaymentStatus(), PaymentStatus.UNPAID.getValue());
}
// TODO: Use a AccountTrxnEntityMatcher to verify the allocation totals
@Test
public void shouldUpdateLoanSummaryAndPerformanceHistoryOnPayOff() {
PaymentAllocation paymentAllocation = new PaymentAllocation(RUPEE);
loanScheduleEntity = new LoanScheduleEntity() {
@Override
public boolean isPaid() {
return true;
}
};
loanScheduleEntity.setAccount(loanBO);
loanScheduleEntity.setPaymentAllocation(paymentAllocation);
Mockito.doNothing().when(accountPayment).addAccountTrxn(Mockito.<AccountTrxnEntity>any());
when(loanBO.getlegacyLoanDao()).thenReturn(legacyLoanDao);
when(accountPayment.getAccount()).thenReturn(loanBO);
loanScheduleEntity.updateSummaryAndPerformanceHistory(accountPayment, personnel, paymentDate);
verify(accountPayment, times(1)).addAccountTrxn(Mockito.<AccountTrxnEntity>any());
verify(loanBO, times(1)).getlegacyLoanDao();
verify(accountPayment, times(1)).getAccount();
verify(loanBO, times(1)).recordSummaryAndPerfHistory(true, paymentAllocation);
}
@Test
public void shouldUpdateLoanSummaryAndNotPerformanceHistoryOnPartPay() {
PaymentAllocation paymentAllocation = new PaymentAllocation(RUPEE);
loanScheduleEntity = new LoanScheduleEntity() {
@Override
public boolean isPaid() {
return false;
}
};
loanScheduleEntity.setAccount(loanBO);
loanScheduleEntity.setPaymentAllocation(paymentAllocation);
Mockito.doNothing().when(accountPayment).addAccountTrxn(Mockito.<AccountTrxnEntity>any());
when(loanBO.getlegacyLoanDao()).thenReturn(legacyLoanDao);
when(accountPayment.getAccount()).thenReturn(loanBO);
loanScheduleEntity.updateSummaryAndPerformanceHistory(accountPayment, personnel, paymentDate);
verify(accountPayment, times(1)).addAccountTrxn(Mockito.<AccountTrxnEntity>any());
verify(loanBO, times(1)).getlegacyLoanDao();
verify(accountPayment, times(1)).getAccount();
verify(loanBO, times(1)).recordSummaryAndPerfHistory(false, paymentAllocation);
}
@Test
public void shouldPayComponents() {
loanScheduleEntity = new LoanScheduleEntity() {
@Override
public Money getTotalDueWithFees() {
return Money.zero(RUPEE);
}
};
when(loanBO.getCurrency()).thenReturn(RUPEE);
loanScheduleEntity.setAccount(loanBO);
loanScheduleEntity.setPrincipalPaid(Money.zero(RUPEE));
loanScheduleEntity.setInterestPaid(Money.zero(RUPEE));
loanScheduleEntity.setExtraInterestPaid(Money.zero(RUPEE));
loanScheduleEntity.setPenaltyPaid(Money.zero(RUPEE));
loanScheduleEntity.setMiscPenaltyPaid(Money.zero(RUPEE));
loanScheduleEntity.setMiscFeePaid(Money.zero(RUPEE));
AccountFeesActionDetailEntity feesActionDetailEntity = new LoanFeeScheduleEntity(loanScheduleEntity,
mock(FeeBO.class), mock(AccountFeesEntity.class), new Money(RUPEE, 100d));
feesActionDetailEntity.setAccountFeesActionDetailId(1);
loanScheduleEntity.addAccountFeesAction(feesActionDetailEntity);
loanScheduleEntity.payComponents(getInstallment(), RUPEE, paymentDate);
PaymentAllocation paymentAllocation = loanScheduleEntity.getPaymentAllocation();
assertEquals(new Money(RUPEE, 90.0), paymentAllocation.getPrincipalPaid());
assertEquals(new Money(RUPEE, 80.0), paymentAllocation.getInterestPaid());
assertEquals(new Money(RUPEE, 70.0), paymentAllocation.getExtraInterestPaid());
assertEquals(new Money(RUPEE, 60.0), paymentAllocation.getTotalFeesPaid());
assertEquals(new Money(RUPEE, 50.0), paymentAllocation.getMiscFeePaid());
assertEquals(new Money(RUPEE, 40.0), paymentAllocation.getPenaltyPaid());
assertEquals(new Money(RUPEE, 30.0), paymentAllocation.getMiscPenaltyPaid());
assertEquals(new Money(RUPEE, 100.0), loanScheduleEntity.getExtraInterest());
assertEquals(new Money(RUPEE, 100.0), loanScheduleEntity.getInterest());
}
private Installment getInstallment() {
return new InstallmentBuilder("1").
withDueDate(getDate(23, 10, 2010)).
withPrincipal(100).withPrincipalPaid(90).
withInterest(100).withInterestPaid(80).
withExtraInterest(100).withExtraInterestPaid(70).
withMiscFees(100).withMiscFeesPaid(50).
withPenalty(100).withPenaltyPaid(40).
withMiscPenalty(100).withMiscPenaltyPaid(30).
withFees(100).withFeesPaid(60).
build();
}
@Test
public void shouldPayComponentsAndAllocatePayments() {
LoanFeeScheduleEntity fee1, fee2, fee3, fee4, fee5, fee6;
loanScheduleEntity.setPrincipal(makeMoney(300));
loanScheduleEntity.setPrincipalPaid(makeMoney(150));
loanScheduleEntity.setInterest(makeMoney(20));
loanScheduleEntity.setInterestPaid(makeMoney(10));
loanScheduleEntity.addAccountFeesAction(fee1 = getFee(11, 10, 5));
loanScheduleEntity.addAccountFeesAction(fee2 = getFee(22, 8, 4));
loanScheduleEntity.addAccountFeesAction(fee3 = getFee(33, 6, 3));
loanScheduleEntity.addAccountFeesAction(fee4 = getFee(44, 4, 2));
loanScheduleEntity.addAccountFeesAction(fee5 = getFee(55, 2, 1));
loanScheduleEntity.addAccountFeesAction(fee6 = getFee(66, 1, 0));
loanScheduleEntity.setMiscFee(makeMoney(0));
loanScheduleEntity.setMiscFeePaid(makeMoney(0));
loanScheduleEntity.setPenalty(makeMoney(1));
loanScheduleEntity.setPenaltyPaid(makeMoney(1));
loanScheduleEntity.setMiscPenalty(makeMoney(10));
loanScheduleEntity.setMiscPenaltyPaid(makeMoney(5));
loanScheduleEntity.setAccount(loanBO);
when(loanBO.getCurrency()).thenReturn(RUPEE);
Money paymentAmount = makeMoney(1000d);
Money balance = loanScheduleEntity.payComponents(paymentAmount, paymentDate);
PaymentAllocation paymentAllocation = loanScheduleEntity.getPaymentAllocation();
assertThat(paymentAllocation.getPenaltyPaid().getAmount().doubleValue(), is(0d));
assertThat(paymentAllocation.getMiscFeePaid().getAmount().doubleValue(), is(0d));
assertThat(paymentAllocation.getMiscPenaltyPaid().getAmount().doubleValue(), is(5d));
assertThat(paymentAllocation.getInterestPaid().getAmount().doubleValue(), is(10d));
assertThat(paymentAllocation.getPrincipalPaid().getAmount().doubleValue(), is(150d));
assertThat(paymentAllocation.getFeePaid(fee1.getAccountFeesActionDetailId()).getAmount().doubleValue(), is(5d));
assertThat(paymentAllocation.getFeePaid(fee2.getAccountFeesActionDetailId()).getAmount().doubleValue(), is(4d));
assertThat(paymentAllocation.getFeePaid(fee3.getAccountFeesActionDetailId()).getAmount().doubleValue(), is(3d));
assertThat(paymentAllocation.getFeePaid(fee4.getAccountFeesActionDetailId()).getAmount().doubleValue(), is(2d));
assertThat(paymentAllocation.getFeePaid(fee5.getAccountFeesActionDetailId()).getAmount().doubleValue(), is(1d));
assertThat(paymentAllocation.getFeePaid(fee6.getAccountFeesActionDetailId()).getAmount().doubleValue(), is(1d));
assertThat(balance.getAmount().doubleValue(), is(819d));
verify(loanBO, atLeastOnce()).getCurrency();
}
@Test
public void shouldPartiallyPayComponentsAndAllocatePayments() {
loanScheduleEntity.setPrincipal(makeMoney(30));
loanScheduleEntity.setPrincipalPaid(makeMoney(0));
loanScheduleEntity.setInterest(makeMoney(20));
loanScheduleEntity.setInterestPaid(makeMoney(0));
loanScheduleEntity.setPenalty(makeMoney(10));
loanScheduleEntity.setPenaltyPaid(makeMoney(0));
loanScheduleEntity.setMiscFee(makeMoney(0));
loanScheduleEntity.setMiscFeePaid(makeMoney(0));
loanScheduleEntity.setMiscPenalty(makeMoney(0));
loanScheduleEntity.setMiscPenaltyPaid(makeMoney(0));
loanScheduleEntity.setAccount(loanBO);
when(loanBO.getCurrency()).thenReturn(RUPEE);
Money paymentAmount = makeMoney(15d);
Money balance = loanScheduleEntity.payComponents(paymentAmount, paymentDate);
PaymentAllocation paymentAllocation = loanScheduleEntity.getPaymentAllocation();
assertThat(paymentAllocation.getPenaltyPaid().getAmount().doubleValue(), is(10d));
assertThat(paymentAllocation.getInterestPaid().getAmount().doubleValue(), is(5d));
assertThat(paymentAllocation.getPrincipalPaid().getAmount().doubleValue(), is(0d));
assertThat(balance.getAmount().doubleValue(), is(0d));
verify(loanBO, atLeastOnce()).getCurrency();
}
@Test
public void shouldDetermineWhetherPaymentApplied() {
when(loanBO.getCurrency()).thenReturn(RUPEE);
loanScheduleEntity.setAccount(loanBO);
loanScheduleEntity.setPrincipalPaid(makeMoney(0));
loanScheduleEntity.setInterestPaid(makeMoney(0));
loanScheduleEntity.setPenaltyPaid(makeMoney(0));
loanScheduleEntity.setMiscFeePaid(makeMoney(0));
loanScheduleEntity.setMiscPenaltyPaid(makeMoney(0));
loanScheduleEntity.setExtraInterestPaid(makeMoney(10));
assertThat(loanScheduleEntity.isPaymentApplied(), is(true));
verify(loanBO).getCurrency();
}
@Test
public void shouldPopulateComputedInterestForPAWDEPAdjustment() {
loanScheduleEntity.setAccount(loanBO);
loanScheduleEntity.setConfigService(configService);
when(loanBO.getCurrency()).thenReturn(RUPEE);
when(loanBO.isDecliningBalanceInterestRecalculation()).thenReturn(true);
when(configService.isRecalculateInterestEnabled()).thenReturn(false);
when(accountPayment.getAccount()).thenReturn(loanBO);
LoanTrxnDetailEntity loanReverseTrxn = new LoanTrxnDetailEntity(accountPayment,
AccountActionTypes.LOAN_ADJUSTMENT, Short.valueOf("1"), paymentDate,
personnel, paymentDate,
makeMoney(280), "test for loan adjustment", null, makeMoney(-14.06),
makeMoney(-3.3), makeMoney(0), makeMoney(0), makeMoney(0), null, null);
CalculatedInterestOnPayment interestOnPayment = new CalculatedInterestOnPayment();
interestOnPayment.setExtraInterestPaid(makeMoney(0.8d));
interestOnPayment.setOriginalInterest(makeMoney(14.96d));
interestOnPayment.setInterestDueTillPaid(makeMoney(2.5d));
loanReverseTrxn.setCalculatedInterestOnPayment(interestOnPayment);
loanScheduleEntity.setPrincipalPaid(makeMoney(14.06d));
loanScheduleEntity.setInterestPaid(makeMoney(2.5d));
loanScheduleEntity.setExtraInterestPaid(makeMoney(0.8d));
loanScheduleEntity.setPenaltyPaid(makeMoney(0d));
loanScheduleEntity.setMiscPenaltyPaid(makeMoney(0d));
loanScheduleEntity.setMiscFeePaid(makeMoney(0d));
loanScheduleEntity.updatePaymentDetailsForAdjustment(loanReverseTrxn);
assertThat(loanScheduleEntity.getPrincipalPaidAsDouble(), is(0d));
assertThat(loanScheduleEntity.getInterest().getAmount().doubleValue(), is(14.96d));
assertThat(loanScheduleEntity.getInterestPaidAsDouble(), is(0d));
assertThat(loanScheduleEntity.getExtraInterestPaidAsDouble(), is(0d));
}
@Test
public void shouldNotPopulateComputedInterestForPAWDEPAdjustmentForFutureInstallment() {
loanScheduleEntity.setAccount(loanBO);
loanScheduleEntity.setConfigService(configService);
when(loanBO.getCurrency()).thenReturn(RUPEE);
when(loanBO.isDecliningBalanceInterestRecalculation()).thenReturn(true);
when(configService.isRecalculateInterestEnabled()).thenReturn(false);
when(accountPayment.getAccount()).thenReturn(loanBO);
LoanTrxnDetailEntity loanReverseTrxn = new LoanTrxnDetailEntity(accountPayment,
AccountActionTypes.LOAN_ADJUSTMENT, Short.valueOf("1"), paymentDate,
personnel, paymentDate,
makeMoney(280), "test for loan adjustment", null, makeMoney(-14.06),
makeMoney(-2.5), makeMoney(0), makeMoney(0), makeMoney(0), null, null);
loanScheduleEntity.setInterest(makeMoney(14.96d));
loanScheduleEntity.setPrincipalPaid(makeMoney(14.06d));
loanScheduleEntity.setInterestPaid(makeMoney(2.5d));
loanScheduleEntity.setExtraInterestPaid(makeMoney(0d));
loanScheduleEntity.setPenaltyPaid(makeMoney(0d));
loanScheduleEntity.setMiscPenaltyPaid(makeMoney(0d));
loanScheduleEntity.setMiscFeePaid(makeMoney(0d));
loanScheduleEntity.updatePaymentDetailsForAdjustment(loanReverseTrxn);
assertThat(loanScheduleEntity.getPrincipalPaidAsDouble(), is(0d));
assertThat(loanScheduleEntity.getInterest().getAmount().doubleValue(), is(14.96d));
assertThat(loanScheduleEntity.getInterestPaidAsDouble(), is(0d));
assertThat(loanScheduleEntity.getExtraInterestPaidAsDouble(), is(0d));
}
@Test
public void shouldPopulateComputedInterestForNormalAdjustment() {
loanScheduleEntity.setAccount(loanBO);
loanScheduleEntity.setConfigService(configService);
when(loanBO.getCurrency()).thenReturn(RUPEE);
when(loanBO.isDecliningBalanceInterestRecalculation()).thenReturn(false);
when(configService.isRecalculateInterestEnabled()).thenReturn(false);
when(accountPayment.getAccount()).thenReturn(loanBO);
LoanTrxnDetailEntity loanReverseTrxn = new LoanTrxnDetailEntity(accountPayment,
AccountActionTypes.LOAN_ADJUSTMENT, Short.valueOf("1"), paymentDate,
personnel, paymentDate,
makeMoney(280), "test for loan adjustment", null, makeMoney(-14.06),
makeMoney(-2.5), makeMoney(0), makeMoney(0), makeMoney(0), null, null);
loanScheduleEntity.setInterest(makeMoney(14.96d));
loanScheduleEntity.setPrincipalPaid(makeMoney(14.06d));
loanScheduleEntity.setInterestPaid(makeMoney(2.5d));
loanScheduleEntity.setExtraInterestPaid(makeMoney(0d));
loanScheduleEntity.setPenaltyPaid(makeMoney(0d));
loanScheduleEntity.setMiscPenaltyPaid(makeMoney(0d));
loanScheduleEntity.setMiscFeePaid(makeMoney(0d));
loanScheduleEntity.updatePaymentDetailsForAdjustment(loanReverseTrxn);
assertThat(loanScheduleEntity.getPrincipalPaidAsDouble(), is(0d));
assertThat(loanScheduleEntity.getInterest().getAmount().doubleValue(), is(14.96d));
assertThat(loanScheduleEntity.getInterestPaidAsDouble(), is(0d));
assertThat(loanScheduleEntity.getExtraInterestPaidAsDouble(), is(0d));
}
@Test
public void testRecordAdjustmentSinglePaymentExists() {
loanScheduleEntity.setAccount(loanBO);
when(loanBO.getLastPmntToBeAdjusted()).thenReturn(null);
loanScheduleEntity.recordForAdjustment();
Assert.assertEquals(PaymentStatus.UNPAID, loanScheduleEntity.getPaymentStatusAsEnum());
Assert.assertNull(loanScheduleEntity.getPaymentDate());
verify(loanBO, Mockito.times(1)).getLastPmntToBeAdjusted();
}
@Test
public void testRecordAdjustmentWhenMultiplePaymentsExist() {
loanScheduleEntity.setAccount(loanBO);
when(loanBO.getLastPmntToBeAdjusted()).thenReturn(accountPaymentEntity);
Date dateOfPaymentPriorToAdjustedOne = new Date();
when(accountPaymentEntity.getPaymentDate()).thenReturn(dateOfPaymentPriorToAdjustedOne);
loanScheduleEntity.recordForAdjustment();
Assert.assertEquals(PaymentStatus.UNPAID, loanScheduleEntity.getPaymentStatusAsEnum());
Assert.assertEquals(dateOfPaymentPriorToAdjustedOne, loanScheduleEntity.getPaymentDate());
verify(accountPaymentEntity, Mockito.times(1)).getPaymentDate();
verify(loanBO, Mockito.times(1)).getLastPmntToBeAdjusted();
}
private LoanFeeScheduleEntity getFee(int id, double amount, double amountPaid) {
LoanFeeScheduleEntity loanFeeScheduleEntity = new LoanFeeScheduleEntity();
loanFeeScheduleEntity.setAccountFeesActionDetailId(id);
loanFeeScheduleEntity.setFeeAmount(makeMoney(amount));
loanFeeScheduleEntity.setFeeAmountPaid(makeMoney(amountPaid));
return loanFeeScheduleEntity;
}
private Money makeMoney(double amount) {
return new Money(RUPEE, amount);
}
}