/* * 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 java.util.Arrays.asList; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mifos.framework.TestUtils.getDate; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mifos.accounts.business.AccountActionEntity; import org.mifos.accounts.business.AccountPaymentEntity; import org.mifos.accounts.business.AccountTrxnEntity; import org.mifos.accounts.loan.business.matchers.LoanScheduleEntityMatcher; 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.loan.schedule.domain.Schedule; import org.mifos.accounts.loan.schedule.domain.ScheduleMatcher; import org.mifos.accounts.util.helpers.PaymentStatus; import org.mifos.application.master.business.MifosCurrency; import org.mifos.customers.personnel.business.PersonnelBO; import org.mifos.framework.exceptions.PersistenceException; import org.mifos.framework.util.CollectionUtils; import org.mifos.framework.util.helpers.Transformer; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; // TODO: Test data instantiation inside this class needs improvement - johnvic/buddy @RunWith(MockitoJUnitRunner.class) public class ScheduleMapperTest { private MifosCurrency rupee = new MifosCurrency(Short.valueOf("1"), "Rupee", BigDecimal.valueOf(1), "INR"); private static final Double DAILY_INTEREST_RATE = 0.000658; private static BigDecimal LOAN_AMOUNT = new BigDecimal(1000); private static final Date DISBURSEMENT_DATE = getDate(23, 9, 2010); private ScheduleMapper scheduleMapper; @Mock private LoanBO loanBO; @Mock private PersonnelBO personnelBO; @Mock private AccountPaymentEntity accountPaymentEntity; @Mock private LegacyLoanDao legacyLoanDao; @Mock private AccountActionEntity accountActionEntity; @Before public void setUp() { scheduleMapper = new ScheduleMapper(); when(loanBO.getCurrency()).thenReturn(rupee); } @Test public void shouldMapLoanScheduleEntityToSchedule() { Collection<LoanScheduleEntity> loanScheduleEntities = getLoanScheduleEntities(getDate(24, 11, 2010)); Schedule schedule = scheduleMapper.mapToSchedule(loanScheduleEntities, DISBURSEMENT_DATE, DAILY_INTEREST_RATE, LOAN_AMOUNT); assertThat(schedule, new ScheduleMatcher(getSchedule())); } @Test public void shouldMapScheduleToLoanScheduleEntity() throws PersistenceException { AccountPaymentEntity accountPaymentEntity = new AccountPaymentEntity(loanBO, null, null, null, null, null); LoanScheduleEntity scheduleEntityForPopulateTestInput = getLoanScheduleEntityForPopulateTestInput(); Set<LoanScheduleEntity> loanScheduleEntities = new LinkedHashSet<LoanScheduleEntity>(); loanScheduleEntities.add(scheduleEntityForPopulateTestInput); when(loanBO.getLoanScheduleEntities()).thenReturn(loanScheduleEntities); when(loanBO.getlegacyLoanDao()).thenReturn(legacyLoanDao); Date paymentDate = getDate(24, 11, 2010); Schedule schedule = getScheduleWithSingleInstallment(); scheduleMapper.populatePaymentDetails(schedule, loanBO, paymentDate, personnelBO, accountPaymentEntity); assertCalculatedInterestOnPayment(accountPaymentEntity); assertThat(getLoanScheduleEntity(paymentDate), new LoanScheduleEntityMatcher(scheduleEntityForPopulateTestInput)); verify(loanBO, times(1)).getLoanScheduleEntities(); verify(loanBO, times(1)).getlegacyLoanDao(); verify(loanBO, times(1)).recordSummaryAndPerfHistory(anyBoolean(), Matchers.<PaymentAllocation>any()); } private void assertCalculatedInterestOnPayment(AccountPaymentEntity accountPaymentEntity) { List<AccountTrxnEntity> accountTrxns = new ArrayList<AccountTrxnEntity>(accountPaymentEntity.getAccountTrxns()); assertThat(accountTrxns.size(), is(1)); LoanTrxnDetailEntity loanTrxnDetailEntity = (LoanTrxnDetailEntity) accountTrxns.get(0); CalculatedInterestOnPayment calculatedInterestOnPayment = loanTrxnDetailEntity.getCalculatedInterestOnPayment(); assertThat(calculatedInterestOnPayment, is(not(nullValue()))); assertThat(calculatedInterestOnPayment.getOriginalInterest().getAmount().doubleValue(), is(10d)); assertThat(calculatedInterestOnPayment.getExtraInterestPaid().getAmount().doubleValue(), is(4.5d)); assertThat(calculatedInterestOnPayment.getInterestDueTillPaid().getAmount().doubleValue(), is(5d)); } @Test public void shouldPopulateExtraInterestInLoanScheduleEntities() { List<Installment> installments = getInstallments(0, 0, 0); Schedule schedule = new Schedule(DISBURSEMENT_DATE, DAILY_INTEREST_RATE, LOAN_AMOUNT, installments); for (int i = 0; i < installments.size(); i++) { installments.get(i).setExtraInterest(new BigDecimal(i)); } Collection<LoanScheduleEntity> loanScheduleEntities = getLoanScheduleEntities(getDate(24, 11, 2010)); Map<Integer, LoanScheduleEntity> loanScheduleEntityMap = getLoanScheduleEntityMap(loanScheduleEntities); scheduleMapper.populateExtraInterestInLoanScheduleEntities(schedule, loanScheduleEntityMap); assertExtraInterest(loanScheduleEntityMap.get(1), 0d); assertExtraInterest(loanScheduleEntityMap.get(2), 1d); assertExtraInterest(loanScheduleEntityMap.get(3), 2d); } private Collection<LoanScheduleEntity> getLoanScheduleEntities(Date paymentDate) { LoanScheduleEntity loanScheduleEntity1 = new LoanScheduleBuilder("1", loanBO). withDueDate(getDate(23, 10, 2010)).withPaymentStatus(PaymentStatus.PAID). withPaymentDate(paymentDate). withPrincipal(100).withPrincipalPaid(100). withInterest(10).withInterestPaid(10). withExtraInterest(9).withExtraInterestPaid(9). withMiscFees(8).withMiscFeesPaid(8). withPenalty(7).withPenaltyPaid(7). withMiscPenalty(6).withMiscPenaltyPaid(6). addFees(1, 5, 5).build(); LoanScheduleEntity loanScheduleEntity2 = new LoanScheduleBuilder("2", loanBO). withDueDate(getDate(23, 11, 2010)).withPaymentStatus(PaymentStatus.UNPAID). withPaymentDate(paymentDate). withPrincipal(1000).withPrincipalPaid(100). withInterest(100).withInterestPaid(10). withExtraInterest(90).withExtraInterestPaid(9). withMiscFees(80).withMiscFeesPaid(8). withPenalty(70).withPenaltyPaid(7). withMiscPenalty(60).withMiscPenaltyPaid(6). addFees(1, 50, 5).build(); LoanScheduleEntity loanScheduleEntity3 = new LoanScheduleBuilder("3", loanBO). withDueDate(getDate(23, 12, 2010)).withPaymentStatus(PaymentStatus.UNPAID). withPrincipal(1000).withPrincipalPaid(0). withInterest(100).withInterestPaid(0). withExtraInterest(90).withExtraInterestPaid(0). withMiscFees(80).withMiscFeesPaid(0). withPenalty(70).withPenaltyPaid(0). withMiscPenalty(60).withMiscPenaltyPaid(0). addFees(1, 50, 0).build(); return Arrays.asList(loanScheduleEntity1, loanScheduleEntity2, loanScheduleEntity3); } private LoanScheduleEntity getLoanScheduleEntity(Date paymentDate) { return new LoanScheduleBuilder("1", loanBO). withDueDate(getDate(23, 10, 2010)).withPaymentStatus(PaymentStatus.PAID). withPaymentDate(paymentDate). withPrincipal(100).withPrincipalPaid(100). withInterest(10).withInterestPaid(10). withExtraInterest(9).withExtraInterestPaid(9). withMiscFees(8).withMiscFeesPaid(8). withPenalty(7).withPenaltyPaid(7). withMiscPenalty(6).withMiscPenaltyPaid(6). addFees(1, 5, 5).build(); } private LoanScheduleEntity getLoanScheduleEntityForPopulateTestInput() { return new LoanScheduleBuilder("1", loanBO). withDueDate(getDate(23, 10, 2010)). withPaymentDate(getDate(10, 11, 2010)). withPaymentStatus(PaymentStatus.UNPAID). withPrincipal(100).withPrincipalPaid(50). withInterest(10).withInterestPaid(5). withExtraInterest(9).withExtraInterestPaid(4.5). withMiscFees(8).withMiscFeesPaid(4). withPenalty(7).withPenaltyPaid(3.5). withMiscPenalty(6).withMiscPenaltyPaid(3). addFees(1, 5, 4).build(); } public Schedule getSchedule() { Installment installment1 = new InstallmentBuilder("1"). withDueDate(getDate(23, 10, 2010)). withPrincipal(100).withPrincipalPaid(100). withInterest(10).withInterestPaid(10). withExtraInterest(9).withExtraInterestPaid(9). withMiscFees(8).withMiscFeesPaid(8). withPenalty(7).withPenaltyPaid(7). withMiscPenalty(6).withMiscPenaltyPaid(6). withFees(5).withFeesPaid(5). build(); Installment installment2 = new InstallmentBuilder("2"). withDueDate(getDate(23, 11, 2010)). withPrincipal(1000).withPrincipalPaid(100). withInterest(100).withInterestPaid(10). withExtraInterest(90).withExtraInterestPaid(9). withMiscFees(80).withMiscFeesPaid(8). withPenalty(70).withPenaltyPaid(7). withMiscPenalty(60).withMiscPenaltyPaid(6). withFees(50).withFeesPaid(5). build(); Installment installment3 = new InstallmentBuilder("3"). withDueDate(getDate(23, 12, 2010)). withPrincipal(1000).withPrincipalPaid(0). withInterest(100).withInterestPaid(0). withExtraInterest(90).withExtraInterestPaid(0). withMiscFees(80).withMiscFeesPaid(0). withPenalty(70).withPenaltyPaid(0). withMiscPenalty(60).withMiscPenaltyPaid(0). withFees(50).withFeesPaid(0). build(); List<Installment> installments = Arrays.asList(installment1, installment2, installment3); return new Schedule(DISBURSEMENT_DATE, DAILY_INTEREST_RATE, LOAN_AMOUNT, installments); } public Schedule getScheduleWithSingleInstallment() { Installment installment1 = new InstallmentBuilder("1"). withDueDate(getDate(23, 10, 2010)). withPrincipal(100).withPrincipalPaid(50). withInterest(10).withInterestPaid(5). withExtraInterest(9).withExtraInterestPaid(4.5). withMiscFees(8).withMiscFeesPaid(4). withPenalty(7).withPenaltyPaid(3.5). withMiscPenalty(6).withMiscPenaltyPaid(3). withFees(5).withFeesPaid(2.5). build(); return new Schedule(DISBURSEMENT_DATE, DAILY_INTEREST_RATE, LOAN_AMOUNT, Arrays.asList(installment1)); } private void assertExtraInterest(LoanScheduleEntity loanScheduleEntity, double extraInterest) { assertThat(loanScheduleEntity.getExtraInterest().getAmount().doubleValue(), is(extraInterest)); } private List<Installment> getInstallments(double... extraInterest) { Installment installment1 = new InstallmentBuilder("1").withDueDate(getDate(23, 10, 2010)).withPrincipal(100) .withInterest(10).withExtraInterest(extraInterest[0]).build(); Installment installment2 = new InstallmentBuilder("2").withDueDate(getDate(23, 10, 2010)).withPrincipal(100) .withInterest(10).withExtraInterest(extraInterest[1]).build(); Installment installment3 = new InstallmentBuilder("3").withDueDate(getDate(23, 12, 2010)).withPrincipal(100) .withInterest(10).withExtraInterest(extraInterest[2]).build(); return asList(installment1, installment2, installment3); } private Map<Integer, LoanScheduleEntity> getLoanScheduleEntityMap(Collection<LoanScheduleEntity> loanScheduleEntities) { return CollectionUtils.asValueMap(loanScheduleEntities, new Transformer<LoanScheduleEntity, Integer>() { @Override public Integer transform(LoanScheduleEntity input) { return Integer.valueOf(input.getInstallmentId()); } }); } // TODO: This matcher currently verifies only the CalculatedInterestOnPayment details. Needs extension to cover other fields !!! private class LoanTrxnDetailEntityMatcher extends TypeSafeMatcher<LoanTrxnDetailEntity> { private double originalInterest; private double extraInterestPaid; public LoanTrxnDetailEntityMatcher(double originalInterest, double extraInterestPaid) { this.originalInterest = originalInterest; this.extraInterestPaid = extraInterestPaid; } @Override public boolean matchesSafely(LoanTrxnDetailEntity loanTrxnDetailEntity) { CalculatedInterestOnPayment calculatedInterestOnPayment = loanTrxnDetailEntity.getCalculatedInterestOnPayment(); return calculatedInterestOnPayment != null && calculatedInterestOnPayment.getOriginalInterest().getAmount().doubleValue() == originalInterest && calculatedInterestOnPayment.getExtraInterestPaid().getAmount().doubleValue() == originalInterest; } @Override public void describeTo(Description description) { description.appendText("LoanTrxnDetailEntity did not match"); } } }