Java tutorial
/* * 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 junit.framework.Assert; import org.apache.commons.lang.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mifos.accounts.business.AccountActionDateEntity; import org.mifos.accounts.business.AccountBO; import org.mifos.accounts.business.AccountPaymentEntity; import org.mifos.accounts.business.AccountTrxnEntity; import org.mifos.accounts.exceptions.AccountException; import org.mifos.accounts.fees.business.FeeBO; import org.mifos.accounts.fees.business.FeeDto; import org.mifos.accounts.fees.util.helpers.FeeCategory; import org.mifos.accounts.fees.util.helpers.FeeFormula; import org.mifos.accounts.fees.util.helpers.FeeFrequencyType; import org.mifos.accounts.financial.business.FinancialTransactionBO; import org.mifos.accounts.financial.util.helpers.FinancialConstants; import org.mifos.accounts.loan.persistance.LegacyLoanDao; import org.mifos.accounts.productdefinition.business.LoanOfferingBO; import org.mifos.accounts.productdefinition.util.helpers.ApplicableTo; import org.mifos.accounts.productdefinition.util.helpers.GraceType; import org.mifos.accounts.productdefinition.util.helpers.InterestType; import org.mifos.accounts.productdefinition.util.helpers.PrdStatus; import org.mifos.accounts.util.helpers.AccountState; import org.mifos.accounts.util.helpers.PaymentData; import org.mifos.application.master.util.helpers.PaymentTypes; import org.mifos.application.meeting.business.MeetingBO; import org.mifos.application.meeting.exceptions.MeetingException; import org.mifos.application.meeting.util.helpers.RecurrenceType; import org.mifos.config.AccountingRules; import org.mifos.config.AccountingRulesConstants; import org.mifos.config.business.MifosConfigurationManager; import org.mifos.config.persistence.ConfigurationPersistence; import org.mifos.core.ClasspathResource; import org.mifos.customers.business.CustomerBO; import org.mifos.customers.personnel.business.PersonnelBO; import org.mifos.customers.util.helpers.CustomerStatus; import org.mifos.framework.MifosIntegrationTestCase; import org.mifos.framework.TestUtils; import org.mifos.framework.exceptions.ApplicationException; import org.mifos.framework.exceptions.PersistenceException; import org.mifos.framework.exceptions.PropertyNotFoundException; import org.mifos.framework.exceptions.SystemException; import org.mifos.framework.hibernate.helper.StaticHibernateUtil; import org.mifos.framework.persistence.TestObjectPersistence; 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.util.UserContext; import org.springframework.beans.factory.annotation.Autowired; import java.io.*; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.net.URISyntaxException; import java.util.*; import static org.apache.commons.lang.math.NumberUtils.DOUBLE_ZERO; import static org.apache.commons.lang.math.NumberUtils.SHORT_ZERO; import static org.mifos.application.meeting.util.helpers.MeetingType.CUSTOMER_MEETING; import static org.mifos.framework.util.helpers.TestObjectFactory.EVERY_MONTH; /* * LoanCalculationTest is a starting point for defining and exploring * expected behavior for different loan payment calculations. * */ public class LoanCalculationIntegrationTest extends MifosIntegrationTestCase { // these constants for parsing the spreadsheet final String principal = "Principal"; final String loanType = "Loan Type"; final String annualInterest = "Annual Interest"; final String numberOfPayments = "Number of Payments"; final String paymentFrequency = "Payment Frequency"; final String initialRoundingMode = "InitialRoundingMode"; final String initialRoundOffMultiple = "InitialRoundOffMultiple"; final String finalRoundingMode = "FinalRoundingMode"; final String finalRoundOffMultiple = "FinalRoundOffMultiple"; final String currencyRounding = "CurrencyRounding"; final String digitsAfterDecimal = "Digits After Decimal"; final String daysInYear = "Days in Year"; final String totals = "Summed Totals"; final String start = "Start"; final String gracePeriodType = "GracePeriodType"; final String gracePeriod = "GracePeriod"; final String calculatedTotals = "Calculated Totals"; final String feeFrequency = "FeeFrequency"; final String feeType = "FeeType"; final String feeValue = "FeeValue"; final String feePercentage = "FeePercentage"; final String feeTypePrincipalPlusInterest = "Principal+Interest"; final String feeTypeInterest = "Interest"; final String feeTypePrincipal = "Principal"; final String feeTypeValue = "Value"; final String account999 = "Account 999"; String rootPath = "org/mifos/accounts/loan/business/testCaseData/"; // TODO: probably should be of type LoanBO protected AccountBO accountBO = null; protected CustomerBO center = null; protected CustomerBO group = null; private CustomerBO client = null; private BigDecimal savedInitialRoundOffMultiple = null; private BigDecimal savedFinalRoundOffMultiple = null; private RoundingMode savedCurrencyRoundingMode = null; private RoundingMode savedInitialRoundingMode = null; private RoundingMode savedFinalRoundingMode = null; private Short savedDigitAfterDecimal; private int savedDaysInYear = 0; private UserContext userContext; private boolean allConsoleOutputEnabled = false; private boolean isFileNameConsoleOutputEnabled = false; @Autowired private LegacyLoanDao legacyLoanDao; @Before public void setUp() throws Exception { enableCustomWorkingDays(); userContext = TestObjectFactory.getContext(); savedInitialRoundOffMultiple = AccountingRules.getInitialRoundOffMultiple(); savedFinalRoundOffMultiple = AccountingRules.getFinalRoundOffMultiple(); savedCurrencyRoundingMode = AccountingRules.getCurrencyRoundingMode(); savedDigitAfterDecimal = AccountingRules.getDigitsAfterDecimal(); savedInitialRoundingMode = AccountingRules.getInitialRoundingMode(); savedFinalRoundingMode = AccountingRules.getFinalRoundingMode(); savedDaysInYear = AccountingRules.getNumberOfInterestDays(); } @After public void tearDown() throws Exception { AccountingRules.setInitialRoundOffMultiple(savedInitialRoundOffMultiple); AccountingRules.setFinalRoundOffMultiple(savedFinalRoundOffMultiple); AccountingRules.setCurrencyRoundingMode(savedCurrencyRoundingMode); AccountingRules.setDigitsAfterDecimal(savedDigitAfterDecimal); AccountingRules.setInitialRoundingMode(savedInitialRoundingMode); AccountingRules.setFinalRoundingMode(savedFinalRoundingMode); setNumberOfInterestDays(savedDaysInYear); } /* This part is for the testing of 999 account */ /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ private void runOne999AccountTestCaseWithDataFromSpreadSheetForLastPaymentReversal(String fileName, int expected999AccountTransactions, int paymentToReverse, boolean payLastPayment) throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { LoanTestCaseData testCaseData = loadSpreadSheetData(fileName); Results calculatedResults = new Results(); InternalConfiguration config = testCaseData.getInternalConfig(); LoanParameters loanParams = testCaseData.getLoanParams(); accountBO = setUpLoanFor999AccountTestLastPaymentReversal(config, loanParams, calculatedResults, paymentToReverse, payLastPayment); List<AccountPaymentEntity> paymentList = accountBO.getAccountPayments(); int transactionCount = 0; for (AccountPaymentEntity payment : paymentList) { Set<AccountTrxnEntity> transactionList = payment.getAccountTrxns(); for (AccountTrxnEntity transaction : transactionList) { Set<FinancialTransactionBO> list = transaction.getFinancialTransactions(); for (FinancialTransactionBO financialTransaction : list) { if (financialTransaction.getPostedAmount() .equals(testCaseData.getExpectedResult().getAccount999()) || financialTransaction.getPostedAmount().negate() .equals(testCaseData.getExpectedResult().getAccount999())) { transactionCount++; String debitOrCredit = "Credit"; if (financialTransaction.getDebitCreditFlag() == 0) { debitOrCredit = "Debit"; } if (isAllConsoleOutputEnabled()) { System.out.println("Posted amount: " + financialTransaction.getPostedAmount() + " Debit/Credit: " + debitOrCredit + " GLCode: " + financialTransaction.getGlcode().getGlcode() + " Transaction Id: " + financialTransaction.getTrxnId()); } } } } } Assert.assertEquals(expected999AccountTransactions, transactionCount); } private void runOne999AccountTestCaseWithDataFromSpreadSheetForLoanReversal(String fileName, int expected999AccountTransactions, int paymentToReverse, boolean payLastPayment) throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { LoanTestCaseData testCaseData = loadSpreadSheetData(fileName); Results calculatedResults = new Results(); InternalConfiguration config = testCaseData.getInternalConfig(); LoanParameters loanParams = testCaseData.getLoanParams(); accountBO = setUpLoanFor999AccountTestLoanReversal(config, loanParams, calculatedResults, paymentToReverse, payLastPayment); List<AccountPaymentEntity> paymentList = accountBO.getAccountPayments(); int transactionCount = 0; for (AccountPaymentEntity payment : paymentList) { Set<AccountTrxnEntity> transactionList = payment.getAccountTrxns(); for (AccountTrxnEntity transaction : transactionList) { Set<FinancialTransactionBO> list = transaction.getFinancialTransactions(); for (FinancialTransactionBO financialTransaction : list) { if (financialTransaction.getPostedAmount() .equals(testCaseData.getExpectedResult().getAccount999()) || financialTransaction.getPostedAmount().negate() .equals(testCaseData.getExpectedResult().getAccount999())) { transactionCount++; String debitOrCredit = "Credit"; if (financialTransaction.getDebitCreditFlag() == 0) { debitOrCredit = "Debit"; } if (isAllConsoleOutputEnabled()) { System.out.println("Posted amount: " + financialTransaction.getPostedAmount() + " Debit/Credit: " + debitOrCredit + " GLCode: " + financialTransaction.getGlcode().getGlcode() + " Transaction Id: " + financialTransaction.getTrxnId()); } } } } } Assert.assertEquals(transactionCount, expected999AccountTransactions); } private void verifyReversedLastPaymentLoanSchedules(LoanScheduleEntity[] schedules, Results expectedResults) { List<PaymentDetail> list = expectedResults.getPayments(); Assert.assertEquals(list.size(), schedules.length); for (int i = 0; i < schedules.length; i++) { if (i == schedules.length - 1) { Money zeroAmount = new Money(getCurrency(), "0"); Assert.assertEquals(schedules[i].getPrincipalPaid(), zeroAmount); Assert.assertNotNull(schedules[i].getPaymentDate()); Assert.assertEquals(schedules[i].isPaid(), false); } else { Assert.assertEquals(i + "th installment is still not paid", schedules[i].isPaid(), true); } verifyScheduleAndPaymentDetail(schedules[i], list.get(i)); } } private void setUpLoanAndVerifyForScheduleGenerationWhenLastPaymentIsReversed(InternalConfiguration config, LoanParameters loanParams, Results expectedResults) throws AccountException, PersistenceException, MeetingException { accountBO = setUpLoanFor999Account(config, loanParams); PaymentData paymentData = null; Set<AccountActionDateEntity> actionDateEntities = accountBO.getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); // before any payment is made printLoanScheduleEntities(paymentsArray); PersonnelBO personnelBO = legacyPersonnelDao.getPersonnel(userContext.getId()); LoanScheduleEntity loanSchedule; Short paymentTypeId = PaymentTypes.CASH.getValue(); for (LoanScheduleEntity element : paymentsArray) { loanSchedule = element; Money amountPaid = loanSchedule.getTotalDueWithFees(); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(accountBO, paymentData); } new TestObjectPersistence().persist(accountBO); actionDateEntities = accountBO.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); // after all payments are made printLoanScheduleEntities(paymentsArray); List<AccountPaymentEntity> accountPayments = legacyLoanDao .retrieveAllAccountPayments(accountBO.getAccountId()); accountBO.setAccountPayments(accountPayments); PersonnelBO loggedInUser = IntegrationTestObjectMother.testUser(); accountBO.adjustLastPayment("Adjust last payment", loggedInUser); new TestObjectPersistence().persist(accountBO); accountPayments = legacyLoanDao.retrieveAllAccountPayments(accountBO.getAccountId()); Assert.assertEquals(accountPayments.get(0).getAmount(), new Money(getCurrency(), "0")); // this // is // the // last // payment // reversed // so // amount // is // 0 actionDateEntities = accountBO.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); // after last payment is reversed printLoanScheduleEntities(paymentsArray); verifyReversedLastPaymentLoanSchedules(paymentsArray, expectedResults); } private void runOneLoanScheduleGenerationForLastPaymentReversal(String fileName) throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { LoanTestCaseData testCaseData = loadSpreadSheetData(fileName); InternalConfiguration config = testCaseData.getInternalConfig(); LoanParameters loanParams = testCaseData.getLoanParams(); setUpLoanAndVerifyForScheduleGenerationWhenLastPaymentIsReversed(config, loanParams, testCaseData.expectedResult); } private void runOneLoanScheduleGenerationForLoanReversal(String fileName) throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException { LoanTestCaseData testCaseData = loadSpreadSheetData(fileName); InternalConfiguration config = testCaseData.getInternalConfig(); LoanParameters loanParams = testCaseData.getLoanParams(); setUpLoanAndVerifyForScheduleGenerationWhenLoanIsReversed(config, loanParams, testCaseData.expectedResult); } private void verifyScheduleAndPaymentDetail(LoanScheduleEntity schedule, PaymentDetail payment) { Assert.assertEquals(schedule.getPrincipal(), payment.getPrincipal()); Assert.assertEquals(schedule.getInterest(), payment.getInterest()); Assert.assertEquals(schedule.getTotalFeesDue(), payment.getFee()); } private void verifyReversedLoanSchedules(LoanScheduleEntity[] schedules, Results expectedResults) { List<PaymentDetail> list = expectedResults.getPayments(); Assert.assertEquals(list.size(), schedules.length); for (int i = 0; i < schedules.length; i++) { Money zeroAmount = new Money(getCurrency(), "0"); Assert.assertEquals(schedules[i].getPrincipalPaid(), zeroAmount); if (i == 0) { Assert.assertNotNull(schedules[i].getPaymentDate()); } else { Assert.assertNull(schedules[i].getPaymentDate()); } Assert.assertEquals(schedules[i].isPaid(), false); verifyScheduleAndPaymentDetail(schedules[i], list.get(i)); } } private void setUpLoanAndVerifyForScheduleGenerationWhenLoanIsReversed(InternalConfiguration config, LoanParameters loanParams, Results expectedResults) throws AccountException, PersistenceException, MeetingException { accountBO = setUpLoanFor999Account(config, loanParams); PaymentData paymentData = null; Set<AccountActionDateEntity> actionDateEntities = accountBO.getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); // before any payment is made printLoanScheduleEntities(paymentsArray); PersonnelBO personnelBO = legacyPersonnelDao.getPersonnel(userContext.getId()); LoanScheduleEntity loanSchedule = null; Short paymentTypeId = PaymentTypes.CASH.getValue(); for (LoanScheduleEntity element : paymentsArray) { loanSchedule = element; Money amountPaid = loanSchedule.getTotalDueWithFees(); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(accountBO, paymentData); } new TestObjectPersistence().persist(accountBO); actionDateEntities = accountBO.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); // after all payments are made printLoanScheduleEntities(paymentsArray); // reverse loan ((LoanBO) accountBO).reverseLoanDisbursal(personnelBO, "Reverse this loan for testing"); new TestObjectPersistence().persist(accountBO); actionDateEntities = accountBO.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); verifyReversedLoanSchedules(paymentsArray, expectedResults); List<AccountPaymentEntity> accountPayments = legacyLoanDao .retrieveAllAccountPayments(accountBO.getAccountId()); // every payment is reversed so amount is 0 for (AccountPaymentEntity payment : accountPayments) { Assert.assertEquals(payment.getAmount(), new Money(getCurrency(), "0")); } } private AccountBO setUpLoanFor999Account(InternalConfiguration config, LoanParameters loanParams) throws AccountException, PersistenceException, MeetingException { setNumberOfInterestDays(config.getDaysInYear()); AccountingRules.setDigitsAfterDecimal((short) config.getDigitsAfterDecimal()); Money.setDefaultCurrency(AccountingRules.getMifosCurrency(new ConfigurationPersistence())); setInitialRoundingMode(config.getInitialRoundingMode()); setFinalRoundingMode(config.getFinalRoundingMode()); AccountingRules.setInitialRoundOffMultiple(new BigDecimal(config.getInitialRoundOffMultiple())); AccountingRules.setFinalRoundOffMultiple(new BigDecimal(config.getFinalRoundOffMultiple())); AccountingRules.setCurrencyRoundingMode(config.getCurrencyRoundingMode()); // AccountingRules.setRoundingRule(config.getCurrencyRoundingMode()); /* * When constructing a "meeting" here, it looks like the frequency should be "EVERY_X" for weekly or monthly * loan interest posting. */ // EVERY_WEEK, EVERY_DAY and EVERY_MONTH are defined as 1 MeetingBO meeting = null; if (loanParams.getPaymentFrequency() == RecurrenceType.MONTHLY) { meeting = new MeetingBO((short) Calendar.getInstance().get(Calendar.DAY_OF_MONTH), TestObjectFactory.EVERY_MONTH, new Date(), CUSTOMER_MEETING, "meeting place"); } else { meeting = TestObjectFactory.createMeeting(TestObjectFactory .getNewMeetingForToday(loanParams.getPaymentFrequency(), EVERY_MONTH, CUSTOMER_MEETING)); } center = TestObjectFactory.createWeeklyFeeCenter(this.getClass().getSimpleName() + " Center", meeting); group = TestObjectFactory.createWeeklyFeeGroupUnderCenter(this.getClass().getSimpleName() + " Group", CustomerStatus.GROUP_ACTIVE, center); Date startDate = new Date(System.currentTimeMillis()); LoanOfferingBO loanOffering = TestObjectFactory.createLoanOffering( this.getClass().getSimpleName() + " Loan", "L", ApplicableTo.GROUPS, startDate, PrdStatus.LOAN_ACTIVE, Double.parseDouble(loanParams.getPrincipal()), Double.parseDouble(loanParams.getAnnualInterest()), loanParams.getNumberOfPayments(), loanParams.getLoanType(), false, false, center.getCustomerMeeting().getMeeting(), config.getGracePeriodType(), config.getGracePeriod(), "1", "1"); List<FeeDto> feeViewList = createFeeViews(config, loanParams, meeting); AccountBO loan = legacyLoanDao.createLoan(TestUtils.makeUser(), loanOffering, group, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING, new Money(getCurrency(), loanParams.getPrincipal()), loanParams.getNumberOfPayments(), startDate, false, Double.parseDouble(loanParams.getAnnualInterest()), config.getGracePeriod(), null, feeViewList, null, DOUBLE_ZERO, DOUBLE_ZERO, SHORT_ZERO, SHORT_ZERO, false); return loan; } private AccountBO setUpLoanFor999AccountTestLoanReversal(InternalConfiguration config, LoanParameters loanParams, Results calculatedResults, int paymentToReverse, boolean payLastPayment) throws AccountException, PersistenceException, MeetingException { AccountBO loan = setUpLoanFor999Account(config, loanParams); PaymentData paymentData = null; Set<AccountActionDateEntity> actionDateEntities = loan.getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); PersonnelBO personnelBO = legacyPersonnelDao.getPersonnel(userContext.getId()); LoanScheduleEntity loanSchedule = null; Short paymentTypeId = PaymentTypes.CASH.getValue(); for (int i = 0; i < paymentsArray.length; i++) { loanSchedule = paymentsArray[i]; Money amountPaid = loanSchedule.getTotalDueWithFees(); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); if (i == (paymentToReverse - 1)) { break; } } boolean lastPayment = paymentToReverse == paymentsArray.length; calculatedResults.setAccount999(((LoanBO) loan).calculate999Account(lastPayment)); new TestObjectPersistence().persist(loan); ((LoanBO) loan).reverseLoanDisbursal(personnelBO, "Test 999 account for loan reversal"); new TestObjectPersistence().persist(loan); return loan; } private AccountBO setUpLoanFor999AccountTestLastPaymentReversal(InternalConfiguration config, LoanParameters loanParams, Results calculatedResults, int paymentToReverse, boolean payLastPayment) throws AccountException, PersistenceException, MeetingException { AccountBO loan = setUpLoanFor999Account(config, loanParams); PaymentData paymentData = null; Set<AccountActionDateEntity> actionDateEntities = loan.getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); PersonnelBO personnelBO = legacyPersonnelDao.getPersonnel(userContext.getId()); LoanScheduleEntity loanSchedule = null; Short paymentTypeId = PaymentTypes.CASH.getValue(); for (int i = 0; i < paymentsArray.length; i++) { loanSchedule = paymentsArray[i]; Money amountPaid = loanSchedule.getTotalDueWithFees(); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); if (i == (paymentToReverse - 1)) { break; } } boolean lastPayment = paymentToReverse == paymentsArray.length; calculatedResults.setAccount999(((LoanBO) loan).calculate999Account(lastPayment)); new TestObjectPersistence().persist(loan); actionDateEntities = loan.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); List<AccountPaymentEntity> accountPayments = legacyLoanDao.retrieveAllAccountPayments(loan.getAccountId()); Assert.assertEquals(accountPayments.size(), paymentToReverse); loan.setAccountPayments(accountPayments); PersonnelBO loggedInUser = IntegrationTestObjectMother.testUser(); loan.adjustLastPayment("Adjust last payment", loggedInUser); new TestObjectPersistence().persist(loan); accountPayments = legacyLoanDao.retrieveAllAccountPayments(loan.getAccountId()); Assert.assertEquals(accountPayments.get(0).getAmount(), new Money(getCurrency(), "0")); // this // is // the // last // payment // reversed // so // amount // is // 0 actionDateEntities = loan.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); Assert.assertEquals(paymentsArray[paymentToReverse - 1].getPrincipalPaid(), new Money(getCurrency(), "0")); if (payLastPayment) { for (int i = paymentToReverse - 1; i < paymentsArray.length; i++) { loanSchedule = paymentsArray[i]; Money amountPaid = loanSchedule.getTotalDueWithFees(); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); new TestObjectPersistence().persist(loan); } } return loan; } private void setUpLoanAndVerify999AccountWhenLoanIsRepaid(InternalConfiguration config, LoanParameters loanParams, Results expectedResults) throws AccountException, PersistenceException, MeetingException { accountBO = setUpLoanFor999Account(config, loanParams); PaymentData paymentData = null; Set<AccountActionDateEntity> actionDateEntities = accountBO.getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); // before any payment is made printLoanScheduleEntities(paymentsArray); PersonnelBO personnelBO = legacyPersonnelDao.getPersonnel(userContext.getId()); LoanScheduleEntity loanSchedule = null; Short paymentTypeId = PaymentTypes.CASH.getValue(); // pay one payment loanSchedule = paymentsArray[0]; Money amountPaid = loanSchedule.getPrincipal().add(loanSchedule.getInterest()); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(accountBO, paymentData); new TestObjectPersistence().persist(accountBO); actionDateEntities = accountBO.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); printLoanScheduleEntities(paymentsArray); // loan repay UserContext uc = TestUtils.makeUser(); String interestDueForNextInstallment = "0"; ((LoanBO) accountBO).makeEarlyRepayment(((LoanBO) accountBO).getEarlyRepayAmount(), null, null, "1", uc.getId(), false, new Money(((LoanBO) accountBO).getCurrency(), interestDueForNextInstallment)); new TestObjectPersistence().persist(accountBO); actionDateEntities = accountBO.getAccountActionDates(); paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); printLoanScheduleEntities(paymentsArray); // no 999 account is logged List<AccountPaymentEntity> paymentList = ((LoanBO) accountBO).getAccountPayments(); int i = 0; for (AccountPaymentEntity payment : paymentList) { Set<AccountTrxnEntity> transactionList = payment.getAccountTrxns(); for (AccountTrxnEntity transaction : transactionList) { Set<FinancialTransactionBO> list = ((LoanTrxnDetailEntity) transaction).getFinancialTransactions(); for (FinancialTransactionBO financialTransaction : list) { if (financialTransaction.getPostedAmount().equals(expectedResults.getAccount999()) || financialTransaction.getPostedAmount().negate() .equals(expectedResults.getAccount999())) { i++; } } } } Assert.assertEquals(i, 0); } private void run999AccountWhenLoanIsRepaid(String fileName) throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException { LoanTestCaseData testCaseData = loadSpreadSheetData(fileName); InternalConfiguration config = testCaseData.getInternalConfig(); LoanParameters loanParams = testCaseData.getLoanParams(); setUpLoanAndVerify999AccountWhenLoanIsRepaid(config, loanParams, testCaseData.expectedResult); } @Test public void testDecliningInterestEPITestCases() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; String[] dataFileNames = getCSVFiles(rootPath); for (String dataFileName : dataFileNames) { if (fileNameContains(dataFileName, decliningEPIGraceFeeTestCases)) { runOneTestCaseWithDataFromSpreadSheet(rootPath, dataFileName); StaticHibernateUtil.clearSession(); StaticHibernateUtil.rollbackTransaction(); } } } // decliningEPI @Test public void testDecliningEPISoham25Installments1DigitAfterDecimal() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; runOneTestCaseWithDataFromSpreadSheet(rootPath, "decliningEPI-Soham25Installments1DigitAfterDecimal.csv"); } @Test public void testDecliningEPISoham60Installments1DigitAfterDecimal() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; runOneTestCaseWithDataFromSpreadSheet(rootPath, "decliningEPI-Soham60Installments1DigitAfterDecimal.csv"); } @Test public void testDecliningEPISoham25Installments() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; runOneTestCaseWithDataFromSpreadSheet(rootPath, "decliningEPI-Soham25Installments.csv"); } @Test public void testDecliningEPISoham60Installments() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; runOneTestCaseWithDataFromSpreadSheet(rootPath, "decliningEPI-Soham60Installments.csv"); } @Test public void testDecliningEPINoFeeNoGrace() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; runOneTestCaseWithDataFromSpreadSheet(rootPath, "decliningEPI-NoFee-NoGrace.csv"); } @Test public void testDecliningEPIWithFeeWithGrace() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; runOneTestCaseWithDataFromSpreadSheet(rootPath, "decliningEPI-WithFee-WithGrace.csv"); } @Test public void testDecliningEPINoFeeWithGrace() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningEPI/"; runOneTestCaseWithDataFromSpreadSheet(rootPath, "decliningEPI-NoFee-WithGrace.csv"); } @Test public void test999AccountLoansWithFees() throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException, Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningInterest/"; String[] dataFileNames = getCSVFiles(rootPath); for (String dataFileName : dataFileNames) { if (dataFileName.startsWith("testcase-2008-05-13-declining-grace-fee-set1")) { runOne999AccountTestCaseLoanWithFees(rootPath + dataFileName); StaticHibernateUtil.clearSession(); StaticHibernateUtil.rollbackTransaction(); } } } @Test public void test999AccountLoansWithFees2() throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException, Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/flatInterest/"; String[] dataFileNames = getCSVFiles(rootPath); for (String dataFileName : dataFileNames) { if (dataFileName.startsWith("testcase") && dataFileName.contains("flat-grace-fee-set")) { runOne999AccountTestCaseLoanWithFees(rootPath + dataFileName); StaticHibernateUtil.clearSession(); StaticHibernateUtil.rollbackTransaction(); } } } // verify that 999 account transactions are logged after last payment is // made @Test public void testPositive999AccountTest2LoanWithFees() throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException { String dataFileName = "account999-withfees.csv"; runOne999AccountTestCaseLoanWithFees(rootPath + dataFileName); } @Test public void test999AccountWhenLoanIsRepaid() throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException, Exception { String dataFileName = "account999-test3.csv"; run999AccountWhenLoanIsRepaid(rootPath + dataFileName); } @Test public void test999AccountForLoanReversal() throws Exception { String dataFileName = "account999-test3.csv"; int expected999AccountTransactions = 4; int paymentToReverse = 3; boolean payLastPayment = false; runOne999AccountTestCaseWithDataFromSpreadSheetForLoanReversal(rootPath + dataFileName, expected999AccountTransactions, paymentToReverse, payLastPayment); } @Test public void test999AccountForLastPaymentReversal() throws Exception { String dataFileName = "account999-test3.csv"; int expected999AccountTransactions = 4; int paymentToReverse = 3; boolean payLastPayment = false; runOne999AccountTestCaseWithDataFromSpreadSheetForLastPaymentReversal(rootPath + dataFileName, expected999AccountTransactions, paymentToReverse, payLastPayment); } @Test public void testLoanScheduleGenerationWhenLastPaymentIsReversed() throws Exception { String dataFileName = "account999-test3.csv"; runOneLoanScheduleGenerationForLastPaymentReversal(rootPath + dataFileName); } @Test public void testLoanScheduleGenerationWhenLoanIsReversed() throws Exception { String dataFileName = "account999-test3.csv"; runOneLoanScheduleGenerationForLoanReversal(rootPath + dataFileName); } // verify that 999 account transactions are logged after last payment is // made @Test public void test999Account() throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { String dataFileName = "account999-test2.csv"; runOne999AccountTestCaseWithDataFromSpreadSheet(rootPath + dataFileName); } // verify that 999 account transactions are logged after last payment is // made @Test public void test999AccountTest1() throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { String dataFileName = "account999-test1.csv"; runOne999AccountTestCaseWithDataFromSpreadSheet(rootPath + dataFileName); } // no 999account should be logged in this case @Test public void test999AccountForMiddlePaymentReversal() throws Exception { String dataFileName = "account999-test3.csv"; int expected999AccountTransactions = 0; int paymentToReverse = 2; boolean payLastPayment = false; runOne999AccountTestCaseWithDataFromSpreadSheetForLastPaymentReversal(rootPath + dataFileName, expected999AccountTransactions, paymentToReverse, payLastPayment); } // payment is reversed and repay to the last payment @Test public void test999AccountForMiddlePaymentReversalAndPayToLastPayment() throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { String dataFileName = "account999-test3.csv"; int expected999AccountTransactions = 2; int paymentToReverse = 2; boolean payLastPayment = true; runOne999AccountTestCaseWithDataFromSpreadSheetForLastPaymentReversal(rootPath + dataFileName, expected999AccountTransactions, paymentToReverse, payLastPayment); } private AccountBO setUpLoanFor999AccountTest(InternalConfiguration config, LoanParameters loanParams, Results calculatedResults) throws AccountException, PersistenceException, MeetingException { setNumberOfInterestDays(config.getDaysInYear()); AccountingRules.setDigitsAfterDecimal((short) config.getDigitsAfterDecimal()); Money.setDefaultCurrency(AccountingRules.getMifosCurrency(new ConfigurationPersistence())); setInitialRoundingMode(config.getInitialRoundingMode()); setFinalRoundingMode(config.getFinalRoundingMode()); AccountingRules.setInitialRoundOffMultiple(new BigDecimal(config.getInitialRoundOffMultiple())); AccountingRules.setFinalRoundOffMultiple(new BigDecimal(config.getFinalRoundOffMultiple())); AccountingRules.setCurrencyRoundingMode(config.getCurrencyRoundingMode()); // AccountingRules.setRoundingRule(config.getCurrencyRoundingMode()); /* * When constructing a "meeting" here, it looks like the frequency should be "EVERY_X" for weekly or monthly * loan interest posting. */ // EVERY_WEEK, EVERY_DAY and EVERY_MONTH are defined as 1 MeetingBO meeting = null; if (loanParams.getPaymentFrequency() == RecurrenceType.MONTHLY) { meeting = new MeetingBO((short) Calendar.getInstance().get(Calendar.DAY_OF_MONTH), TestObjectFactory.EVERY_MONTH, new Date(), CUSTOMER_MEETING, "meeting place"); } else { meeting = TestObjectFactory.createMeeting(TestObjectFactory .getNewMeetingForToday(loanParams.getPaymentFrequency(), EVERY_MONTH, CUSTOMER_MEETING)); } center = TestObjectFactory.createWeeklyFeeCenter(this.getClass().getSimpleName() + " Center", meeting); group = TestObjectFactory.createWeeklyFeeGroupUnderCenter(this.getClass().getSimpleName() + " Group", CustomerStatus.GROUP_ACTIVE, center); Date startDate = new Date(System.currentTimeMillis()); LoanOfferingBO loanOffering = TestObjectFactory.createLoanOffering( this.getClass().getSimpleName() + " Loan", "L", ApplicableTo.GROUPS, startDate, PrdStatus.LOAN_ACTIVE, Double.parseDouble(loanParams.getPrincipal()), Double.parseDouble(loanParams.getAnnualInterest()), loanParams.getNumberOfPayments(), loanParams.getLoanType(), false, false, center.getCustomerMeeting().getMeeting(), config.getGracePeriodType(), config.getGracePeriod(), "1", "1"); List<FeeDto> feeViewList = createFeeViews(config, loanParams, meeting); AccountBO loan = legacyLoanDao.createLoan(TestUtils.makeUser(), loanOffering, group, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING, new Money(getCurrency(), loanParams.getPrincipal()), loanParams.getNumberOfPayments(), startDate, false, Double.parseDouble(loanParams.getAnnualInterest()), config.getGracePeriod(), null, feeViewList, null, DOUBLE_ZERO, DOUBLE_ZERO, SHORT_ZERO, SHORT_ZERO, false); PaymentData paymentData = null; Set<AccountActionDateEntity> actionDateEntities = loan.getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); PersonnelBO personnelBO = legacyPersonnelDao.getPersonnel(userContext.getId()); LoanScheduleEntity loanSchedule = null; Short paymentTypeId = PaymentTypes.CASH.getValue(); for (LoanScheduleEntity element : paymentsArray) { loanSchedule = element; Money amountPaid = loanSchedule.getTotalDueWithFees(); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); } boolean lastPayment = true; calculatedResults.setAccount999(((LoanBO) loan).calculate999Account(lastPayment)); new TestObjectPersistence().persist(loan); return loan; } private AccountBO setUpLoanFor999AccountTestLoanWithFees(InternalConfiguration config, LoanParameters loanParams, Results calculatedResults) throws AccountException, PersistenceException, MeetingException { setNumberOfInterestDays(config.getDaysInYear()); AccountingRules.setDigitsAfterDecimal((short) config.getDigitsAfterDecimal()); Money.setDefaultCurrency(AccountingRules.getMifosCurrency(new ConfigurationPersistence())); setInitialRoundingMode(config.getInitialRoundingMode()); setFinalRoundingMode(config.getFinalRoundingMode()); AccountingRules.setInitialRoundOffMultiple(new BigDecimal(config.getInitialRoundOffMultiple())); AccountingRules.setFinalRoundOffMultiple(new BigDecimal(config.getFinalRoundOffMultiple())); AccountingRules.setCurrencyRoundingMode(config.getCurrencyRoundingMode()); // AccountingRules.setRoundingRule(config.getCurrencyRoundingMode()); /* * When constructing a "meeting" here, it looks like the frequency should be "EVERY_X" for weekly or monthly * loan interest posting. */ // EVERY_WEEK, EVERY_DAY and EVERY_MONTH are defined as 1 MeetingBO meeting = null; if (loanParams.getPaymentFrequency() == RecurrenceType.MONTHLY) { meeting = new MeetingBO((short) Calendar.getInstance().get(Calendar.DAY_OF_MONTH), TestObjectFactory.EVERY_MONTH, new Date(), CUSTOMER_MEETING, "meeting place"); } else { meeting = TestObjectFactory.createMeeting(TestObjectFactory .getNewMeetingForToday(loanParams.getPaymentFrequency(), EVERY_MONTH, CUSTOMER_MEETING)); } center = TestObjectFactory.createWeeklyFeeCenter(this.getClass().getSimpleName() + " Center", meeting); group = TestObjectFactory.createWeeklyFeeGroupUnderCenter(this.getClass().getSimpleName() + " Group", CustomerStatus.GROUP_ACTIVE, center); Date startDate = new Date(System.currentTimeMillis()); LoanOfferingBO loanOffering = TestObjectFactory.createLoanOffering( this.getClass().getSimpleName() + " Loan", "L", ApplicableTo.GROUPS, startDate, PrdStatus.LOAN_ACTIVE, Double.parseDouble(loanParams.getPrincipal()), Double.parseDouble(loanParams.getAnnualInterest()), loanParams.getNumberOfPayments(), loanParams.getLoanType(), false, false, center.getCustomerMeeting().getMeeting(), config.getGracePeriodType(), config.getGracePeriod(), "1", "1"); // loanOffering.updateLoanOfferingSameForAllLoan(loanOffering); List<FeeDto> feeViewList = createFeeViews(config, loanParams, meeting); AccountBO loan = legacyLoanDao.createLoan(TestUtils.makeUser(), loanOffering, group, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING, new Money(getCurrency(), loanParams.getPrincipal()), loanParams.getNumberOfPayments(), startDate, false, Double.parseDouble(loanParams.getAnnualInterest()), config.getGracePeriod(), null, feeViewList, null, DOUBLE_ZERO, DOUBLE_ZERO, SHORT_ZERO, SHORT_ZERO, false); PaymentData paymentData = null; Set<AccountActionDateEntity> actionDateEntities = loan.getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); PersonnelBO personnelBO = legacyPersonnelDao.getPersonnel(userContext.getId()); LoanScheduleEntity loanSchedule = null; Short paymentTypeId = PaymentTypes.CASH.getValue(); for (LoanScheduleEntity element : paymentsArray) { loanSchedule = element; Money amountPaid = loanSchedule.getTotalDueWithFees(); paymentData = PaymentData.createPaymentData(amountPaid, personnelBO, paymentTypeId, loanSchedule.getActionDate()); IntegrationTestObjectMother.applyAccountPayment(loan, paymentData); } boolean lastPayment = true; calculatedResults.setAccount999(((LoanBO) loan).calculate999Account(lastPayment)); new TestObjectPersistence().persist(loan); return loan; } private void compare999Account(Money expected999Account, Money calculated999Account, String testName) { if (isAllConsoleOutputEnabled()) { System.out.println("Running test: " + testName); System.out.println("Results (Expected : Calculated : Difference)"); printComparison("999 Account: ", expected999Account, calculated999Account); } Assert.assertEquals(expected999Account, calculated999Account); } private void runOne999AccountTestCaseWithDataFromSpreadSheet(String fileName) throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { LoanTestCaseData testCaseData = loadSpreadSheetData(fileName); Results calculatedResults = new Results(); InternalConfiguration config = testCaseData.getInternalConfig(); LoanParameters loanParams = testCaseData.getLoanParams(); accountBO = setUpLoanFor999AccountTest(config, loanParams, calculatedResults); AccountPaymentEntity lastPmt = null; List<AccountPaymentEntity> paymentList = ((LoanBO) accountBO).getAccountPayments(); int i = 1; for (AccountPaymentEntity payment : paymentList) { if (i == loanParams.getNumberOfPayments()) { lastPmt = payment; break; } i++; } Set<AccountTrxnEntity> transactionList = lastPmt.getAccountTrxns(); for (AccountTrxnEntity transaction : transactionList) { Set<FinancialTransactionBO> list = transaction.getFinancialTransactions(); for (FinancialTransactionBO financialTransaction : list) { if (financialTransaction.getGlcode().getGlcodeId() == 51) { Assert.assertEquals(financialTransaction.getGlcode().getGlcode(), "31401"); Money postedAmount = financialTransaction.getPostedAmount(); Money expected999Account = testCaseData.getExpectedResult().getAccount999(); Assert.assertEquals(removeSign(expected999Account), postedAmount); if (expected999Account.isGreaterThanZero()) { Assert.assertEquals(FinancialConstants.fromValue(financialTransaction.getDebitCreditFlag()), FinancialConstants.CREDIT); } else { Assert.assertEquals(FinancialConstants.fromValue(financialTransaction.getDebitCreditFlag()), FinancialConstants.DEBIT); } } } } compare999Account(testCaseData.getExpectedResult().getAccount999(), calculatedResults.getAccount999(), fileName); } private void runOne999AccountTestCaseLoanWithFees(String fileName) throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { if (isAllConsoleOutputEnabled() || isFileNameConsoleOutputEnabled()) { System.out.println("Running 999 Account Test with Fees: " + fileName); } LoanTestCaseData testCaseData = loadSpreadSheetData(fileName); Results calculatedResults = new Results(); InternalConfiguration config = testCaseData.getInternalConfig(); LoanParameters loanParams = testCaseData.getLoanParams(); accountBO = setUpLoanFor999AccountTestLoanWithFees(config, loanParams, calculatedResults); AccountPaymentEntity lastPmt = null; List<AccountPaymentEntity> paymentList = accountBO.getAccountPayments(); int i = 1; for (AccountPaymentEntity payment : paymentList) { if (i == loanParams.getNumberOfPayments()) { lastPmt = payment; break; } i++; } Set<AccountTrxnEntity> transactionList = lastPmt.getAccountTrxns(); for (AccountTrxnEntity transaction : transactionList) { Set<FinancialTransactionBO> list = transaction.getFinancialTransactions(); for (FinancialTransactionBO financialTransaction : list) { if (financialTransaction.getGlcode().getGlcodeId() == 51) { Assert.assertEquals(financialTransaction.getGlcode().getGlcode(), "31401"); Money postedAmount = financialTransaction.getPostedAmount(); Money expected999Account = testCaseData.getExpectedResult().getAccount999(); if (!postedAmount.equals(removeSign(expected999Account))) { System.out.println("File name: " + fileName + " posted amount: " + postedAmount + " expected amount: " + expected999Account); } Assert.assertEquals(removeSign(expected999Account), postedAmount); if (expected999Account.isGreaterThanZero()) { Assert.assertEquals(FinancialConstants.fromValue(financialTransaction.getDebitCreditFlag()), FinancialConstants.CREDIT); } else { Assert.assertEquals(FinancialConstants.fromValue(financialTransaction.getDebitCreditFlag()), FinancialConstants.DEBIT); } } } } compare999Account(testCaseData.getExpectedResult().getAccount999(), calculatedResults.getAccount999(), fileName); } /** * Added as a fix for negative posted amounts stored in cvs for test Since the MIFOS-2474 no account transaction is * records -ve amount * * @param amount * @return */ private Money removeSign(final Money amount) { if (amount != null && amount.isLessThanZero()) { return amount.negate(); } return amount; } // verify that 999 account transactions are logged after last payment is // made @Test public void testPositive999AccountTest2() throws NumberFormatException, SystemException, ApplicationException, URISyntaxException { String dataFileName = "account999-test3.csv"; runOne999AccountTestCaseWithDataFromSpreadSheet(rootPath + dataFileName); } private void printLoanScheduleEntities(LoanScheduleEntity[] loanSchedules) { if (!isAllConsoleOutputEnabled()) { return; } for (int i = 0; i < loanSchedules.length; i++) { System.out.println("Loan Schedule #: " + (i + 1)); System.out.println("Principal: " + loanSchedules[i].getPrincipal()); System.out.println("Principal Paid: " + loanSchedules[i].getPrincipalPaid()); System.out.println("Interest Paid: " + loanSchedules[i].getInterestPaid()); System.out.println("Interest: " + loanSchedules[i].getInterest()); System.out.println("Total Due: " + loanSchedules[i].getTotalDue()); if (loanSchedules[i].getPaymentDate() != null) { System.out.println("Payment Date: " + loanSchedules[i].getPaymentDate().toString()); } else { System.out.println("Payment Date: null"); } System.out.println("Is paid: " + loanSchedules[i].isPaid()); } } private boolean isAllConsoleOutputEnabled() { return allConsoleOutputEnabled; } private boolean isFileNameConsoleOutputEnabled() { return isFileNameConsoleOutputEnabled; } /* end of of 999 account testing */ /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /* This part is for the new financial calculation */ /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ class LoanParameters { private String principal = null; private InterestType loanType = null; private String annualInterest = null; private short numberOfPayments = 0; private RecurrenceType paymentFrequency = null; public LoanParameters() { } public String getPrincipal() { return principal; } public void setPrincipal(String principal) { this.principal = principal; } public InterestType getLoanType() { return loanType; } public void setLoanType(InterestType loanType) { this.loanType = loanType; } public String getAnnualInterest() { return annualInterest; } public void setAnnualInterest(String annualInterest) { this.annualInterest = annualInterest; } public short getNumberOfPayments() { return numberOfPayments; } public void setNumberOfPayments(short numberOfPayments) { this.numberOfPayments = numberOfPayments; } public RecurrenceType getPaymentFrequency() { return paymentFrequency; } public void setPaymentFrequency(RecurrenceType paymentFrequency) { this.paymentFrequency = paymentFrequency; } } class InternalConfiguration { private int daysInYear = 0; // right now we are just supporting CEILING, FLOOR, HALF_UP private RoundingMode initialRoundingMode = null; // should this be constrained to .001, .01, .5, .1, 1 as in the // spreadsheet? private String initialRoundOffMultiple = null; // right now we are just supporting CEILING, FLOOR, HALF_UP private RoundingMode finalRoundingMode = null; // should this be constrained to .001, .01, .5, .1, 1 as in the // spreadsheet? private String finalRoundOffMultiple = null; // right now we are just supporting CEILING, FLOOR, HALF_UP private RoundingMode currencyRoundingMode = null; // the number of digits to use to the right of the decimal for interal // caculations private int internalPrecision = 13; // digits after decimal right now is in the application configuration private int digitsAfterDecimal = 1; // grace period type private GraceType gracePeriodType = GraceType.NONE; private short gracePeriod = 0; private FeeFrequencyType feeFrequency; // if feeFrequency is null there // is no fee setting for the loan private FeeFormula feeType; // if rate-based fee, indicates what the // rate applies to private boolean isFeeRateBased; // true if rate based, false if applies // a fixed amount private String feeValue = null; private String feePercentage = null; public FeeFrequencyType getFeeFrequency() { return feeFrequency; } public void setFeeFrequency(FeeFrequencyType feeFrequency) { this.feeFrequency = feeFrequency; } public String getFeePercentage() { return feePercentage; } public void setFeePercentage(String feePercentage) { this.feePercentage = feePercentage; } public FeeFormula getFeeType() { return feeType; } public void setFeeType(FeeFormula feeType) { this.feeType = feeType; } public String getFeeValue() { return feeValue; } public void setFeeValue(String feeValue) { this.feeValue = feeValue; } public int getDigitsAfterDecimal() { return digitsAfterDecimal; } public void setDigitsAfterDecimal(int digitsAfterDecimal) { this.digitsAfterDecimal = digitsAfterDecimal; } public InternalConfiguration(int daysInYear, RoundingMode initialRoundingMode, String initialRoundOffMultiple, RoundingMode finalRoundingMode, String finalRoundOffMultiple, RoundingMode currencyRoundingMode, int internalPrecision, GraceType gracePeriodType, short gracePeriod) { super(); this.daysInYear = daysInYear; this.initialRoundingMode = initialRoundingMode; this.initialRoundOffMultiple = initialRoundOffMultiple; this.finalRoundingMode = finalRoundingMode; this.finalRoundOffMultiple = finalRoundOffMultiple; this.currencyRoundingMode = currencyRoundingMode; this.internalPrecision = internalPrecision; this.gracePeriodType = gracePeriodType; this.gracePeriod = gracePeriod; } public InternalConfiguration() { } public int getDaysInYear() { return daysInYear; } public void setDaysInYear(int daysInYear) { this.daysInYear = daysInYear; } public RoundingMode getInitialRoundingMode() { return initialRoundingMode; } public void setInitialRoundingMode(RoundingMode initialRoundingMode) { this.initialRoundingMode = initialRoundingMode; } public String getInitialRoundOffMultiple() { return initialRoundOffMultiple; } public void setInitialRoundOffMultiple(String initialRoundOffMultiple) { this.initialRoundOffMultiple = initialRoundOffMultiple; } public RoundingMode getFinalRoundingMode() { return finalRoundingMode; } public void setFinalRoundingMode(RoundingMode finalRoundingMode) { this.finalRoundingMode = finalRoundingMode; } public String getFinalRoundOffMultiple() { return finalRoundOffMultiple; } public void setFinalRoundOffMultiple(String finalRoundOffMultiple) { this.finalRoundOffMultiple = finalRoundOffMultiple; } public RoundingMode getCurrencyRoundingMode() { return currencyRoundingMode; } public void setCurrencyRoundingMode(RoundingMode currencyRoundingMode) { this.currencyRoundingMode = currencyRoundingMode; } public int getInternalPrecision() { return internalPrecision; } public void setInternalPrecision(int internalPrecision) { this.internalPrecision = internalPrecision; } public short getGracePeriod() { return gracePeriod; } public void setGracePeriod(short gracePeriod) { this.gracePeriod = gracePeriod; } public GraceType getGracePeriodType() { return gracePeriodType; } public void setGracePeriodType(GraceType gracePeriodType) { this.gracePeriodType = gracePeriodType; } public boolean isFeeRateBased() { return this.isFeeRateBased; } public void setIsFeeRateBased(boolean isRateBased) { this.isFeeRateBased = isRateBased; } } class Results { // each installment has payment = interest + principal Money totalPayments = null; // sum of all payments Money totalInterest = null; // sum of all interests for all payments Money totalFee = null; Money totalPrincipal = null; // sum of all principals for all payments // detailed list of all payments. Each payment includes payment, // interest, principal and balance List<PaymentDetail> payments = null; Money roundedTotalInterest = null; Money account999 = null; public List<PaymentDetail> getPayments() { return payments; } public void setPayments(List<PaymentDetail> payments) { this.payments = payments; } public Money getTotalInterest() { return totalInterest; } public void setTotalInterest(Money totalInterest) { this.totalInterest = totalInterest; } public Money getTotalPayments() { return totalPayments; } public void setTotalPayments(Money totalPayments) { this.totalPayments = totalPayments; } public Money getTotalPrincipal() { return totalPrincipal; } public void setTotalPrincipal(Money totalPrincipal) { this.totalPrincipal = totalPrincipal; } public Money getAccount999() { return account999; } public void setAccount999(Money account999) { this.account999 = account999; } public void setRoundedTotalInterest(Money roundedTotalInterest) { this.roundedTotalInterest = roundedTotalInterest; } public Money getTotalFee() { return totalFee; } public void setTotalFee(Money totalFee) { this.totalFee = totalFee; } } class LoanTestCaseData { private LoanParameters loanParams = null; private Results expectedResult = null; InternalConfiguration internalConfig = null; public InternalConfiguration getInternalConfig() { return internalConfig; } public void setInternalConfig(InternalConfiguration config) { this.internalConfig = config; } public LoanTestCaseData() { } public Results getExpectedResult() { return expectedResult; } public void setExpectedResult(Results expectedResult) { this.expectedResult = expectedResult; } public LoanParameters getLoanParams() { return loanParams; } public void setLoanParams(LoanParameters loanParams) { this.loanParams = loanParams; } } private void printResults(Results expectedResult, Results calculatedResult, String testName) { if (!isAllConsoleOutputEnabled()) { return; } // System.out.println("Running test: " + testName); System.out.println("Results are (Expected : Calculated : Difference)"); printComparison("Total Interest: ", expectedResult.getTotalInterest(), calculatedResult.getTotalInterest()); printComparison("Total Payments: ", expectedResult.getTotalPayments(), calculatedResult.getTotalPayments()); printComparison("Total Principal: ", expectedResult.getTotalPrincipal(), calculatedResult.getTotalPrincipal()); printComparison("Total Fees: ", expectedResult.getTotalFee(), calculatedResult.getTotalFee()); List<PaymentDetail> expectedPayments = expectedResult.getPayments(); List<PaymentDetail> calculatedPayments = calculatedResult.getPayments(); System.out.println("Number of Installments: " + expectedPayments.size() + " : " + calculatedPayments.size() + " : " + (expectedPayments.size() - calculatedPayments.size())); for (int i = 0; i < expectedPayments.size(); i++) { System.out.println("Payment #: " + (i + 1)); printComparison("Balance: ", expectedPayments.get(i).getBalance(), calculatedPayments.get(i).getBalance()); printComparison("Interest: ", expectedPayments.get(i).getInterest(), calculatedPayments.get(i).getInterest()); printComparison("Payment: ", expectedPayments.get(i).getPayment(), calculatedPayments.get(i).getPayment()); printComparison("Principal: ", expectedPayments.get(i).getPrincipal(), calculatedPayments.get(i).getPrincipal()); printComparison("Fee: ", expectedPayments.get(i).getFee(), calculatedPayments.get(i).getFee()); } } private void compareResults(Results expectedResult, Results calculatedResult, String testName) { printResults(expectedResult, calculatedResult, testName); Assert.assertEquals(testName, expectedResult.getTotalInterest(), calculatedResult.getTotalInterest()); Assert.assertEquals(testName, expectedResult.getTotalPayments(), calculatedResult.getTotalPayments()); Assert.assertEquals(testName, expectedResult.getTotalPrincipal(), calculatedResult.getTotalPrincipal()); List<PaymentDetail> expectedPayments = expectedResult.getPayments(); List<PaymentDetail> calculatedPayments = calculatedResult.getPayments(); Assert.assertEquals(testName, expectedPayments.size(), calculatedPayments.size()); for (int i = 0; i < expectedPayments.size(); i++) { /* * Do not assert balance since it is derived from loan informationAssert.assertEquals(testName, * expectedPayments.get(i).getBalance(), calculatedPayments.get(i).getBalance()); */ Assert.assertEquals(testName, expectedPayments.get(i).getInterest(), calculatedPayments.get(i).getInterest()); Assert.assertEquals(testName, expectedPayments.get(i).getPayment(), calculatedPayments.get(i).getPayment()); Assert.assertEquals(testName, expectedPayments.get(i).getPrincipal(), calculatedPayments.get(i).getPrincipal()); } } private void printComparison(String label, Money expected, Money calculated) { if (!isAllConsoleOutputEnabled()) { return; } System.out.println(label + expected + " : " + calculated + " : " + expected.subtract(calculated)); } private void setNumberOfInterestDays(int days) { MifosConfigurationManager configMgr = MifosConfigurationManager.getInstance(); configMgr.setProperty(AccountingRulesConstants.NUMBER_OF_INTEREST_DAYS, new Short((short) days)); } private void setInitialRoundingMode(RoundingMode mode) { MifosConfigurationManager configMgr = MifosConfigurationManager.getInstance(); configMgr.setProperty(AccountingRulesConstants.INITIAL_ROUNDING_MODE, mode.toString()); } private void setFinalRoundingMode(RoundingMode mode) { MifosConfigurationManager configMgr = MifosConfigurationManager.getInstance(); configMgr.setProperty(AccountingRulesConstants.FINAL_ROUNDING_MODE, mode.toString()); } private AccountBO setUpLoan(InternalConfiguration config, LoanParameters loanParams) throws MeetingException, NumberFormatException, AccountException { setNumberOfInterestDays(config.getDaysInYear()); AccountingRules.setDigitsAfterDecimal((short) config.getDigitsAfterDecimal()); setInitialRoundingMode(config.getInitialRoundingMode()); setFinalRoundingMode(config.getFinalRoundingMode()); AccountingRules.setInitialRoundOffMultiple(new BigDecimal(config.getInitialRoundOffMultiple())); AccountingRules.setFinalRoundOffMultiple(new BigDecimal(config.getFinalRoundOffMultiple())); AccountingRules.setCurrencyRoundingMode(config.getCurrencyRoundingMode()); Money.setDefaultCurrency(AccountingRules.getMifosCurrency(new ConfigurationPersistence())); /* * When constructing a "meeting" here, it looks like the frequency should be "EVERY_X" for weekly or monthly * loan interest posting. */ // EVERY_WEEK, EVERY_DAY and EVERY_MONTH are defined as 1 MeetingBO meeting = null; if (loanParams.getPaymentFrequency() == RecurrenceType.MONTHLY) { meeting = new MeetingBO((short) Calendar.getInstance().get(Calendar.DAY_OF_MONTH), TestObjectFactory.EVERY_MONTH, new Date(), CUSTOMER_MEETING, "meeting place"); } else { meeting = TestObjectFactory.createMeeting(TestObjectFactory .getNewMeetingForToday(loanParams.getPaymentFrequency(), EVERY_MONTH, CUSTOMER_MEETING)); } center = TestObjectFactory.createWeeklyFeeCenter(this.getClass().getSimpleName() + " Center", meeting); group = TestObjectFactory.createWeeklyFeeGroupUnderCenter(this.getClass().getSimpleName() + " Group", CustomerStatus.GROUP_ACTIVE, center); Date startDate = new Date(System.currentTimeMillis()); LoanOfferingBO loanOffering = TestObjectFactory.createLoanOffering( this.getClass().getSimpleName() + " Loan", "L", ApplicableTo.GROUPS, startDate, PrdStatus.LOAN_ACTIVE, Double.parseDouble(loanParams.getPrincipal()), Double.parseDouble(loanParams.getAnnualInterest()), loanParams.getNumberOfPayments(), loanParams.getLoanType(), false, false, center.getCustomerMeeting().getMeeting(), config.getGracePeriodType(), config.getGracePeriod(), "1", "1"); List<FeeDto> feeViewList = createFeeViews(config, loanParams, meeting); AccountBO accountBO = legacyLoanDao.createLoan(TestUtils.makeUser(), loanOffering, group, AccountState.LOAN_ACTIVE_IN_GOOD_STANDING, new Money(getCurrency(), loanParams.getPrincipal()), loanParams.getNumberOfPayments(), startDate, false, Double.parseDouble(loanParams.getAnnualInterest()), config.getGracePeriod(), null, feeViewList, null, DOUBLE_ZERO, DOUBLE_ZERO, SHORT_ZERO, SHORT_ZERO, false); new TestObjectPersistence().persist(accountBO); return accountBO; } private List<FeeDto> createFeeViews(InternalConfiguration config, LoanParameters loanParams, MeetingBO meeting) { List<FeeDto> feeDtos = new ArrayList<FeeDto>(); // Only periodic fees get merged into loan installments if (!(config.getFeeFrequency() == null) && config.getFeeFrequency() == FeeFrequencyType.PERIODIC) { feeDtos.add(createPeriodicFeeView(config, loanParams, meeting)); } return feeDtos; } private FeeDto createPeriodicFeeView(InternalConfiguration config, LoanParameters loanParams, MeetingBO meeting) { FeeBO fee = null; if (config.isFeeRateBased()) { fee = TestObjectFactory.createPeriodicRateFee("testLoanFee", FeeCategory.LOAN, new Double(config.getFeePercentage()), config.getFeeType(), loanParams.getPaymentFrequency(), (short) 1, userContext, meeting); } else { fee = TestObjectFactory.createPeriodicAmountFee("testLoanFee", FeeCategory.LOAN, config.getFeeValue(), loanParams.getPaymentFrequency(), Short.valueOf("1")); } FeeDto feeDto = new FeeDto(userContext, fee); return feeDto; } private Results calculatePayments(InternalConfiguration config, AccountBO accountBO, LoanParameters loanParams) { Set<AccountActionDateEntity> actionDateEntities = ((LoanBO) accountBO).getAccountActionDates(); LoanScheduleEntity[] paymentsArray = LoanBOTestUtils.getSortedAccountActionDateEntity(actionDateEntities, loanParams.getNumberOfPayments()); MathContext context = new MathContext(config.getInternalPrecision()); BigDecimal totalPrincipal = new BigDecimal(0, context); BigDecimal totalInterest = new BigDecimal(0, context); BigDecimal totalFees = new BigDecimal(0, context); Money totalPayments = new Money(getCurrency(), "0"); Results calculatedResult = new Results(); List<PaymentDetail> payments = new ArrayList<PaymentDetail>(); for (LoanScheduleEntity loanEntry : paymentsArray) { PaymentDetail payment = new PaymentDetail(); Money calculatedPayment = new Money(getCurrency(), loanEntry.getPrincipal().getAmount().add( loanEntry.getInterest().getAmount().add(loanEntry.getTotalFeesDueWithMiscFee().getAmount()))); payment.setPayment(calculatedPayment); payment.setInterest(loanEntry.getInterest()); payment.setPrincipal(loanEntry.getPrincipal()); payment.setFee(loanEntry.getTotalFeesDueWithMiscFee()); totalPrincipal = totalPrincipal.add(loanEntry.getPrincipal().getAmount()); totalInterest = totalInterest.add(loanEntry.getInterest().getAmount()); totalPayments = totalPayments.add(calculatedPayment); totalFees = totalFees.add(loanEntry.getTotalFeesDueWithMiscFee().getAmount()); payments.add(payment); } calculatedResult.setPayments(payments); calculatedResult.setTotalInterest(new Money(getCurrency(), totalInterest)); calculatedResult.setTotalPayments(totalPayments); calculatedResult.setTotalPrincipal(new Money(getCurrency(), totalPrincipal)); calculatedResult.setTotalFee(new Money(getCurrency(), totalFees)); /* * Set balance after each installment is paid, excluding fees or penalties. For flat-interest loans, balance is * total of all remaining principal and interest. For declining-interest loans, balance is total remaining * principal. */ if (loanParams.loanType.getValue() == InterestType.FLAT.getValue()) { Money balance = new Money(getCurrency(), totalPrincipal.add(totalInterest)); for (PaymentDetail paymentDetail : payments) { balance = balance.subtract(paymentDetail.getPrincipal()).subtract(paymentDetail.getInterest()); paymentDetail.setBalance(balance); } } else if (loanParams.loanType.getValue() == InterestType.DECLINING.getValue()) { Money balance = new Money(getCurrency(), totalPrincipal); for (PaymentDetail paymentDetail : payments) { balance = balance.subtract(paymentDetail.getPrincipal()); paymentDetail.setBalance(balance); } } return calculatedResult; } private void parseLoanParams(String paramType, String line, LoanParameters loanParams) { String tempLine = line.substring(paramType.length(), line.length() - 1); String[] tokens = tempLine.split(","); for (String token : tokens) { if (StringUtils.isNotBlank(token)) { if ((paramType.indexOf(principal) >= 0) && (loanParams.getPrincipal() == null)) { loanParams.setPrincipal(token); } else if (paramType.indexOf(loanType) >= 0) { InterestType interestType = null; if (token.equals("Fixed Principal")) { interestType = InterestType.DECLINING_EPI; } else { interestType = InterestType.valueOf(token.toUpperCase()); } loanParams.setLoanType(interestType); } else if (paramType.indexOf(annualInterest) >= 0) { int pos = token.indexOf("%"); String interest = token.substring(0, pos); loanParams.setAnnualInterest(interest); } else if (paramType.indexOf(numberOfPayments) >= 0) { loanParams.setNumberOfPayments(Short.parseShort(token)); } else if (paramType.indexOf(paymentFrequency) >= 0) { RecurrenceType recurrenceType = RecurrenceType.valueOf(token.toUpperCase()); loanParams.setPaymentFrequency(recurrenceType); } break; } } } private String getToken(String line, String param) { int index = line.indexOf(param); line = line.substring(index + param.length(), line.length() - 1); String[] tokens = line.split(","); String token = null; for (String token2 : tokens) { token = token2; if (StringUtils.isNotBlank(token)) { break; } } return token; } private void parseConfigParams(String paramType, String line, InternalConfiguration config) { String tempLine = line.substring(paramType.length(), line.length() - 1); String[] tokens = tempLine.split(","); for (String token2 : tokens) { String token = token2; if (StringUtils.isNotBlank(token)) { if (paramType.indexOf(initialRoundingMode) >= 0) { RoundingMode mode = RoundingMode.valueOf(token.toUpperCase()); config.setInitialRoundingMode(mode); token = getToken(tempLine, feeFrequency); if (token.toUpperCase().equals("PERIODIC")) { config.setFeeFrequency(FeeFrequencyType.PERIODIC); } else if (token.toUpperCase().equals("ONETIME")) { config.setFeeFrequency(FeeFrequencyType.ONETIME); } else { config.setFeeFrequency(null); } } else if (paramType.indexOf(initialRoundOffMultiple) >= 0) { config.setInitialRoundOffMultiple(token); token = getToken(tempLine, feeType); config.setIsFeeRateBased(true); if (token.equals(feeTypePrincipalPlusInterest)) { config.setFeeType(FeeFormula.AMOUNT_AND_INTEREST); } else if (token.equals(feeTypeInterest)) { config.setFeeType(FeeFormula.INTEREST); } else if (token.equals(feeTypePrincipal)) { config.setFeeType(FeeFormula.AMOUNT); } else if (token.equals(feeTypeValue)) { // don't use FeeFormula config.setIsFeeRateBased(false); } else { throw new RuntimeException("Unrecognized fee type: " + token); } } else if (paramType.indexOf(finalRoundingMode) >= 0) { RoundingMode mode = RoundingMode.valueOf(token.toUpperCase()); config.setFinalRoundingMode(mode); token = getToken(tempLine, feeValue); config.setFeeValue(token); } else if (paramType.indexOf(finalRoundOffMultiple) >= 0) { config.setFinalRoundOffMultiple(token); token = getToken(tempLine, feePercentage); int pos = token.indexOf("%"); token = token.substring(0, pos); config.setFeePercentage(token); } else if (paramType.indexOf(currencyRounding) >= 0) { RoundingMode mode = RoundingMode.valueOf(token.toUpperCase()); config.setCurrencyRoundingMode(mode); token = getToken(tempLine, gracePeriodType); GraceType type = null; if (token.toUpperCase().equals("ALL")) { type = GraceType.GRACEONALLREPAYMENTS; } else if (token.toUpperCase().equals("PRINCIPAL")) { type = GraceType.PRINCIPALONLYGRACE; } else { type = GraceType.NONE; } config.setGracePeriodType(type); } else if (paramType.indexOf(digitsAfterDecimal) >= 0) { config.setDigitsAfterDecimal(Short.parseShort(token)); token = getToken(tempLine, gracePeriod); config.setGracePeriod(Short.parseShort(token)); } else if (paramType.indexOf(daysInYear) >= 0) { config.setDaysInYear(Short.parseShort(token)); } /* * else if (paramType.indexOf(gracePeriodType)>= 0) { GraceType type = null; if * (token.toUpperCase().equals("ALL")) type = GraceType.GRACEONALLREPAYMENTS; else if * (token.toUpperCase().equals("PRINCIPAL")) type = GraceType.PRINCIPALONLYGRACE; else type = * GraceType.NONE; * * config.setGracePeriodType(type); } else if (paramType.indexOf(gracePeriod)>= 0) { if * (config.getGracePeriodType() != GraceType.NONE) config.setGracePeriod(Short.parseShort(token)); } */ break; } } } private void parseTotals(String paramType, String line, Results result) { String tempLine = line.substring(paramType.length(), line.length() - 1); int index = tempLine.indexOf(paramType); tempLine = tempLine.substring(index + paramType.length(), tempLine.length() - 1); String[] tokens = tempLine.split(","); boolean totalPayments = false; boolean totalInterests = true; boolean totalFee = true; boolean totalPrincipals = true; for (String token2 : tokens) { String token = token2.trim(); if (StringUtils.isNotBlank(token)) { if (totalPayments == false) { result.setTotalPayments(new Money(getCurrency(), token)); totalPayments = true; totalPrincipals = false; } else if (totalInterests == false) { result.setTotalInterest(new Money(getCurrency(), token)); totalInterests = true; totalFee = false; } else if (totalFee == false) { result.setTotalFee(new Money(getCurrency(), token)); totalFee = true; } else if (totalPrincipals == false) { result.setTotalPrincipal(new Money(getCurrency(), token)); totalPrincipals = true; totalInterests = false; } else { return; } } } } private void parse999Account(String paramType, String line, Results result) { String tempLine = line.substring(paramType.length(), line.length() - 1); int index = tempLine.indexOf(paramType); tempLine = tempLine.substring(index + paramType.length(), tempLine.length() - 1); String[] tokens = tempLine.split(","); if (tokens.length < 2) { return; } result.setAccount999(new Money(getCurrency(), tokens[1]).negate()); } private void parseRoundedTotalInterest(String paramType, String line, Results result) { String tempLine = line.substring(paramType.length(), line.length() - 1); int index = tempLine.indexOf(paramType); tempLine = tempLine.substring(index + paramType.length(), tempLine.length() - 1); String[] tokens = tempLine.split(","); if (tokens.length < 8) { return; } result.setRoundedTotalInterest(new Money(getCurrency(), tokens[3])); } private void parsePaymentDetail(String paramType, String line, Results result) { int index = line.indexOf(",,"); String tempLine = line.substring(index + 1, line.length() - 1); String[] tokens = tempLine.split(","); boolean paymentIndex = false; boolean payment = true; boolean principal = true; boolean interest = true; boolean balance = true; boolean fee = true; PaymentDetail paymentDetail = new PaymentDetail(); for (String token2 : tokens) { String token = token2.trim(); if (StringUtils.isNotBlank(token)) { if (paymentIndex == false) { int paymentNumber = Integer.parseInt(token); int expectedPaymentNumber = result.getPayments().size() + 1; if (paymentNumber != expectedPaymentNumber) { throw new RuntimeException("Parsing error. paymentNumber " + paymentNumber + " Expected: " + expectedPaymentNumber); } paymentIndex = true; payment = false; } else if (payment == false) { paymentDetail.setPayment(new Money(getCurrency(), token)); payment = true; principal = false; } else if (principal == false) { paymentDetail.setPrincipal(new Money(getCurrency(), token)); principal = true; interest = false; } else if (interest == false) { paymentDetail.setInterest(new Money(getCurrency(), token)); interest = true; fee = false; } else if (fee == false) { paymentDetail.setFee(new Money(getCurrency(), token)); fee = true; balance = false; } else if (balance == false) { paymentDetail.setBalance(new Money(getCurrency(), token)); result.getPayments().add(paymentDetail); return; } } } } private LoanTestCaseData loadSpreadSheetData(String fileName) throws URISyntaxException { File file = new File(ClasspathResource.getURI(fileName)); FileInputStream fileInputStream = null; InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; LoanTestCaseData testCaseData = new LoanTestCaseData(); boolean startPayment = false; int paymentIndex = 0; String line = null; try { fileInputStream = new FileInputStream(file); inputStreamReader = new InputStreamReader(fileInputStream); bufferedReader = new BufferedReader(inputStreamReader); // dataInputStream.available() returns 0 if the file does not have // more lines. LoanParameters loanParams = new LoanParameters(); InternalConfiguration config = new InternalConfiguration(); Results expectedResult = new Results(); List<PaymentDetail> list = new ArrayList<PaymentDetail>(); expectedResult.setPayments(list); while ((line = bufferedReader.readLine()) != null) { String[] tokens = line.split(","); for (String token : tokens) { if (StringUtils.isNotBlank(token)) { if ((token.indexOf(principal) >= 0) || (token.indexOf(loanType) >= 0) || (token.indexOf(annualInterest) >= 0) || (token.indexOf(numberOfPayments) >= 0) || (token.indexOf(paymentFrequency) >= 0)) { parseLoanParams(token, line, loanParams); break; } else if ((token.indexOf(initialRoundingMode) >= 0) || (token.indexOf(finalRoundingMode) >= 0) || (token.indexOf(initialRoundOffMultiple) >= 0) || (token.indexOf(finalRoundOffMultiple) >= 0) || (token.indexOf(currencyRounding) >= 0) || (token.indexOf(digitsAfterDecimal) >= 0) || (token.indexOf(daysInYear) >= 0)) { parseConfigParams(token, line, config); break; } else if (token.indexOf(calculatedTotals) == 0) { parseRoundedTotalInterest(token, line, expectedResult); } else if (token.indexOf(account999) == 0) { parse999Account(token, line, expectedResult); } else if (token.indexOf(totals) >= 0) { parseTotals(token, line, expectedResult); } else if (token.indexOf(start) >= 0) { startPayment = true; break; } else if (startPayment) { parsePaymentDetail(token, line, expectedResult); paymentIndex++; if (paymentIndex >= loanParams.getNumberOfPayments()) { testCaseData.setExpectedResult(expectedResult); testCaseData.setInternalConfig(config); testCaseData.setLoanParams(loanParams); return testCaseData; } break; } } } } fileInputStream.close(); inputStreamReader.close(); bufferedReader.close(); } catch (Exception e) { throw new RuntimeException(e); } return testCaseData; } /* * This test case will populate the data classes for a loan test case with data from spreadsheet and calculates * payments and compares */ private void runOneTestCaseWithDataFromSpreadSheet(String directoryName, String fileName) throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException { if (isAllConsoleOutputEnabled() || isFileNameConsoleOutputEnabled()) { System.out.println("Running Test: " + fileName); } LoanTestCaseData testCaseData = loadSpreadSheetData(directoryName + fileName); accountBO = setUpLoan(testCaseData.getInternalConfig(), testCaseData.getLoanParams()); // calculated results Results calculatedResult = calculatePayments(testCaseData.getInternalConfig(), accountBO, testCaseData.getLoanParams()); compareResults(testCaseData.getExpectedResult(), calculatedResult, fileName); } private String[] getCSVFiles(String directoryPath) throws URISyntaxException { File dir = new File(ClasspathResource.getURI(directoryPath)); FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".csv"); } }; return dir.list(filter); } @Test public void testCaseWithDataFromSpreadSheets() throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException { // String rootPath = // "org/mifos/accounts/loan/business/testCaseData/flatInterest/"; String rootPath = "org/mifos/accounts/loan/business/testCaseData/"; // String[] dataFileNames = {"testcases-2008-04-22.set1.01.csv"}; String[] dataFileNames = { "loan-repayment-master-test1.csv" }; for (String dataFileName : dataFileNames) { runOneTestCaseWithDataFromSpreadSheet(rootPath, dataFileName); } } @Test public void testIssue1623FromSpreadSheets() throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException, URISyntaxException { String rootPath = "org/mifos/accounts/loan/business/testCaseData/"; String[] dataFileNames = { "loan-repayment-master-issue1623.csv" }; for (String dataFileName : dataFileNames) { runOneTestCaseWithDataFromSpreadSheet(rootPath, dataFileName); } } @Test public void testFlatInterestTestCases() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/flatInterest/"; String[] dataFileNames = getCSVFiles(rootPath); for (String dataFileName : dataFileNames) { if ( // dataFileNames[i].contains("set1.23") fileNameContains(dataFileName, flatGraceFeeTestCases) || fileNameContains(dataFileName, flatNegativeLastPaymentTestCases)) { runOneTestCaseWithDataFromSpreadSheet(rootPath, dataFileName); StaticHibernateUtil.clearSession(); StaticHibernateUtil.rollbackTransaction(); } } } @Test public void testDecliningInterestTestCases() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningInterest/"; String[] dataFileNames = getCSVFiles(rootPath); for (String dataFileName : dataFileNames) { if (fileNameContains(dataFileName, decliningGraceFeeTestCases) || fileNameContains(dataFileName, decliningNegativeLastPaymentTestCases)) { runOneTestCaseWithDataFromSpreadSheet(rootPath, dataFileName); StaticHibernateUtil.clearSession(); StaticHibernateUtil.rollbackTransaction(); } } } public void xtestAllDecliningInterestEdgeTestCases() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/decliningInterestedge/"; String[] dataFileNames = getCSVFiles(rootPath); for (String dataFileName : dataFileNames) { if (dataFileName.startsWith("testcase") && fileNameContains(dataFileName, selectedCaseNumbers)) { runOneTestCaseWithDataFromSpreadSheet(rootPath, dataFileName); } } } // marked "@Ignore" in JUnit4, so converting to xtest so it won't run in JUnit3 public void xtestAllFlatInterestEdgeTestCases() throws Exception { String rootPath = "org/mifos/accounts/loan/business/testCaseData/flatinterestedge/"; String[] dataFileNames = getCSVFiles(rootPath); for (String dataFileName : dataFileNames) { if (dataFileName.startsWith("testcase") && fileNameContains(dataFileName, flatTestCases)) { runOneTestCaseWithDataFromSpreadSheet(rootPath, dataFileName); } } } private String[] flatTestCases = { "testcase-2008-05-12-flat-set1.01", "testcase-2008-05-12-flat-set1.02", "testcase-2008-05-12-flat-set1.03", "testcase-2008-05-12-flat-set1.04", "testcase-2008-05-12-flat-set1.05", "testcase-2008-05-12-flat-set1.06", "testcase-2008-05-12-flat-set1.07", "testcase-2008-05-12-flat-set1.08", "testcase-2008-05-12-flat-set1.09", "testcase-2008-05-12-flat-set1.10", "testcase-2008-05-12-flat-set1.11", "testcase-2008-05-12-flat-set1.12", "testcase-2008-05-12-flat-set1.13", "testcase-2008-05-12-flat-set1.14", "testcase-2008-05-12-flat-set1.15", "testcase-2008-05-12-flat-set1.16", "testcase-2008-05-12-flat-set1.17", "testcase-2008-05-12-flat-set1.18", "testcase-2008-05-12-flat-set1.19" }; private String[] flatNegativeLastPaymentTestCases = { "testcase-2008-05-27-flat-negative-payment-set1.01.csv", "testcase-2008-05-27-flat-negative-payment-set1.02.csv", "testcase-2008-05-27-flat-negative-payment-set1.03.csv" }; private String[] decliningNegativeLastPaymentTestCases = { "testcase-2008-05-27-declining-negative-payment-set1.01.csv", "testcase-2008-05-27-declining-negative-payment-set1.02.csv", "testcase-2008-05-27-declining-negative-payment-set1.03.csv" }; private String[] flatGraceFeeTestCases = { "testcase-2008-05-13-flat-grace-fee-set1.01", "testcase-2008-05-13-flat-grace-fee-set1.02", "testcase-2008-05-13-flat-grace-fee-set1.03", "testcase-2008-05-13-flat-grace-fee-set1.04", "testcase-2008-05-13-flat-grace-fee-set1.05", "testcase-2008-05-13-flat-grace-fee-set1.06", "testcase-2008-05-13-flat-grace-fee-set1.07", "testcase-2008-05-13-flat-grace-fee-set1.08", "testcase-2008-05-13-flat-grace-fee-set1.09", "testcase-2008-05-13-flat-grace-fee-set1.10", "testcase-2008-05-13-flat-grace-fee-set1.11", "testcase-2008-05-13-flat-grace-fee-set1.12", "testcase-2008-05-13-flat-grace-fee-set1.13", "testcase-2008-05-13-flat-grace-fee-set1.14", "testcase-2008-05-13-flat-grace-fee-set1.15", "testcase-2008-05-13-flat-grace-fee-set1.16", "testcase-2008-05-13-flat-grace-fee-set1.17", "testcase-2008-05-13-flat-grace-fee-set1.18", "testcase-2008-05-13-flat-grace-fee-set1.19", "testcase-2008-05-13-flat-grace-fee-set1.20", "testcase-2008-05-13-flat-grace-fee-set1.21", "testcase-2008-05-13-flat-grace-fee-set1.22", "testcase-2008-05-13-flat-grace-fee-set1.23", "testcase-2008-05-13-flat-grace-fee-set1.24", "testcase-2008-05-13-flat-grace-fee-set1.25", "testcase-2008-05-13-flat-grace-fee-set1.26" }; private String[] decliningEPIGraceFeeTestCases = { "testcase-2008-06-27-decliningEPI-grace-fee-set1.01", "testcase-2008-06-27-decliningEPI-grace-fee-set1.02", "testcase-2008-06-27-decliningEPI-grace-fee-set1.03", "testcase-2008-06-27-decliningEPI-grace-fee-set1.04", "testcase-2008-06-27-decliningEPI-grace-fee-set1.05", "testcase-2008-06-27-decliningEPI-grace-fee-set1.06", "testcase-2008-06-27-decliningEPI-grace-fee-set1.07", "testcase-2008-06-27-decliningEPI-grace-fee-set1.08", "testcase-2008-06-27-decliningEPI-grace-fee-set1.09", "testcase-2008-06-27-decliningEPI-grace-fee-set1.10", "testcase-2008-06-27-decliningEPI-grace-fee-set1.11", "testcase-2008-06-27-decliningEPI-grace-fee-set1.12", "testcase-2008-06-27-decliningEPI-grace-fee-set1.13", "testcase-2008-06-27-decliningEPI-grace-fee-set1.14", "testcase-2008-06-27-decliningEPI-grace-fee-set1.15", "testcase-2008-06-27-decliningEPI-grace-fee-set1.16", "testcase-2008-06-27-decliningEPI-grace-fee-set1.17", "testcase-2008-06-27-decliningEPI-grace-fee-set1.18", "testcase-2008-06-27-decliningEPI-grace-fee-set1.19", "testcase-2008-06-27-decliningEPI-grace-fee-set1.20", "testcase-2008-06-27-decliningEPI-grace-fee-set1.21", "testcase-2008-06-27-decliningEPI-grace-fee-set1.22", "testcase-2008-06-27-decliningEPI-grace-fee-set1.23", "testcase-2008-06-27-decliningEPI-grace-fee-set1.24", "testcase-2008-06-27-decliningEPI-grace-fee-set1.25", "testcase-2008-06-27-decliningEPI-grace-fee-set1.26" }; private String[] decliningGraceFeeTestCases = { "testcase-2008-05-13-declining-grace-fee-set1.01", "testcase-2008-05-13-declining-grace-fee-set1.02", "testcase-2008-05-13-declining-grace-fee-set1.03", "testcase-2008-05-13-declining-grace-fee-set1.04", "testcase-2008-05-13-declining-grace-fee-set1.05", "testcase-2008-05-13-declining-grace-fee-set1.06", "testcase-2008-05-13-declining-grace-fee-set1.07", "testcase-2008-05-13-declining-grace-fee-set1.08", "testcase-2008-05-13-declining-grace-fee-set1.09", "testcase-2008-05-13-declining-grace-fee-set1.10", "testcase-2008-05-13-declining-grace-fee-set1.11", "testcase-2008-05-13-declining-grace-fee-set1.12", "testcase-2008-05-13-declining-grace-fee-set1.13", "testcase-2008-05-13-declining-grace-fee-set1.14", "testcase-2008-05-13-declining-grace-fee-set1.15", "testcase-2008-05-13-declining-grace-fee-set1.16", "testcase-2008-05-13-declining-grace-fee-set1.17", "testcase-2008-05-13-declining-grace-fee-set1.18", "testcase-2008-05-13-declining-grace-fee-set1.19", "testcase-2008-05-13-declining-grace-fee-set1.20", "testcase-2008-05-13-declining-grace-fee-set1.21", "testcase-2008-05-13-declining-grace-fee-set1.22", "testcase-2008-05-13-declining-grace-fee-set1.23", "testcase-2008-05-13-declining-grace-fee-set1.24", "testcase-2008-05-13-declining-grace-fee-set1.25", "testcase-2008-05-13-declining-grace-fee-set1.26" }; private String[] selectedCaseNumbers = { // "set1.01", //JDBC error could not insert LoanOfferingBO // "set1.02a", // "set1.03", //negative principal payments // "set1.04", // "set1.05" //Infinite or NAN // "set1.06", //JDBC error could not insert LoanOfferingBO // "set1.07", //negative principal payments // "set1.08", // "set1.09", // "set1.10" //Infinite or NAN // "set1.11", //JDBC error could not insert LoanOfferingBO // "set1.12", "set1.13" // assertion failed -- comparison failure // "set1.14", // "set1.15", //spreadsheet error // "set1.16", //spreadsheet error // "set1.17", //spreadsheet error // "set1.18", // "set1.19", //JDBC error could not insert LoanOfferingBO // "set1.20", // "set1.21", //Infinite or NAN // "set1.22", //spreadsheet error // "set1.23", // "set1.24", //JDBC error could not insert LoanOfferingBO // "set1.25", //spreadsheet error // "set1.26" //Infinite or NAN // "set2" }; private boolean fileNameContains(String fileName, String[] testNumbers) { for (String testNumber : testNumbers) { if (fileName.contains(testNumber)) { return true; } } return false; } /* * This test case populates data from spreadsheet for loan params and expected results */ // marked "@Ignore" in JUnit4, so converting to xtest so it won't run in JUnit3 // getting NullPointerException @Test public void testOneExampleOfTestCaseFromSpreadSheet() throws NumberFormatException, PropertyNotFoundException, SystemException, ApplicationException { // set up config InternalConfiguration config = new InternalConfiguration(); config.setDaysInYear(365); config.setFinalRoundingMode(RoundingMode.CEILING); config.setFinalRoundOffMultiple("0.01"); config.setInitialRoundingMode(RoundingMode.CEILING); config.setInitialRoundOffMultiple("1"); config.setCurrencyRoundingMode(RoundingMode.CEILING); config.setInternalPrecision(13); config.setDigitsAfterDecimal(3); // set up loan params LoanParameters loanParams = new LoanParameters(); loanParams.setLoanType(InterestType.FLAT); loanParams.setNumberOfPayments((short) 5); loanParams.setPaymentFrequency(RecurrenceType.WEEKLY); loanParams.setAnnualInterest("12.00"); loanParams.setPrincipal("1002"); // set up expected results Results expectedResult = new Results(); expectedResult.setTotalInterest(new Money(getCurrency(), "11.53")); expectedResult.setTotalPayments(new Money(getCurrency(), "1013.53")); expectedResult.setTotalPrincipal(new Money(getCurrency(), "1002")); // this loan amount List<PaymentDetail> list = new ArrayList<PaymentDetail>(); // 1st payment PaymentDetail payment = new PaymentDetail(); payment.setPayment(new Money(getCurrency(), "203.000")); payment.setInterest(new Money(getCurrency(), "2.306")); payment.setBalance(new Money(getCurrency(), "810.530")); payment.setPrincipal(new Money(getCurrency(), "200.694")); list.add(payment); // 2nd payment payment = new PaymentDetail(); payment.setPayment(new Money(getCurrency(), "203.000")); payment.setInterest(new Money(getCurrency(), "2.306")); payment.setBalance(new Money(getCurrency(), "607.530")); payment.setPrincipal(new Money(getCurrency(), "200.694")); list.add(payment); // 3rd payment payment = new PaymentDetail(); payment.setPayment(new Money(getCurrency(), "203.000")); payment.setInterest(new Money(getCurrency(), "2.306")); payment.setBalance(new Money(getCurrency(), "404.530")); payment.setPrincipal(new Money(getCurrency(), "200.694")); list.add(payment); // 4th payment payment = new PaymentDetail(); payment.setPayment(new Money(getCurrency(), "203.000")); payment.setInterest(new Money(getCurrency(), "2.306")); payment.setBalance(new Money(getCurrency(), "201.530")); payment.setPrincipal(new Money(getCurrency(), "200.694")); list.add(payment); // last payment payment = new PaymentDetail(); payment.setPayment(new Money(getCurrency(), "201.530")); payment.setInterest(new Money(getCurrency(), "2.306")); payment.setBalance(new Money(getCurrency(), "0")); payment.setPrincipal(new Money(getCurrency(), "199.224")); list.add(payment); expectedResult.setPayments(list); expectedResult.setPayments(list); accountBO = setUpLoan(config, loanParams); // calculated results Results calculatedResult = calculatePayments(config, accountBO, loanParams); compareResults(expectedResult, calculatedResult, "testOneExampleOfTestCaseFromSpreadSheet"); } class PaymentDetail { Money payment = null; Money principal = null; Money interest = null; Money fee = null; Money balance = null; public PaymentDetail(Money payment, Money principal, Money interest, Money balance) { super(); this.payment = payment; this.principal = principal; this.interest = interest; this.balance = balance; } public PaymentDetail() { } public Money getBalance() { return balance; } public void setBalance(Money balance) { this.balance = balance; } public Money getInterest() { return interest; } public void setInterest(Money interest) { this.interest = interest; } public Money getPayment() { return payment; } public void setPayment(Money payment) { this.payment = payment; } public Money getPrincipal() { return principal; } public void setPrincipal(Money principal) { this.principal = principal; } public Money getFee() { return fee; } public void setFee(Money fee) { this.fee = fee; } } }