/* * 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.framework.components.batchjobs.helpers; import static org.mifos.application.meeting.util.helpers.MeetingType.CUSTOMER_MEETING; import static org.mifos.application.meeting.util.helpers.RecurrenceType.WEEKLY; import static org.mifos.framework.util.helpers.TestObjectFactory.EVERY_WEEK; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Date; import java.util.List; import junit.framework.Assert; import org.hibernate.Query; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.mifos.accounts.loan.business.LoanArrearsAgingEntity; import org.mifos.accounts.loan.business.LoanBO; import org.mifos.accounts.loan.persistance.LegacyLoanDao; import org.mifos.accounts.productdefinition.business.LoanOfferingBO; import org.mifos.accounts.util.helpers.AccountState; import org.mifos.accounts.util.helpers.PaymentData; import org.mifos.application.meeting.business.MeetingBO; import org.mifos.application.meeting.util.helpers.MeetingType; import org.mifos.application.meeting.util.helpers.RecurrenceType; import org.mifos.application.meeting.util.helpers.WeekDay; import org.mifos.builders.MifosUserBuilder; import org.mifos.clientportfolio.loan.service.RecurringSchedule; import org.mifos.clientportfolio.newloan.applicationservice.CreateLoanAccount; import org.mifos.config.AccountingRulesConstants; import org.mifos.config.business.Configuration; import org.mifos.config.business.MifosConfigurationManager; import org.mifos.customers.business.CustomerBO; import org.mifos.customers.util.helpers.CustomerStatus; import org.mifos.dto.domain.CreateAccountFeeDto; import org.mifos.dto.domain.CreateAccountPenaltyDto; import org.mifos.framework.MifosIntegrationTestCase; import org.mifos.framework.components.batchjobs.exceptions.BatchJobException; import org.mifos.framework.exceptions.PersistenceException; import org.mifos.framework.hibernate.helper.StaticHibernateUtil; import org.mifos.framework.util.DateTimeService; import org.mifos.framework.util.helpers.IntegrationTestObjectMother; import org.mifos.framework.util.helpers.Money; import org.mifos.framework.util.helpers.TestObjectFactory; import org.mifos.security.MifosUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; public class LoanArrearsAgingHelperIntegrationTest extends MifosIntegrationTestCase { @Autowired private LegacyLoanDao legacyLoanDao; private LoanArrearsAgingHelper loanArrearsAgingHelper; private LoanArrearsHelper loanArrearHelper; private CustomerBO center; private CustomerBO group; private MeetingBO meeting; private DateTime dateTime; private final long dummy = 0; // used for calling execute metohod private final int loanAmount = 100; private final double interestRate = 100.0; private final int totalInterest = 20; private final short numInstallments = 10; private final int principalForOneInstallment = loanAmount / numInstallments; private final int interestForOneInstallment = totalInterest / numInstallments; private final int onePayment = principalForOneInstallment + interestForOneInstallment; private final int repaymentInterval = 7; // 7 days for weekly loan repayment private static String oldRoundingMode; @Before public void setUp() throws Exception { StaticHibernateUtil.getSessionTL().clear(); oldRoundingMode = (String) MifosConfigurationManager.getInstance().getProperty(AccountingRulesConstants.CURRENCY_ROUNDING_MODE); //FIXME this test was using CELLING ROUNDING MODE due to MIFOS-2659 MifosConfigurationManager.getInstance().setProperty(AccountingRulesConstants.CURRENCY_ROUNDING_MODE, RoundingMode.CEILING.toString()); /* * Takes lateness days out of the equation so that the LoanArrearsTask will set all overdue account into bad * standing. * * Resetting the database in tear down puts the original value back for other tests */ setLatenessDaysToZero(); LoanArrearsTask arrearsTask = new LoanArrearsTask(); loanArrearHelper = (LoanArrearsHelper) arrearsTask.getTaskHelper(); LoanArrearsAgingTask loanArrearsAgingTask = new LoanArrearsAgingTask(); loanArrearsAgingHelper = (LoanArrearsAgingHelper) loanArrearsAgingTask.getTaskHelper(); dateTime = initializeToFixedDateTime(); SecurityContext securityContext = new SecurityContextImpl(); MifosUser principal = new MifosUserBuilder().nonLoanOfficer().withAdminRole().build(); Authentication authentication = new TestingAuthenticationToken(principal, principal); securityContext.setAuthentication(authentication); SecurityContextHolder.setContext(securityContext); } @After public void tearDown() throws Exception { new DateTimeService().resetToCurrentSystemDateTime(); loanArrearsAgingHelper = null; loanArrearHelper = null; MifosConfigurationManager.getInstance().setProperty(AccountingRulesConstants.CURRENCY_ROUNDING_MODE, oldRoundingMode); StaticHibernateUtil.flushAndClearSession(); } private LoanBO setUpLoan(DateTime dateTime, AccountState accountState) { Date startDate = dateTime.toDate(); meeting = getNewMeeting(WEEKLY, EVERY_WEEK, CUSTOMER_MEETING, WeekDay.MONDAY, startDate); center = TestObjectFactory.createWeeklyFeeCenter(this.getClass().getSimpleName() + " Center", meeting); group = TestObjectFactory.createWeeklyFeeGroupUnderCenter(this.getClass().getSimpleName() + " Group", CustomerStatus.GROUP_ACTIVE, center); LoanOfferingBO product = TestObjectFactory.createLoanOffering("LoanProduct", "LP", startDate, meeting); LoanBO loan = createBasicLoanAccount(group, accountState, product, "" + loanAmount, numInstallments, interestRate); return loan; } private LoanBO createBasicLoanAccount(final CustomerBO customer, final AccountState state, final LoanOfferingBO loanOffering, String loanAmountAsString, short numInstallments, double interestRate) { MeetingBO meeting = TestObjectFactory.createLoanMeeting(customer.getCustomerMeeting().getMeeting()); List<Date> meetingDates = TestObjectFactory.getMeetingDates(customer.getOfficeId(), meeting, numInstallments); LoanOfferingBO loanProduct = IntegrationTestObjectMother.findLoanProductBySystemId(loanOffering.getGlobalPrdOfferingNum()); BigDecimal loanAmount = BigDecimal.valueOf(Double.valueOf(loanAmountAsString)); BigDecimal minAllowedLoanAmount = loanAmount; BigDecimal maxAllowedLoanAmount = loanAmount; LocalDate disbursementDate = new LocalDate(meetingDates.get(0)); int numberOfInstallments = numInstallments; int minAllowedNumberOfInstallments = loanProduct.getEligibleInstallmentSameForAllLoan().getMaxNoOfInstall(); int maxAllowedNumberOfInstallments = loanProduct.getEligibleInstallmentSameForAllLoan().getMaxNoOfInstall(); int graceDuration = 0; Integer sourceOfFundId = null; Integer loanPurposeId = null; Integer collateralTypeId = null; String collateralNotes = null; String externalId = null; boolean repaymentScheduleIndependentOfCustomerMeeting = false; RecurringSchedule recurringSchedule = null; List<CreateAccountFeeDto> accountFees = new ArrayList<CreateAccountFeeDto>(); CreateLoanAccount createLoanAccount = new CreateLoanAccount(customer.getCustomerId(), loanProduct.getPrdOfferingId().intValue(), state.getValue().intValue(), loanAmount, minAllowedLoanAmount, maxAllowedLoanAmount, interestRate, disbursementDate, null, numberOfInstallments, minAllowedNumberOfInstallments, maxAllowedNumberOfInstallments, graceDuration, sourceOfFundId, loanPurposeId, collateralTypeId, collateralNotes, externalId, repaymentScheduleIndependentOfCustomerMeeting, recurringSchedule, accountFees, new ArrayList<CreateAccountPenaltyDto>()); return IntegrationTestObjectMother.createClientLoan(createLoanAccount); } /* * This is a method taken from TestObjectFactory and modified to make it configurable. This could be pushed back up * to TestObjectFactory or better yet converted to a builder pattern. */ private MeetingBO getNewMeeting(final RecurrenceType frequency, final short recurAfter, final MeetingType meetingType, final WeekDay weekday, Date theDate) { MeetingBO meeting; try { meeting = new MeetingBO(frequency, recurAfter, theDate, meetingType); meeting.setMeetingPlace("Loan Meeting Place"); } catch (Exception e) { throw new RuntimeException(e); } meeting.getMeetingDetails().getMeetingRecurrence().setWeekDay(weekday); return meeting; } private DateTime initializeToFixedDateTime() { DateTime dateTime = new DateTime(2009, 12, 7, 0, 0, 0, 0); DateTimeService dateTimeService = new DateTimeService(); dateTimeService.setCurrentDateTimeFixed(dateTime); return dateTime; } @Test public void testThatLoanInPendingApprovalStateDoesNotGenerateArrearsData() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_PENDING_APPROVAL); Assert.assertNull(ageLoanTenDaysAndGetLoanArrearsAgingEntity(loan)); } @Test public void testThatLoanInActiveInBadStandingStateDoesGenerateArrearsData() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_BAD_STANDING); Assert.assertNotNull(ageLoanTenDaysAndGetLoanArrearsAgingEntity(loan)); } private LoanArrearsAgingEntity ageLoanTenDaysAndGetLoanArrearsAgingEntity(LoanBO loan) throws BatchJobException, PersistenceException { int daysOverdue = 3; return ageLoanAndGetLoanArrearsAgingEntity(loan, repaymentInterval + daysOverdue); } private LoanArrearsAgingEntity ageLoanAndGetLoanArrearsAgingEntity(LoanBO loan, int daysToAgeLoan) throws BatchJobException, PersistenceException { Assert.assertNull(loan.getLoanArrearsAgingEntity()); new DateTimeService().setCurrentDateTimeFixed(dateTime.plusDays(daysToAgeLoan)); runLoanArrearsThenLoanArrearsAging(); StaticHibernateUtil.flushAndClearSession(); LoanBO reloadedLoan = legacyLoanDao.getAccount(loan.getAccountId()); return reloadedLoan.getLoanArrearsAgingEntity(); } // create a weekly 10 week loan of 100 with flat 100% interest (total interest is 20 or 2 each week) // advance the date by ten days, run the batch job // one weeks payment should be in arrears @Ignore @Test public void testLoanWithOneOverduePayment() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING); LoanArrearsAgingEntity loanArrearsAgingEntity = ageLoanTenDaysAndGetLoanArrearsAgingEntity(loan); Assert.assertEquals(new Money(getCurrency(), "" + loanAmount), loanArrearsAgingEntity.getUnpaidPrincipal()); Assert.assertEquals(new Money(getCurrency(), "" + totalInterest), loanArrearsAgingEntity.getUnpaidInterest()); Assert.assertEquals(new Money(getCurrency(), "" + principalForOneInstallment), loanArrearsAgingEntity .getOverduePrincipal()); Assert.assertEquals(new Money(getCurrency(), "" + interestForOneInstallment), loanArrearsAgingEntity .getOverdueInterest()); Assert.assertEquals(new Money(getCurrency(), "" + (principalForOneInstallment + interestForOneInstallment)), loanArrearsAgingEntity.getOverdueBalance()); Assert.assertEquals(Short.valueOf("3"), loanArrearsAgingEntity.getDaysInArrears()); assertForLoanArrearsAgingEntity(loan); } @Test public void testLoanWithTwoOverduePayments() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING); int daysOverdue = 10; LoanArrearsAgingEntity loanArrearsAgingEntity = ageLoanAndGetLoanArrearsAgingEntity(loan, repaymentInterval + daysOverdue); Assert.assertEquals(new Money(getCurrency(), "" + loanAmount), loanArrearsAgingEntity.getUnpaidPrincipal()); Assert.assertEquals(new Money(getCurrency(), "" + totalInterest), loanArrearsAgingEntity.getUnpaidInterest()); Assert.assertEquals(new Money(getCurrency(), "" + 2 * principalForOneInstallment), loanArrearsAgingEntity .getOverduePrincipal()); Assert.assertEquals(new Money(getCurrency(), "" + 2 * interestForOneInstallment), loanArrearsAgingEntity .getOverdueInterest()); Assert.assertEquals(new Money(getCurrency(), "" + (2 * (principalForOneInstallment + interestForOneInstallment))), loanArrearsAgingEntity .getOverdueBalance()); Assert.assertEquals(daysOverdue, loanArrearsAgingEntity.getDaysInArrears().intValue()); } /** * unsure why its failing. */ @Ignore @Test public void testLoanWithOnePaymentMadeAndOneOverduePayments() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING); Assert.assertNull(loan.getLoanArrearsAgingEntity()); short daysOverdue = 3; // advance time daysOverdue past the second repayment interval dateTime = dateTime.plusDays(2 * repaymentInterval + daysOverdue); new DateTimeService().setCurrentDateTimeFixed(dateTime); // make one payment, so we should still be one payment behind after that PaymentData paymentData = PaymentData.createPaymentData(new Money(Configuration.getInstance().getSystemConfig() .getCurrency(), "" + onePayment), loan.getPersonnel(), Short.valueOf("1"), dateTime.toDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); runLoanArrearsThenLoanArrearsAging(); StaticHibernateUtil.flushAndClearSession(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNotNull(loan.getLoanArrearsAgingEntity()); LoanArrearsAgingEntity loanArrearsAgingEntity = loan.getLoanArrearsAgingEntity(); Assert.assertEquals(new Money(getCurrency(), "" + (loanAmount - principalForOneInstallment)), loanArrearsAgingEntity.getUnpaidPrincipal()); Assert.assertEquals(new Money(getCurrency(), "" + (totalInterest - interestForOneInstallment)), loanArrearsAgingEntity.getUnpaidInterest()); Assert.assertEquals(new Money(getCurrency(), "" + principalForOneInstallment), loanArrearsAgingEntity .getOverduePrincipal()); Assert.assertEquals(new Money(getCurrency(), "" + interestForOneInstallment), loanArrearsAgingEntity .getOverdueInterest()); Assert.assertEquals(new Money(getCurrency(), "" + (principalForOneInstallment + interestForOneInstallment)), loanArrearsAgingEntity.getOverdueBalance()); Assert.assertEquals(Short.valueOf(daysOverdue), loanArrearsAgingEntity.getDaysInArrears()); } @Ignore @Test public void testLoanWithOnePaymentMadeAndNoOverduePayments() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_BAD_STANDING); Assert.assertNull(loan.getLoanArrearsAgingEntity()); short daysPastPaymentDate = 3; // advance time daysOverdue past the second repayment interval dateTime = dateTime.plusDays(repaymentInterval + daysPastPaymentDate); new DateTimeService().setCurrentDateTimeFixed(dateTime); runLoanArrearsThenLoanArrearsAging(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNotNull(loan.getLoanArrearsAgingEntity()); // make one payment, so we should still be one payment behind after that PaymentData paymentData = PaymentData.createPaymentData(new Money(Configuration.getInstance().getSystemConfig() .getCurrency(), "" + onePayment), loan.getPersonnel(), Short.valueOf("1"), dateTime.toDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); //jpwrunLoanArrearsThenLoanArrearsAging(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNull(loan.getLoanArrearsAgingEntity()); Assert.assertTrue(loan.getState().equals(AccountState.LOAN_ACTIVE_IN_GOOD_STANDING)); } @Ignore @Test public void testLoanWithEarlyRepayment() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_BAD_STANDING); Assert.assertNull(loan.getLoanArrearsAgingEntity()); short daysPastPaymentDate = 3; // advance time daysOverdue past the second repayment interval dateTime = dateTime.plusDays(repaymentInterval + daysPastPaymentDate); new DateTimeService().setCurrentDateTimeFixed(dateTime); runLoanArrearsThenLoanArrearsAging(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNotNull(loan.getLoanArrearsAgingEntity()); Money totalAmount = new Money(Configuration.getInstance().getSystemConfig().getCurrency(), "" + loanAmount + totalInterest); String receiptNumber = "1"; loan.makeEarlyRepayment(null, loan.getPersonnel().getPersonnelId(), false, new Money(loan.getCurrency(), "0")); //jpw runLoanArrearsThenLoanArrearsAging(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNull(loan.getLoanArrearsAgingEntity()); Assert.assertTrue(loan.getState().equals(AccountState.LOAN_CLOSED_OBLIGATIONS_MET)); } @Ignore @Test public void testLoanWithEarlyRepaymentWithInterestWaiver() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING); Assert.assertNull(loan.getLoanArrearsAgingEntity()); short daysPastPaymentDate = 3; // advance time daysOverdue past the second repayment interval dateTime = dateTime.plusDays(repaymentInterval + daysPastPaymentDate); new DateTimeService().setCurrentDateTimeFixed(dateTime); runLoanArrearsThenLoanArrearsAging(); StaticHibernateUtil.flushAndClearSession(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNotNull(loan.getLoanArrearsAgingEntity()); Money totalAmount = new Money(Configuration.getInstance().getSystemConfig().getCurrency(), "" + loanAmount + 0); String receiptNumber = "1"; loan.makeEarlyRepayment(null, loan.getPersonnel().getPersonnelId(), true, new Money(loan.getCurrency(), "0")); //jpw runLoanArrearsThenLoanArrearsAging(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNull(loan.getLoanArrearsAgingEntity()); Assert.assertTrue(loan.getState().equals(AccountState.LOAN_CLOSED_OBLIGATIONS_MET)); } /** * unsure why its failing. */ @Ignore @Test public void testLoanWithOnePartialPayment() throws Exception { LoanBO loan = setUpLoan(dateTime, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING); Assert.assertNull(loan.getLoanArrearsAgingEntity()); short daysOverdue = 3; dateTime = dateTime.plusDays(repaymentInterval + daysOverdue); new DateTimeService().setCurrentDateTimeFixed(dateTime); // make one payment of 8. Interest is paid off first, so 2 goes to interest and 6 to principal int principalPayment = 6; int interestPayment = 2; int paymentAmount = interestPayment + principalPayment; // paymentAmount == 8 PaymentData paymentData = PaymentData.createPaymentData(new Money(Configuration.getInstance().getSystemConfig() .getCurrency(), "" + paymentAmount), loan.getPersonnel(), Short.valueOf("1"), dateTime.toDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); runLoanArrearsThenLoanArrearsAging(); StaticHibernateUtil.flushAndClearSession(); loan = legacyLoanDao.getAccount(loan.getAccountId()); Assert.assertNotNull(loan.getLoanArrearsAgingEntity()); LoanArrearsAgingEntity loanArrearsAgingEntity = loan.getLoanArrearsAgingEntity(); Assert.assertEquals(new Money(getCurrency(), "" + (loanAmount - principalPayment)), loanArrearsAgingEntity .getUnpaidPrincipal()); Assert.assertEquals(new Money(getCurrency(), "" + (totalInterest - interestPayment)), loanArrearsAgingEntity .getUnpaidInterest()); Assert.assertEquals(new Money(getCurrency(), "" + (principalForOneInstallment - principalPayment)), loanArrearsAgingEntity.getOverduePrincipal()); Assert.assertEquals(new Money(getCurrency(), "0"), loanArrearsAgingEntity.getOverdueInterest()); Assert.assertEquals(new Money(getCurrency(), "" + (principalForOneInstallment - principalPayment)), loanArrearsAgingEntity.getOverdueBalance()); Assert.assertEquals(Short.valueOf(daysOverdue), loanArrearsAgingEntity.getDaysInArrears()); } private void assertForLoanArrearsAgingEntity(LoanBO loan) throws PersistenceException { LoanBO loanAccount = IntegrationTestObjectMother.findLoanBySystemId(loan.getGlobalAccountNum()); LoanArrearsAgingEntity loanArrearsAgingEntity = loanAccount.getLoanArrearsAgingEntity(); Assert .assertEquals(loanAccount.getLoanSummary().getPrincipalDue(), loanArrearsAgingEntity .getUnpaidPrincipal()); Assert.assertEquals(loanAccount.getLoanSummary().getInterestDue(), loanArrearsAgingEntity.getUnpaidInterest()); Assert.assertEquals(loanAccount.getLoanSummary().getPrincipalDue().add( loanAccount.getLoanSummary().getInterestDue()), loanArrearsAgingEntity.getUnpaidBalance()); Assert.assertEquals(loanAccount.getTotalPrincipalAmountInArrears(), loanArrearsAgingEntity .getOverduePrincipal()); Assert.assertEquals(loanAccount.getTotalInterestAmountInArrears(), loanArrearsAgingEntity.getOverdueInterest()); Assert.assertEquals(loanAccount.getTotalPrincipalAmountInArrears().add( loanAccount.getTotalInterestAmountInArrears()), loanArrearsAgingEntity.getOverdueBalance()); Assert.assertEquals(group.getCustomerId(), loanArrearsAgingEntity.getCustomer().getCustomerId()); Assert.assertEquals(center.getCustomerId(), loanArrearsAgingEntity.getParentCustomer().getCustomerId()); Assert.assertEquals(group.getDisplayName(), loanArrearsAgingEntity.getCustomerName()); } private void setLatenessDaysToZero() throws Exception { StaticHibernateUtil.startTransaction(); Query updateQuery = StaticHibernateUtil.getSessionTL().createQuery( "update ProductTypeEntity set latenessDays = 0 where productTypeID = 1"); updateQuery.executeUpdate(); StaticHibernateUtil.flushAndClearSession(); } private void runLoanArrearsThenLoanArrearsAging() throws BatchJobException { loanArrearHelper.execute(dummy); loanArrearsAgingHelper.execute(dummy); } }