org.mifos.accounts.business.AccountBO.java Source code

Java tutorial

Introduction

Here is the source code for org.mifos.accounts.business.AccountBO.java

Source

/*
 * 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.business;

import static org.mifos.accounts.util.helpers.AccountTypes.LOAN_ACCOUNT;
import static org.mifos.accounts.util.helpers.AccountTypes.SAVINGS_ACCOUNT;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifos.accounts.exceptions.AccountException;
import org.mifos.accounts.fees.business.FeeBO;
import org.mifos.accounts.fees.persistence.FeeDao;
import org.mifos.accounts.fees.util.helpers.FeeFrequencyType;
import org.mifos.accounts.fees.util.helpers.FeeStatus;
import org.mifos.accounts.financial.business.FinancialTransactionBO;
import org.mifos.accounts.financial.business.service.FinancialBusinessService;
import org.mifos.accounts.loan.business.LoanBO;
import org.mifos.accounts.persistence.LegacyAccountDao;
import org.mifos.accounts.savings.business.SavingsBO;
import org.mifos.accounts.util.helpers.AccountActionTypes;
import org.mifos.accounts.util.helpers.AccountConstants;
import org.mifos.accounts.util.helpers.AccountExceptionConstants;
import org.mifos.accounts.util.helpers.AccountState;
import org.mifos.accounts.util.helpers.AccountTypes;
import org.mifos.accounts.util.helpers.CustomerAccountPaymentData;
import org.mifos.accounts.util.helpers.FeeInstallment;
import org.mifos.accounts.util.helpers.InstallmentDate;
import org.mifos.accounts.util.helpers.PaymentData;
import org.mifos.accounts.util.helpers.WaiveEnum;
import org.mifos.application.admin.servicefacade.InvalidDateException;
import org.mifos.application.holiday.business.Holiday;
import org.mifos.application.holiday.persistence.HolidayDao;
import org.mifos.application.master.business.CustomFieldType;
import org.mifos.application.master.business.MifosCurrency;
import org.mifos.application.master.persistence.LegacyMasterDao;
import org.mifos.application.meeting.business.MeetingBO;
import org.mifos.application.servicefacade.ApplicationContextProvider;
import org.mifos.clientportfolio.newloan.domain.CreationDetail;
import org.mifos.config.AccountingRules;
import org.mifos.config.FiscalCalendarRules;
import org.mifos.customers.business.CustomerAccountBO;
import org.mifos.customers.business.CustomerBO;
import org.mifos.customers.business.CustomerMeetingEntity;
import org.mifos.customers.office.business.OfficeBO;
import org.mifos.customers.personnel.business.PersonnelBO;
import org.mifos.dto.domain.AccountPaymentParametersDto;
import org.mifos.dto.domain.CustomFieldDto;
import org.mifos.dto.screen.TransactionHistoryDto;
import org.mifos.framework.business.AbstractBusinessObject;
import org.mifos.framework.exceptions.PersistenceException;
import org.mifos.framework.util.DateTimeService;
import org.mifos.framework.util.helpers.DateUtils;
import org.mifos.framework.util.helpers.Money;
import org.mifos.schedule.ScheduledDateGeneration;
import org.mifos.schedule.ScheduledEvent;
import org.mifos.schedule.ScheduledEventFactory;
import org.mifos.schedule.internal.HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration;
import org.mifos.security.util.UserContext;
import org.mifos.service.BusinessRuleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccountBO extends AbstractBusinessObject {

    private static final Logger logger = LoggerFactory.getLogger(AccountBO.class);

    private Integer accountId;
    protected String globalAccountNum;
    private String externalId;
    protected AccountTypeEntity accountType;
    protected CustomerBO customer;
    protected OfficeBO office;
    protected PersonnelBO personnel;
    private AccountStateEntity accountState;
    private Date closedDate;
    private Integer offsettingAllowable;

    // associations
    protected Set<AccountNotesEntity> accountNotes;
    protected List<AccountStatusChangeHistoryEntity> accountStatusChangeHistory = new ArrayList<AccountStatusChangeHistoryEntity>();
    private final Set<AccountFlagMapping> accountFlags;

    /**
     * Links this loan to its applied fees
     */
    protected Set<AccountFeesEntity> accountFees;
    protected Set<AccountActionDateEntity> accountActionDates;
    protected List<AccountPaymentEntity> accountPayments;
    private Set<AccountCustomFieldEntity> accountCustomFields;

    private LegacyAccountDao legacyAccountDao = null;
    private LegacyMasterDao legacyMasterDao = null;
    private DateTimeService dateTimeService = null;
    private FinancialBusinessService financialBusinessService = null;

    public FinancialBusinessService getFinancialBusinessService() {
        if (null == financialBusinessService) {
            financialBusinessService = new FinancialBusinessService();
        }
        return financialBusinessService;
    }

    public void setFinancialBusinessService(final FinancialBusinessService financialBusinessService) {
        this.financialBusinessService = financialBusinessService;
    }

    public DateTimeService getDateTimeService() {
        if (null == dateTimeService) {
            dateTimeService = new DateTimeService();
        }
        return dateTimeService;
    }

    protected FeeDao getFeeDao() {
        return ApplicationContextProvider.getBean(FeeDao.class);
    }

    public void setDateTimeService(final DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    public LegacyAccountDao getlegacyAccountDao() {
        if (null == legacyAccountDao) {
            legacyAccountDao = ApplicationContextProvider.getBean(LegacyAccountDao.class);
        }
        return legacyAccountDao;
    }

    public void setlegacyAccountDao(final LegacyAccountDao legacyAccountDao) {
        this.legacyAccountDao = legacyAccountDao;
    }

    public LegacyMasterDao getlegacyMasterDao() {
        if (null == legacyMasterDao) {
            legacyMasterDao = ApplicationContextProvider.getBean(LegacyMasterDao.class);
        }
        return legacyMasterDao;
    }

    /**
     * default constructor for hibernate usage
     */
    protected AccountBO() {
        this(null);
    }

    /**
     * minimal legal constructor
     */
    public AccountBO(final AccountTypes accountType, final AccountState accountState, final CustomerBO customer,
            final Integer offsettingAllowable, final Set<AccountActionDateEntity> scheduledPayments,
            final Set<AccountFeesEntity> accountFees, final Date createdDate, final Short createdByUserId) {
        this.accountId = null;
        this.accountType = new AccountTypeEntity(accountType.getValue());
        this.accountState = new AccountStateEntity(accountState);
        this.customer = customer;
        this.offsettingAllowable = offsettingAllowable;
        this.createdDate = createdDate;
        this.createdBy = createdByUserId;
        this.accountActionDates = scheduledPayments;
        this.accountFees = accountFees;
        if (customer != null) {
            this.office = customer.getOffice();
            this.personnel = customer.getPersonnel();
        }
        this.accountFlags = new LinkedHashSet<AccountFlagMapping>();
        this.accountCustomFields = new LinkedHashSet<AccountCustomFieldEntity>();
        this.accountPayments = new ArrayList<AccountPaymentEntity>();
    }

    /**
     * minimal legal constructor for savings accounts
     */
    public AccountBO(AccountTypes accountType, AccountState accountState, CustomerBO customer,
            List<? extends AccountActionDateEntity> scheduledRepaymentsOrDeposits, CreationDetail creationDetail) {
        this.accountId = null;
        this.accountType = new AccountTypeEntity(accountType.getValue());
        this.accountState = new AccountStateEntity(accountState);
        this.customer = customer;
        this.createdDate = creationDetail.getCreatedDate().toDate();
        this.createdBy = creationDetail.getCreatedBy().shortValue();

        // ensure scheduled payments are linked to this account.
        for (AccountActionDateEntity scheduledPayment : scheduledRepaymentsOrDeposits) {
            scheduledPayment.setAccount(this);
        }

        this.accountActionDates = new LinkedHashSet<AccountActionDateEntity>(scheduledRepaymentsOrDeposits);
        if (customer != null) {
            this.office = customer.getOffice();
            this.personnel = customer.getPersonnel();
        }
        this.accountFlags = new LinkedHashSet<AccountFlagMapping>();
        this.accountCustomFields = new LinkedHashSet<AccountCustomFieldEntity>();
        this.accountPayments = new ArrayList<AccountPaymentEntity>();

        this.offsettingAllowable = Integer.valueOf(1);
        this.accountFees = new HashSet<AccountFeesEntity>();
    }

    /**
     * @deprecated use minimal legal constructor
     */
    @Deprecated
    AccountBO(final Integer accountId) {
        this.accountId = accountId;
        globalAccountNum = null;
        customer = null;
        office = null;
        personnel = null;
        accountType = null;
        accountFees = new LinkedHashSet<AccountFeesEntity>();
        accountPayments = new ArrayList<AccountPaymentEntity>();
        accountActionDates = new LinkedHashSet<AccountActionDateEntity>();
        accountCustomFields = new LinkedHashSet<AccountCustomFieldEntity>();
        accountNotes = new LinkedHashSet<AccountNotesEntity>();
        accountStatusChangeHistory = new ArrayList<AccountStatusChangeHistoryEntity>();
        accountFlags = new LinkedHashSet<AccountFlagMapping>();
        offsettingAllowable = new Integer(1);
    }

    /**
     * @deprecated use minimal legal constructor
     */
    @Deprecated
    protected AccountBO(final UserContext userContext, final CustomerBO customer, final AccountTypes accountType,
            final AccountState accountState) throws AccountException {
        super(userContext);
        validate(userContext, customer, accountType, accountState);
        accountFees = new LinkedHashSet<AccountFeesEntity>();
        accountPayments = new ArrayList<AccountPaymentEntity>();
        accountActionDates = new LinkedHashSet<AccountActionDateEntity>();
        accountCustomFields = new LinkedHashSet<AccountCustomFieldEntity>();
        accountNotes = new LinkedHashSet<AccountNotesEntity>();
        accountStatusChangeHistory = new ArrayList<AccountStatusChangeHistoryEntity>();
        accountFlags = new LinkedHashSet<AccountFlagMapping>();
        this.accountId = null;
        this.customer = customer;
        this.accountType = new AccountTypeEntity(accountType.getValue());
        if (customer != null) {
            this.office = customer.getOffice();
            this.personnel = customer.getPersonnel();
        }
        this.setAccountState(new AccountStateEntity(accountState));
        this.offsettingAllowable = new Integer(1);
        this.setCreateDetails();
    }

    public Integer getAccountId() {
        return accountId;
    }

    public String getGlobalAccountNum() {
        return globalAccountNum;
    }

    /**
     * Obsolete; most/all callers should call {@link #getType()} instead.
     */
    public AccountTypeEntity getAccountType() {
        return accountType;
    }

    public CustomerBO getCustomer() {
        return customer;
    }

    public OfficeBO getOffice() {
        return office;
    }

    public PersonnelBO getPersonnel() {
        return personnel;
    }

    public Set<AccountNotesEntity> getAccountNotes() {
        return accountNotes;
    }

    public List<AccountStatusChangeHistoryEntity> getAccountStatusChangeHistory() {
        return accountStatusChangeHistory;
    }

    public AccountStatusChangeHistoryEntity getLastAccountStatusChange() {
        return accountStatusChangeHistory.get(accountStatusChangeHistory.size() - 1);
    }

    /**
     * For most purposes this is deprecated and one should call {@link #getState()} instead.
     */
    public AccountStateEntity getAccountState() {
        return accountState;
    }

    public Set<AccountFlagMapping> getAccountFlags() {
        return accountFlags;
    }

    /**
     * Returns the set of {@link AccountFeesEntity}s -- links to the fees that apply to this loan.
     */
    public Set<AccountFeesEntity> getAccountFeesIncludingInactiveFees() {
        return accountFees;
    }

    public Set<AccountFeesEntity> getAccountFees() {
        Set<AccountFeesEntity> activeAccountFees = new HashSet<AccountFeesEntity>();
        for (AccountFeesEntity accountFeesEntity : getAccountFeesIncludingInactiveFees()) {
            if (accountFeesEntity.getFeeStatus() == null
                    || accountFeesEntity.getFeeStatus().equals(FeeStatus.ACTIVE.getValue())) {
                activeAccountFees.add(accountFeesEntity);
            }
        }
        return activeAccountFees;
    }

    public Set<AccountActionDateEntity> getAccountActionDates() {
        return accountActionDates;
    }

    public List<AccountActionDateEntity> getAccountActionDatesSortedByInstallmentId() {
        List<AccountActionDateEntity> sortedList = new ArrayList<AccountActionDateEntity>(getAccountActionDates());
        Collections.sort(sortedList, new Comparator<AccountActionDateEntity>() {
            @Override
            public int compare(AccountActionDateEntity entity1, AccountActionDateEntity entity2) {
                return new Integer(entity1.getInstallmentId()).compareTo(new Integer(entity2.getInstallmentId()));
            }
        });
        return sortedList;
    }

    public List<AccountActionDateEntity> getActionDatesSortedByDate() {
        List<AccountActionDateEntity> sortedList = new ArrayList<AccountActionDateEntity>();
        sortedList.addAll(this.getAccountActionDates());
        Collections.sort(sortedList);
        return sortedList;
    }

    public List<AccountPaymentEntity> getAccountPayments() {

        return accountPayments;
    }

    public Set<AccountCustomFieldEntity> getAccountCustomFields() {
        return accountCustomFields;
    }

    public Date getClosedDate() {
        return (Date) (closedDate == null ? null : closedDate.clone());
    }

    public void setGlobalAccountNum(final String globalAccountNum) {
        this.globalAccountNum = globalAccountNum;
    }

    public void setAccountPayments(final List<AccountPaymentEntity> accountPayments) {

        this.accountPayments = accountPayments;

    }

    protected void setAccountState(final AccountStateEntity accountState) {
        this.accountState = accountState;
    }

    public void setPersonnel(final PersonnelBO personnel) {
        this.personnel = personnel;
    }

    protected void setClosedDate(final Date closedDate) {
        this.closedDate = (Date) (closedDate == null ? null : closedDate.clone());
    }

    public void addAccountStatusChangeHistory(
            final AccountStatusChangeHistoryEntity accountStatusChangeHistoryEntity) {
        this.accountStatusChangeHistory.add(accountStatusChangeHistoryEntity);
    }

    public void addAccountFees(final AccountFeesEntity fees) {
        accountFees.add(fees);
    }

    public void removeAccountFee(final AccountFeesEntity fee) {
        accountFees.remove(fee);
    }

    public void addAccountActionDate(final AccountActionDateEntity accountAction) {
        accountActionDates.add(accountAction);
    }

    public void addAccountPayment(final AccountPaymentEntity payment) {
        if (accountPayments == null) {
            accountPayments = new ArrayList<AccountPaymentEntity>();
        }
        accountPayments.add(payment);
    }

    public void addAccountNotes(final AccountNotesEntity notes) {
        if (this.accountNotes == null) {
            this.accountNotes = new LinkedHashSet<AccountNotesEntity>();
        }
        accountNotes.add(notes);
    }

    protected void addAccountFlag(final AccountStateFlagEntity flagDetail) {
        AccountFlagMapping flagMap = new AccountFlagMapping();
        flagMap.setCreatedBy(this.getUserContext().getId());
        flagMap.setCreatedDate(getDateTimeService().getCurrentJavaDateTime());
        flagMap.setFlag(flagDetail);
        this.accountFlags.add(flagMap);
    }

    public void addAccountCustomField(final AccountCustomFieldEntity customField) {
        if (customField.getFieldId() != null) {
            AccountCustomFieldEntity accountCustomField = getAccountCustomField(customField.getFieldId());
            if (accountCustomField == null) {
                customField.setAccount(this);
                this.accountCustomFields.add(customField);
            } else {
                accountCustomField.setFieldValue(customField.getFieldValue());
            }
        }
    }

    protected void addcustomFields(final List<CustomFieldDto> customFields) throws InvalidDateException {
        if (customFields != null) {
            for (CustomFieldDto view : customFields) {
                if (CustomFieldType.DATE.getValue().equals(view.getFieldType())
                        && org.apache.commons.lang.StringUtils.isNotBlank(view.getFieldValue())) {
                    SimpleDateFormat format = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT,
                            getUserContext().getPreferredLocale());
                    String userfmt = DateUtils.convertToCurrentDateFormat(format.toPattern());
                    this.getAccountCustomFields().add(new AccountCustomFieldEntity(this, view.getFieldId(),
                            DateUtils.convertUserToDbFmt(view.getFieldValue(), userfmt)));
                } else {
                    this.getAccountCustomFields()
                            .add(new AccountCustomFieldEntity(this, view.getFieldId(), view.getFieldValue()));
                }
            }
        }
    }

    public final void applyPayment(final PaymentData paymentData) throws AccountException {
        AccountPaymentEntity accountPayment = makePayment(paymentData);
        addAccountPayment(accountPayment);
        buildFinancialEntries(accountPayment.getAccountTrxns());
    }

    public PaymentData createPaymentData(final Money amount, final Date trxnDate, final String receiptId,
            final Date receiptDate, final Short paymentTypeId, PersonnelBO loggedInUser) {
        if (loggedInUser == null) {
            throw new IllegalStateException(AccountConstants.ERROR_INVALID_PERSONNEL);
        }

        PaymentData paymentData = PaymentData.createPaymentData(amount, loggedInUser, paymentTypeId, trxnDate);
        if (receiptDate != null) {
            paymentData.setReceiptDate(receiptDate);
        }
        if (receiptId != null) {
            paymentData.setReceiptNum(receiptId);
        }

        for (AccountActionDateEntity installment : getTotalInstallmentsDue()) {
            if (this instanceof CustomerAccountBO) {
                paymentData.addAccountPaymentData(new CustomerAccountPaymentData(installment));
            }
        }
        return paymentData;
    }

    public final void adjustPmnt(final String adjustmentComment, PersonnelBO loggedInUser) throws AccountException {
        if (isAdjustPossibleOnLastTrxn()) {
            logger.debug("Adjustment is possible hence attempting to adjust.");
            adjustPayment(findMostRecentNonzeroPaymentByPaymentDate(), loggedInUser, adjustmentComment);
        } else {
            throw new AccountException(AccountExceptionConstants.CANNOTADJUST);
        }
    }

    public void adjustLastPayment(final String adjustmentComment, PersonnelBO loggedInUser)
            throws AccountException {
        if (isAdjustPossibleOnLastTrxn()) {
            logger.debug("Adjustment is possible hence attempting to adjust.");
            adjustPayment(getLastPmntToBeAdjusted(), loggedInUser, adjustmentComment);
        } else {
            throw new AccountException(AccountExceptionConstants.CANNOTADJUST);
        }

    }

    protected final void adjustPayment(final AccountPaymentEntity accountPayment, final PersonnelBO personnel,
            final String adjustmentComment) throws AccountException {
        List<AccountTrxnEntity> reversedTrxns = accountPayment.reversalAdjustment(personnel, adjustmentComment);
        updateInstallmentAfterAdjustment(reversedTrxns, personnel);
        buildFinancialEntries(new LinkedHashSet<AccountTrxnEntity>(reversedTrxns));
    }

    public final void handleChangeInMeetingSchedule(final List<Days> workingDays, final List<Holiday> holidays)
            throws AccountException {
        // find the installment to update
        AccountActionDateEntity nextInstallment = findInstallmentToUpdate();
        if (nextInstallment != null) {
            regenerateFutureInstallments(nextInstallment, workingDays, holidays);
        }
    }

    /**
     * This method should be called only from a subclass. TODO KRP this method should throw a runtime exception.
     *
     * @return null
     */
    protected MeetingBO getMeetingForAccount() {
        return null;
    }

    /*
     * Find the first installment which has an enclosing "interval" such that the entire interval is after the current
     * date. For example assume March 1 is a Monday and that weeks are defined to start on Monday. If the meeting is a
     * weekly meeting on a Wednesday then the "interval" for the meeting of Wednesday March 10 is Monday March 8 to
     * Sunday March 14. If the current date was March 7, then we would return the installment for March 10 since the
     * 3/8-14 interval is after the 7th. But if today were the 8th, then we would return the following installment.
     */
    private AccountActionDateEntity findInstallmentToUpdate() {
        List<AccountActionDateEntity> allInstallments = getAllInstallments();
        if (allInstallments.size() == 0) {
            return null;
        }

        LocalDate currentDate = new LocalDate();
        MeetingBO meeting = getMeetingForAccount();

        int installmentIndex = 0;
        AccountActionDateEntity installment = allInstallments.get(installmentIndex);
        // keep looking at the next installment as long as the current date falls on or
        // after (!before) the start of the current installment
        while (installment != null && !currentDate.isBefore(
                meeting.startDateForMeetingInterval(new LocalDate(installment.getActionDate().getTime())))) {

            ++installmentIndex;
            // if we've iterated over all the installments, then just return null
            if (installmentIndex == allInstallments.size()) {
                installment = null;
            } else {
                installment = allInstallments.get(installmentIndex);
            }
        }
        return installment;
    }

    public void changeStatus(final AccountState newStatus, final Short flagId, final String comment,
            PersonnelBO loggedInUser) throws AccountException {
        changeStatus(newStatus.getValue(), flagId, comment, loggedInUser,
                getDateTimeService().getCurrentJavaDateTime());
    }

    public void changeStatus(final AccountState newStatus, final Short flagId, final String comment,
            final PersonnelBO loggedInUser, final Date transactionDate) throws AccountException {
        changeStatus(newStatus.getValue(), flagId, comment, loggedInUser, transactionDate);
    }

    public final void changeStatus(final Short newStatusId, final Short flagId, final String comment,
            PersonnelBO loggedInUser) throws AccountException {
        changeStatus(newStatusId, flagId, comment, loggedInUser, getDateTimeService().getCurrentJavaDateTime());
    }

    public final void changeStatus(final Short newStatusId, final Short flagId, final String comment,
            final PersonnelBO loggedInUser, final Date argumentDate) throws AccountException {
        Date transactionDate = argumentDate;
        if (transactionDate == null) {
            transactionDate = getDateTimeService().getCurrentJavaDateTime();
        }
        Short oldStatusId = this.getState().getValue();
        if (getUserContext() == null) {
            throw new IllegalStateException("userContext is not set for account.");
        }

        try {
            logger.debug("In the change status method of AccountBO:: new StatusId= " + newStatusId);

            activationDateHelper(newStatusId);
            LegacyMasterDao legacyMasterDao = getlegacyMasterDao();
            AccountStateEntity accountStateEntity = legacyMasterDao.getPersistentObject(AccountStateEntity.class,
                    newStatusId);
            AccountStateFlagEntity accountStateFlagEntity = null;
            if (flagId != null) {
                accountStateFlagEntity = legacyMasterDao.getPersistentObject(AccountStateFlagEntity.class, flagId);
            }

            AccountStatusChangeHistoryEntity historyEntity = new AccountStatusChangeHistoryEntity(
                    this.getAccountState(), accountStateEntity, loggedInUser, this);
            AccountNotesEntity accountNotesEntity = new AccountNotesEntity(transactionDate, comment, loggedInUser,
                    this);
            this.addAccountStatusChangeHistory(historyEntity);
            this.setAccountState(accountStateEntity);
            this.addAccountNotes(accountNotesEntity);
            if (accountStateFlagEntity != null) {
                setFlag(accountStateFlagEntity);
            }
            if (newStatusId.equals(AccountState.LOAN_CANCELLED.getValue())
                    || newStatusId.equals(AccountState.LOAN_CLOSED_OBLIGATIONS_MET.getValue())
                    || newStatusId.equals(AccountState.LOAN_CLOSED_WRITTEN_OFF.getValue())
                    || newStatusId.equals(AccountState.SAVINGS_CANCELLED.getValue())
                    || newStatusId.equals(AccountState.CUSTOMER_ACCOUNT_INACTIVE.getValue())) {
                this.setClosedDate(transactionDate);
            }
            if (newStatusId.equals(AccountState.LOAN_CLOSED_WRITTEN_OFF.getValue())) {
                writeOff(transactionDate);
            }

            if (newStatusId.equals(AccountState.LOAN_CLOSED_RESCHEDULED.getValue())) {
                reschedule(transactionDate);
            }

            if (newStatusId.equals(AccountState.SAVINGS_INACTIVE.getValue())) {
                ((SavingsBO) this).removeRecommendedAmountOnFutureInstallments();
            }

            if (oldStatusId.equals(AccountState.SAVINGS_INACTIVE.getValue())
                    && newStatusId.equals(AccountState.SAVINGS_ACTIVE.getValue())) {
                ((SavingsBO) this).resetRecommendedAmountOnFutureInstallments();
            }

            logger.debug("Coming out successfully from the change status method of AccountBO");
        } catch (PersistenceException e) {
            throw new AccountException(e);
        }
    }

    /**
     * used by subclasses
     * @param transactionDate
     */
    @SuppressWarnings("unused")
    protected void writeOff(Date transactionDate) throws AccountException {
    }

    /**
     * used by subclasses
     * @param transactionDate
     */
    @SuppressWarnings("unused")
    protected void reschedule(Date transactionDate) throws AccountException {
    }

    protected void updateClientPerformanceOnRescheduleLoan() {
    }

    protected void updateAccountFeesEntity(final Short feeId) {
        AccountFeesEntity accountFees = getAccountFees(feeId);
        if (accountFees != null) {
            accountFees.changeFeesStatus(FeeStatus.INACTIVE, getDateTimeService().getCurrentJavaDateTime());
            accountFees.setLastAppliedDate(null);
        }
    }

    /**
     * Return an {@link AccountFeesEntity} that links this account to the given fee, or null if the fee does not apply
     * to this account.
     *
     * @param feeId
     *            the primary key of the {@link FeeBO} being sought.
     * @return
     */
    public AccountFeesEntity getAccountFees(final Short feeId) {
        for (AccountFeesEntity accountFeesEntity : this.getAccountFees()) {
            if (accountFeesEntity.getFees().getFeeId().equals(feeId)) {
                return accountFeesEntity;
            }
        }
        return null;
    }

    public FeeBO getAccountFeesObject(final Short feeId) {
        AccountFeesEntity accountFees = getAccountFees(feeId);
        if (accountFees != null) {
            return accountFees.getFees();
        }
        return null;
    }

    public Boolean isFeeActive(final Short feeId) {
        AccountFeesEntity accountFees = getAccountFees(feeId);
        return accountFees.getFeeStatus() == null || accountFees.getFeeStatus().equals(FeeStatus.ACTIVE.getValue());
    }

    protected Money removeSign(final Money amount) {
        if (amount != null && amount.isLessThanZero()) {
            return amount.negate();
        }

        return amount;
    }

    public double getLastPmntAmnt() {
        if (null != accountPayments && accountPayments.size() > 0) {
            return findMostRecentPaymentByPaymentDate().getAmount().getAmountDoubleValue();
        }
        return 0;
    }

    public double getLastPmntAmntToBeAdjusted() {
        if (null != getLastPmntToBeAdjusted() && null != accountPayments && accountPayments.size() > 0) {
            return getLastPmntToBeAdjusted().getAmount().getAmountDoubleValue();
        }
        return 0;
    }

    /*
     * called from ui
     */
    public AccountPaymentEntity getLastPmnt() {
        return findMostRecentPaymentByPaymentDate();
    }

    private AccountPaymentEntity findMostRecentPaymentByPaymentDate(
            List<AccountPaymentEntity> recentAccountPayments) {

        AccountPaymentEntity mostRecentPayment = null;

        if (!recentAccountPayments.isEmpty()) {
            mostRecentPayment = recentAccountPayments.get(0);
            for (AccountPaymentEntity accountPaymentEntity : recentAccountPayments) {
                LocalDate paymentDate = new LocalDate(accountPaymentEntity.getPaymentDate());
                if ((paymentDate.isAfter(new LocalDate(mostRecentPayment.getPaymentDate()))
                        && paymentDate.isBefore(new LocalDate().plusDays(1)))
                        || (paymentDate.isEqual(new LocalDate(mostRecentPayment.getPaymentDate()))
                                && accountPaymentEntity.getPaymentId() != null
                                && mostRecentPayment.getPaymentId() != null
                                && accountPaymentEntity.getPaymentId() > mostRecentPayment.getPaymentId())) {
                    mostRecentPayment = accountPaymentEntity;
                }
            }
        }

        return mostRecentPayment;
    }

    public AccountPaymentEntity findMostRecentPaymentByPaymentDate() {
        return findMostRecentPaymentByPaymentDate(new ArrayList<AccountPaymentEntity>(this.accountPayments));
    }

    /**
     * This method skips zero-amount payments, ie. these which were reversed by the  "payment adjustment" process
     */
    public AccountPaymentEntity findMostRecentNonzeroPaymentByPaymentDate() {
        List<AccountPaymentEntity> nonzeroPayments = new ArrayList<AccountPaymentEntity>();
        for (AccountPaymentEntity payment : this.accountPayments) {
            if (payment.getAmount().isNonZero()) {
                nonzeroPayments.add(payment);
            }
        }
        return findMostRecentPaymentByPaymentDate(nonzeroPayments);
    }

    public AccountPaymentEntity getLastPmntToBeAdjusted() {
        AccountPaymentEntity accntPmnt = null;
        for (AccountPaymentEntity accntPayment : accountPayments) {
            if (accntPayment.getAmount().isNonZero()) {
                accntPmnt = accntPayment;
                break;
            }
        }
        return accntPmnt;
    }

    public AccountActionDateEntity getAccountActionDate(final Short installmentId) {
        if (null != accountActionDates && accountActionDates.size() > 0) {
            for (AccountActionDateEntity accntActionDate : accountActionDates) {
                if (accntActionDate.getInstallmentId().equals(installmentId)) {
                    return accntActionDate;
                }
            }
        }
        return null;
    }

    public AccountActionDateEntity getAccountActionDate(final Short installmentId, final Integer customerId) {
        if (null != accountActionDates && accountActionDates.size() > 0) {
            for (AccountActionDateEntity accntActionDate : accountActionDates) {
                if (accntActionDate.getInstallmentId().equals(installmentId)
                        && accntActionDate.getCustomer().getCustomerId() == customerId) {
                    return accntActionDate;
                }
            }
        }
        return null;
    }

    /*
     * Return those unpaid AccountActionDateEntities after the next installment.
     */
    public List<AccountActionDateEntity> getApplicableIdsForFutureInstallments() {
        List<AccountActionDateEntity> futureActionDateList = new ArrayList<AccountActionDateEntity>();
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
        if (nextInstallment != null) {
            for (AccountActionDateEntity accountActionDate : getAccountActionDates()) {
                if (!accountActionDate.isPaid()
                        && accountActionDate.getInstallmentId() > nextInstallment.getInstallmentId()) {
                    futureActionDateList.add(accountActionDate);
                }
            }
        }
        return futureActionDateList;
    }

    /*
     * Return those unpaid AccountActionDateEntities on or after the next installment.
     */
    public List<AccountActionDateEntity> getApplicableIdsForFutureInstallmentsForWriteOffOrReschedule() {
        List<AccountActionDateEntity> futureActionDateList = new ArrayList<AccountActionDateEntity>();
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
        if (nextInstallment != null) {
            for (AccountActionDateEntity accountActionDate : getAccountActionDates()) {
                if (!accountActionDate.isPaid()
                        && accountActionDate.getInstallmentId() >= nextInstallment.getInstallmentId()) {
                    futureActionDateList.add(accountActionDate);
                }
            }
        }
        return futureActionDateList;
    }

    protected List<AccountActionDateEntity> getApplicableIdsForNextInstallmentAndArrears() {
        List<AccountActionDateEntity> dueActionDateList = new ArrayList<AccountActionDateEntity>(
                getApplicableIdsForArrears());
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
        if (nextInstallment != null && !nextInstallment.isPaid()) {
            dueActionDateList.add(nextInstallment);
        }
        return dueActionDateList;
    }

    protected List<AccountActionDateEntity> getApplicableIdsForArrears() {
        List<AccountActionDateEntity> dueActionDateList = new ArrayList<AccountActionDateEntity>();
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
        if (nextInstallment == null || !nextInstallment.isPaid()) {
            dueActionDateList.addAll(getDetailsOfInstallmentsInArrears());
        }
        return dueActionDateList;
    }

    public List<AccountActionDateEntity> getPastInstallments() {
        List<AccountActionDateEntity> pastActionDateList = new ArrayList<AccountActionDateEntity>();
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            if (accountActionDateEntity.compareDate(DateUtils.getCurrentDateWithoutTimeStamp()) < 0) {
                pastActionDateList.add(accountActionDateEntity);
            }
        }
        return pastActionDateList;
    }

    public List<AccountActionDateEntity> getFutureInstallments() {
        List<AccountActionDateEntity> futureActionDateList = new ArrayList<AccountActionDateEntity>();
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            if (accountActionDateEntity.compareDate(DateUtils.getCurrentDateWithoutTimeStamp()) > 0) {
                futureActionDateList.add(accountActionDateEntity);
            }
        }
        return futureActionDateList;
    }

    public List<AccountActionDateEntity> getAllInstallments() {
        List<AccountActionDateEntity> actionDateList = new ArrayList<AccountActionDateEntity>();
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            actionDateList.add(accountActionDateEntity);
        }
        return actionDateList;
    }

    public List<TransactionHistoryDto> getTransactionHistoryView() {
        List<TransactionHistoryDto> trxnHistory = new ArrayList<TransactionHistoryDto>();
        for (AccountPaymentEntity accountPayment : getAccountPayments()) {
            for (AccountTrxnEntity accountTrxn : accountPayment.getAccountTrxns()) {
                for (FinancialTransactionBO financialTrxn : accountTrxn.getFinancialTransactions()) {
                    TransactionHistoryDto transactionHistory = new TransactionHistoryDto();
                    setFinancialEntries(financialTrxn, transactionHistory);
                    setAccountingEntries(accountTrxn, transactionHistory);
                    trxnHistory.add(transactionHistory);
                }
            }
        }

        return trxnHistory;
    }

    public Date getNextMeetingDate() {
        AccountActionDateEntity nextAccountAction = getDetailsOfNextInstallment();
        if (nextAccountAction != null) {
            return nextAccountAction.getActionDate();
        }
        // calculate the next date based on the customer's meeting object
        CustomerMeetingEntity customerMeeting = customer.getCustomerMeeting();
        if (customerMeeting != null) {

            ScheduledEvent scheduledEvent = ScheduledEventFactory
                    .createScheduledEventFrom(customerMeeting.getMeeting());
            DateTime nextMeetingDate = scheduledEvent
                    .nearestMatchingDateBeginningAt(new LocalDate().toDateTimeAtStartOfDay());
            return new java.sql.Date(nextMeetingDate.toDate().getTime());
        }
        return new java.sql.Date(DateUtils.getCurrentDateWithoutTimeStamp().getTime());
    }

    public List<AccountActionDateEntity> getDetailsOfInstallmentsInArrearsOn(LocalDate asOf) {
        List<AccountActionDateEntity> installmentsInArrears = new ArrayList<AccountActionDateEntity>();

        Set<AccountActionDateEntity> accountActionDates = getAccountActionDates();

        if (accountActionDates != null && !accountActionDates.isEmpty()) {
            for (AccountActionDateEntity accountAction : accountActionDates) {
                LocalDate installmentDate = new LocalDate(accountAction.getActionDate());
                if (asOf.isAfter(installmentDate) && !accountAction.isPaid()) {
                    installmentsInArrears.add(accountAction);
                }
            }
        }
        return installmentsInArrears;
    }

    public List<AccountActionDateEntity> getDetailsOfUnpaidInstallmentsOn(LocalDate asOf) {
        List<AccountActionDateEntity> unpaidInstallments = new ArrayList<AccountActionDateEntity>();

        Set<AccountActionDateEntity> accountActionDates = getAccountActionDates();

        if (accountActionDates != null && !accountActionDates.isEmpty()) {
            for (AccountActionDateEntity accountAction : accountActionDates) {
                if (!accountAction.isPaid()) {
                    unpaidInstallments.add(accountAction);
                }
            }
        }
        return unpaidInstallments;
    }

    public List<AccountActionDateEntity> getDetailsOfPaidInstallmentsOn(LocalDate asOf) {
        List<AccountActionDateEntity> paidInstallments = new ArrayList<AccountActionDateEntity>();

        Set<AccountActionDateEntity> accountActionDates = getAccountActionDates();

        if (accountActionDates != null && !accountActionDates.isEmpty()) {
            for (AccountActionDateEntity accountAction : accountActionDates) {
                if (accountAction.isPaid()) {
                    paidInstallments.add(accountAction);
                }
            }
        }
        return paidInstallments;
    }

    public List<AccountActionDateEntity> getDetailsOfInstallmentsInArrears() {
        List<AccountActionDateEntity> installmentsInArrears = new ArrayList<AccountActionDateEntity>();
        Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
        if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
            for (AccountActionDateEntity accountAction : getAccountActionDates()) {
                if (accountAction.getActionDate().compareTo(currentDate) < 0 && !accountAction.isPaid()) {
                    installmentsInArrears.add(accountAction);
                }
            }
        }
        return installmentsInArrears;
    }

    /**
     * Return the earliest-dated AccountActionDateEntity on or after today.
     */
    public AccountActionDateEntity getDetailsOfNextInstallment() {
        AccountActionDateEntity nextAccountAction = null;
        Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
        if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
            for (AccountActionDateEntity accountAction : getAccountActionDates()) {
                if (accountAction.getActionDate().compareTo(currentDate) >= 0) {
                    if (null == nextAccountAction
                            || nextAccountAction.getInstallmentId() > accountAction.getInstallmentId()) {
                        nextAccountAction = accountAction;
                    }
                }
            }
        }
        return nextAccountAction;
    }

    public AccountActionDateEntity getDetailsOfNextInstallmentOn(LocalDate asOf) {
        AccountActionDateEntity nextAccountAction = null;
        Date currentDate = asOf.toDateMidnight().toDate();
        if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
            for (AccountActionDateEntity accountAction : getAccountActionDates()) {
                if (accountAction.getActionDate().compareTo(currentDate) >= 0) {
                    if (null == nextAccountAction
                            || nextAccountAction.getInstallmentId() > accountAction.getInstallmentId()) {
                        nextAccountAction = accountAction;
                    }
                }
            }
        }
        return nextAccountAction;
    }

    public Money getTotalAmountDue() {
        Money totalAmt = getTotalAmountInArrears();
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
        if (nextInstallment != null && !nextInstallment.isPaid()) {
            totalAmt = totalAmt.add(getDueAmount(nextInstallment));
        }
        return totalAmt;
    }

    public Money getTotalPaymentDue() {
        Money totalAmt = getTotalAmountInArrears();
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
        if (nextInstallment != null && !nextInstallment.isPaid()
                && DateUtils.getDateWithoutTimeStamp(nextInstallment.getActionDate().getTime())
                        .equals(DateUtils.getCurrentDateWithoutTimeStamp())) {
            totalAmt = totalAmt.add(getDueAmount(nextInstallment));
        }
        return totalAmt;
    }

    /*
     * By default, an account will use the system defined default currency. Derived classes like LoanBO can override
     * this to get the currency from the associated product (PrdOfferingBO)
     */
    public MifosCurrency getCurrency() {
        return Money.getDefaultCurrency();
    }

    public Money getTotalAmountDueOn(LocalDate paymentDueAsOf) {

        Money amountInArrearsOn = getTotalAmountInArrearsOn(paymentDueAsOf);

        Money amountDue = getUpcomingInstallmentAmountDueOn(paymentDueAsOf);

        return amountInArrearsOn.add(amountDue);
    }

    private Money getUpcomingInstallmentAmountDueOn(LocalDate asOf) {

        Money upcomingAmount = Money.zero(getCurrency());

        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallmentOn(asOf);
        if (nextInstallment != null) {
            upcomingAmount = upcomingAmount.add(getDueAmount(nextInstallment));
        }
        return upcomingAmount;
    }

    private Money getTotalAmountInArrearsOn(LocalDate asOf) {
        List<AccountActionDateEntity> installmentsInArrears = getDetailsOfInstallmentsInArrearsOn(asOf);
        Money totalAmount = Money.zero(getCurrency());
        for (AccountActionDateEntity accountAction : installmentsInArrears) {
            totalAmount = totalAmount.add(getDueAmount(accountAction));
        }
        return totalAmount;
    }

    public Money getTotalAmountInArrears() {
        List<AccountActionDateEntity> installmentsInArrears = getDetailsOfInstallmentsInArrears();
        Money totalAmount = new Money(getCurrency());
        if (installmentsInArrears != null && installmentsInArrears.size() > 0) {
            for (AccountActionDateEntity accountAction : installmentsInArrears) {
                totalAmount = totalAmount.add(getDueAmount(accountAction));
            }
        }
        return totalAmount;
    }

    public List<AccountActionDateEntity> getTotalInstallmentsDue() {
        List<AccountActionDateEntity> dueInstallments = getDetailsOfInstallmentsInArrears();
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();
        if (nextInstallment != null && !nextInstallment.isPaid()
                && DateUtils.getDateWithoutTimeStamp(nextInstallment.getActionDate().getTime())
                        .equals(DateUtils.getCurrentDateWithoutTimeStamp())) {
            dueInstallments.add(nextInstallment);
        }
        return dueInstallments;
    }

    private boolean isTrxnDateBeforePreviousMeetingDateAllowed(final Date trxnDate, Date meetingDate,
            boolean repaymentIndependentOfMeetingEnabled) {

        if (repaymentIndependentOfMeetingEnabled) {
            // payment date for loans must be >= disbursement date
            if (this instanceof LoanBO) {
                Date approvalDate = this.getAccountApprovalDate();
                return trxnDate.compareTo(DateUtils.getDateWithoutTimeStamp(approvalDate)) >= 0;
            }
            // must be >= creation date for other accounts
            return trxnDate.compareTo(DateUtils.getDateWithoutTimeStamp(this.getCreatedDate())) >= 0;
        } else if (meetingDate != null) {
            return trxnDate.compareTo(DateUtils.getDateWithoutTimeStamp(meetingDate)) >= 0;
        }
        return false;
    }

    public boolean isTrxnDateValid(final Date trxnDate, Date lastCustomerMeetingDate,
            boolean repaymentIndependentOfMeetingEnabled) {
        if (AccountingRules.isBackDatedTxnAllowed()) {
            return isTrxnDateBeforePreviousMeetingDateAllowed(trxnDate, lastCustomerMeetingDate,
                    repaymentIndependentOfMeetingEnabled);
        }
        return trxnDate.equals(DateUtils.getCurrentDateWithoutTimeStamp());
    }

    public List<AccountNotesEntity> getRecentAccountNotes() {
        List<AccountNotesEntity> notes = new ArrayList<AccountNotesEntity>();
        int count = 0;
        for (AccountNotesEntity accountNotesEntity : getAccountNotes()) {
            if (count > 2) {
                break;
            }
            notes.add(accountNotesEntity);
            count++;
        }
        return notes;
    }

    public void update() throws AccountException {
        setUpdateDetails();
        try {
            getlegacyAccountDao().createOrUpdate(this);
        } catch (PersistenceException e) {
            throw new AccountException(e);
        }
    }

    public AccountState getState() {
        return AccountState.fromShort(getAccountState().getId());
    }

    protected void updateAccountActivity(final Money principal, final Money interest, final Money fee,
            final Money penalty, final Short personnelId, final String description) throws AccountException {
    }

    public void waiveAmountOverDue(final WaiveEnum waiveType) throws AccountException {
    }

    public Boolean isCurrentDateGreaterThanFirstInstallment() {
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            if (DateUtils.getCurrentDateWithoutTimeStamp().compareTo(
                    DateUtils.getDateWithoutTimeStamp(accountActionDateEntity.getActionDate().getTime())) >= 0) {
                return true;
            }
        }
        return false;
    }

    public void applyCharge(final Short feeId, final Double charge) throws AccountException, PersistenceException {
    }

    public AccountTypes getType() {
        throw new RuntimeException("should be implemented in subclass");
    }

    public boolean isOpen() {
        return true;
    }

    public boolean isUpcomingInstallmentUnpaid() {
        AccountActionDateEntity accountActionDateEntity = getDetailsOfUpcomigInstallment();
        if (accountActionDateEntity != null) {
            if (!accountActionDateEntity.isPaid()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the next {@link AccountActionDateEntity} occuring after today, if any, otherwise return null.
     * <p>
     * If more than one installment occurs on the next closest installment date, the method returns the entity with the
     * lowest installment id.
     */
    public AccountActionDateEntity getDetailsOfUpcomigInstallment() {
        AccountActionDateEntity nextAccountAction = null;
        Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
        if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
            for (AccountActionDateEntity accountAction : getAccountActionDates()) {
                if (accountAction.getActionDate().compareTo(currentDate) > 0) {
                    if (null == nextAccountAction) {
                        nextAccountAction = accountAction;
                    } else if (nextAccountAction.getInstallmentId() > accountAction.getInstallmentId()) {
                        nextAccountAction = accountAction;
                    }
                }
            }
        }
        return nextAccountAction;
    }

    /*
     * Delegate to an injected service.
     */
    public final void buildFinancialEntries(final Set<AccountTrxnEntity> accountTrxns) throws AccountException {
        getFinancialBusinessService().buildFinancialEntries(accountTrxns);
    }

    public final String generateId(final String officeGlobalNum) throws AccountException {

        if (StringUtils.isBlank(officeGlobalNum)) {
            throw new BusinessRuleException(AccountExceptionConstants.IDGenerationException);
        }

        StringBuilder systemId = new StringBuilder();
        systemId.append(officeGlobalNum);
        try {
            systemId.append(StringUtils.leftPad(getAccountId().toString(), 11, '0'));
        } catch (Exception se) {
            throw new AccountException(AccountExceptionConstants.IDGenerationException, se);
        }
        return systemId.toString();
    }

    protected final List<AccountActionDateEntity> getDueInstallments() {
        List<AccountActionDateEntity> dueInstallmentList = new ArrayList<AccountActionDateEntity>();
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            if (!accountActionDateEntity.isPaid()) {
                if (accountActionDateEntity.compareDate(DateUtils.getCurrentDateWithoutTimeStamp()) > 0) {
                    dueInstallmentList.add(accountActionDateEntity);
                }
            }
        }
        return dueInstallmentList;
    }

    /**
     * Get all unpaid {@link AccountActionDateEntity}s due today or in the future for this account.
     */
    protected final List<AccountActionDateEntity> getTotalDueInstallments() {
        List<AccountActionDateEntity> dueInstallmentList = new ArrayList<AccountActionDateEntity>();
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            if (!accountActionDateEntity.isPaid()) {
                if (accountActionDateEntity.compareDate(DateUtils.getCurrentDateWithoutTimeStamp()) >= 0) {
                    dueInstallmentList.add(accountActionDateEntity);
                }
            }
        }
        return dueInstallmentList;
    }

    protected final Boolean isFeeAlreadyApplied(final FeeBO fee) {
        return getAccountFees(fee.getFeeId()) != null;
    }

    /**
     * If the given {@FeeBO} has not yet been applied to this account, build and return a new
     * {@link AccountFeesEntity} linking this account to the fee; otherwise return the link object with the fee amount
     * replaced with the charge.
     *
     * @param fee
     *            the fee to apply or update
     * @param charge
     *            the amount to charge for the given fee
     * @return return the new or updated {@link AccountFeesEntity} linking this account to the fee
     */
    protected final AccountFeesEntity getAccountFee(final FeeBO fee, final Double charge) {
        AccountFeesEntity accountFee = null;
        if (fee.isPeriodic() && isFeeAlreadyApplied(fee)) {
            accountFee = getAccountFees(fee.getFeeId());
            accountFee.setFeeAmount(charge);
            accountFee.setFeeStatus(FeeStatus.ACTIVE);
            accountFee.setStatusChangeDate(getDateTimeService().getCurrentJavaDateTime());
        } else {
            accountFee = new AccountFeesEntity(this, fee, charge, FeeStatus.ACTIVE.getValue(), null, null);
        }
        return accountFee;
    }

    @Deprecated
    public final List<InstallmentDate> getInstallmentDates(final MeetingBO meeting, final Short noOfInstallments,
            final Short installmentToSkip) {
        return getInstallmentDates(meeting, noOfInstallments, installmentToSkip, false);
    }

    /**
     * @deprecated - logic for generating installments is pulled out of loan/account.
     */
    @Deprecated
    public final List<InstallmentDate> getInstallmentDates(final MeetingBO meeting, final Short noOfInstallments,
            final Short installmentToSkip, final boolean isRepaymentIndepOfMeetingEnabled) {

        return getInstallmentDates(meeting, noOfInstallments, installmentToSkip, false, true);
    }

    /**
     * @deprecated - used to create installment dates based on 'loan meeting' and working das, holidays, moratoria etc
     *
     * better to pull capability of creating 'installments' out of loan into something more reuseable and isolated
     */
    @Deprecated
    public final List<InstallmentDate> getInstallmentDates(final MeetingBO meeting, final Short noOfInstallments,
            final Short installmentToSkip, final boolean isRepaymentIndepOfMeetingEnabled,
            final boolean adjustForHolidays) {

        logger.debug("Generating intallment dates");

        List<InstallmentDate> dueInstallmentDates = new ArrayList<InstallmentDate>();
        if (noOfInstallments > 0) {
            List<Days> workingDays = new FiscalCalendarRules().getWorkingDaysAsJodaTimeDays();
            List<Holiday> holidays = new ArrayList<Holiday>();

            DateTime startFromMeetingDate = new DateTime(meeting.getMeetingStartDate());

            if (adjustForHolidays) {
                HolidayDao holidayDao = ApplicationContextProvider.getBean(HolidayDao.class);
                holidays = holidayDao.findAllHolidaysFromDateAndNext(getOffice().getOfficeId(),
                        startFromMeetingDate.toLocalDate().toString());
            }

            final int occurrences = noOfInstallments + installmentToSkip;

            ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting);
            ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
                    workingDays, holidays);

            List<Date> dueDates = new ArrayList<Date>();
            // FIXME - keithw - this whole area of installment creation should be pulled out of domain
            DateTime startFromDayAfterAssignedMeetingDateRatherThanSkippingInstallments = startFromMeetingDate;
            if (this.isLoanAccount()) {
                // ensure loans that are created or disbursed on a meeting date start on next valid meeting date and not todays meeting
                // ensure loans that are created or disbursed before a meeting date start on next valid meeting date
                startFromDayAfterAssignedMeetingDateRatherThanSkippingInstallments = startFromMeetingDate
                        .plusDays(1);
            }
            List<DateTime> installmentDates = dateGeneration.generateScheduledDates(occurrences,
                    startFromDayAfterAssignedMeetingDateRatherThanSkippingInstallments, scheduledEvent, false);
            for (DateTime installmentDate : installmentDates) {
                dueDates.add(installmentDate.toDate());
            }

            dueInstallmentDates = createInstallmentDates(installmentToSkip, dueDates);
        }
        return dueInstallmentDates;
    }

    /**
     * @deprecated - remove when loan schedules or 'installment' creation responsibility is moved out of account/loan
     */
    @Deprecated
    protected List<InstallmentDate> createInstallmentDates(final Short installmentToSkip,
            final List<Date> dueDates) {
        List<InstallmentDate> installmentDates = new ArrayList<InstallmentDate>();
        int installmentId = 1;
        for (Date date : dueDates) {
            installmentDates.add(new InstallmentDate((short) installmentId++, date));
        }
        removeInstallmentsNeedNotPay(installmentToSkip, installmentDates);
        return installmentDates;
    }

    /**
     *
     * @param installmentDates
     *            dates adjusted for holidays
     * @param nonAdjustedInstallmentDates
     *            dates not adjusted for holidays
     */
    protected final List<FeeInstallment> getFeeInstallments(final List<InstallmentDate> installmentDates,
            final List<InstallmentDate> nonAdjustedInstallmentDates) throws AccountException {
        List<FeeInstallment> feeInstallmentList = new ArrayList<FeeInstallment>();
        for (AccountFeesEntity accountFeesEntity : getAccountFees()) {
            if (accountFeesEntity.isActive()) {
                Short accountFeeType = accountFeesEntity.getFees().getFeeFrequency().getFeeFrequencyType().getId();
                if (accountFeeType.equals(FeeFrequencyType.ONETIME.getValue())) {
                    feeInstallmentList.add(handleOneTime(accountFeesEntity, installmentDates));
                } else if (accountFeeType.equals(FeeFrequencyType.PERIODIC.getValue())) {
                    feeInstallmentList.addAll(
                            handlePeriodic(accountFeesEntity, installmentDates, nonAdjustedInstallmentDates));
                }
            }
        }
        return feeInstallmentList;
    }

    public final List<Date> getFeeDates(final MeetingBO feeMeetingFrequency,
            final List<InstallmentDate> installmentDates) {
        return getFeeDates(feeMeetingFrequency, installmentDates, true);
    }

    public final List<Date> getFeeDates(final MeetingBO feeMeetingFrequency,
            final List<InstallmentDate> installmentDates, final boolean adjustForHolidays) {

        MeetingBO customerMeeting = getCustomer().getCustomerMeetingValue();

        List<Days> workingDays = new FiscalCalendarRules().getWorkingDaysAsJodaTimeDays();
        HolidayDao holidayDao = ApplicationContextProvider.getBean(HolidayDao.class);
        List<Holiday> holidays;
        if (adjustForHolidays) {
            holidays = holidayDao.findAllHolidaysThisYearAndNext(getOffice().getOfficeId());
        } else {
            holidays = new ArrayList<Holiday>();
        }

        DateTime startFromMeetingDate = new DateTime(installmentDates.get(0).getInstallmentDueDate());
        DateTime repaymentEndDatetime = new DateTime(
                installmentDates.get(installmentDates.size() - 1).getInstallmentDueDate());
        ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(customerMeeting,
                feeMeetingFrequency);
        ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
                workingDays, holidays);

        List<DateTime> feeScheduleDates = dateGeneration.generateScheduledDatesThrough(startFromMeetingDate,
                repaymentEndDatetime, scheduledEvent, true);

        List<Date> feeSchedulesAsJavaDates = new ArrayList<Date>();
        for (DateTime feeSchedule : feeScheduleDates) {
            feeSchedulesAsJavaDates.add(feeSchedule.toDate());
        }

        return feeSchedulesAsJavaDates;
    }

    protected final FeeInstallment buildFeeInstallment(final Short installmentId, final Money accountFeeAmount,
            final AccountFeesEntity accountFee) {
        FeeInstallment feeInstallment = new FeeInstallment();
        feeInstallment.setInstallmentId(installmentId);
        feeInstallment.setAccountFee(accountFeeAmount);
        feeInstallment.setAccountFeesEntity(accountFee);
        accountFee.setAccountFeeAmount(accountFeeAmount);
        return feeInstallment;
    }

    protected final Short getMatchingInstallmentId(final List<InstallmentDate> installmentDates,
            final Date feeDate) {
        for (InstallmentDate installmentDate : installmentDates) {
            if (DateUtils.getDateWithoutTimeStamp(installmentDate.getInstallmentDueDate().getTime())
                    .compareTo(DateUtils.getDateWithoutTimeStamp(feeDate.getTime())) >= 0) {
                return installmentDate.getInstallmentId();
            }
        }
        return null;
    }

    protected final List<FeeInstallment> mergeFeeInstallments(final List<FeeInstallment> feeInstallmentList) {
        List<FeeInstallment> newFeeInstallmentList = new ArrayList<FeeInstallment>();
        for (Iterator<FeeInstallment> iterator = feeInstallmentList.iterator(); iterator.hasNext();) {
            FeeInstallment feeInstallment = iterator.next();
            iterator.remove();
            FeeInstallment feeInstTemp = null;
            for (FeeInstallment feeInst : newFeeInstallmentList) {
                if (feeInst.getInstallmentId().equals(feeInstallment.getInstallmentId())
                        && feeInst.getAccountFeesEntity().equals(feeInstallment.getAccountFeesEntity())) {
                    feeInstTemp = feeInst;
                    break;
                }
            }
            if (feeInstTemp != null) {
                newFeeInstallmentList.remove(feeInstTemp);
                feeInstTemp.setAccountFee(feeInstTemp.getAccountFee().add(feeInstallment.getAccountFee()));
                newFeeInstallmentList.add(feeInstTemp);
            } else {
                newFeeInstallmentList.add(feeInstallment);
            }
        }
        return newFeeInstallmentList;
    }

    protected boolean isCurrentDateEquallToInstallmentDate() {
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            if (!accountActionDateEntity.isPaid()) {
                if (accountActionDateEntity.compareDate(DateUtils.getCurrentDateWithoutTimeStamp()) == 0) {
                    return true;
                }
            }
        }
        return false;
    }

    protected List<AccountFeesEntity> getPeriodicFeeList() {
        List<AccountFeesEntity> periodicFeeList = new ArrayList<AccountFeesEntity>();
        for (AccountFeesEntity accountFee : getAccountFees()) {
            if (accountFee.getFees().isPeriodic() && accountFee.isActive()) {
                // Why? Doesn't appear to do anything with the retrieved FeeBO
                // getFeePersistence().getFee(accountFee.getFees().getFeeId());
                periodicFeeList.add(accountFee);
            }
        }
        return periodicFeeList;
    }

    protected void deleteFutureInstallments() throws AccountException {
        List<AccountActionDateEntity> futureInstllments = getApplicableIdsForFutureInstallments();
        for (AccountActionDateEntity accountActionDateEntity : futureInstllments) {
            accountActionDates.remove(accountActionDateEntity);
            try {
                getlegacyAccountDao().delete(accountActionDateEntity);
            } catch (PersistenceException e) {
                throw new AccountException(e);
            }
        }
    }

    /**
     * This can be very inefficient if an account has many action dates. Note that an account having too many action
     * dates may also be a sign of bad data--at least, this was the case with some GK data.
     */
    public Short getLastInstallmentId() {
        Short lastInstallmentId = null;
        for (AccountActionDateEntity date : this.getAccountActionDates()) {

            if (lastInstallmentId == null) {
                lastInstallmentId = date.getInstallmentId();
            } else {
                if (lastInstallmentId < date.getInstallmentId()) {
                    lastInstallmentId = date.getInstallmentId();
                }
            }
        }
        return lastInstallmentId;

    }

    protected List<AccountTrxnEntity> getAccountTrxnsOrderByTrxnCreationDate() {
        List<AccountTrxnEntity> accountTrxnList = new ArrayList<AccountTrxnEntity>();
        for (AccountPaymentEntity payment : getAccountPayments()) {
            accountTrxnList.addAll(payment.getAccountTrxns());
        }

        Collections.sort(accountTrxnList, new Comparator<AccountTrxnEntity>() {
            @Override
            public int compare(final AccountTrxnEntity trx1, final AccountTrxnEntity trx2) {
                if (trx1.getTrxnCreatedDate().equals(trx2.getTrxnCreatedDate())) {
                    return trx1.getAccountTrxnId().compareTo(trx2.getAccountTrxnId());
                }
                return trx1.getTrxnCreatedDate().compareTo(trx2.getTrxnCreatedDate());
            }
        });
        return accountTrxnList;
    }

    protected List<AccountTrxnEntity> getAccountTrxnsOrderByTrxnDate() {
        List<AccountTrxnEntity> accountTrxnList = new ArrayList<AccountTrxnEntity>();
        for (AccountPaymentEntity payment : getAccountPayments()) {
            accountTrxnList.addAll(payment.getAccountTrxns());
        }

        Collections.sort(accountTrxnList, new Comparator<AccountTrxnEntity>() {
            @Override
            public int compare(final AccountTrxnEntity trx1, final AccountTrxnEntity trx2) {
                if (trx1.getAccountTrxnId() != null && trx2.getAccountTrxnId() != null
                        && trx1.getActionDate().equals(trx2.getActionDate())) {
                    return trx1.getAccountTrxnId().compareTo(trx2.getAccountTrxnId());
                }
                return trx1.getActionDate().compareTo(trx2.getActionDate());
            }
        });
        return accountTrxnList;
    }

    protected void resetAccountActionDates() {
        this.accountActionDates.clear();
    }

    protected void updateInstallmentAfterAdjustment(final List<AccountTrxnEntity> reversedTrxns,
            PersonnelBO loggedInUser) throws AccountException {
    }

    protected Money getDueAmount(final AccountActionDateEntity installment) {
        return null;
    }

    protected void regenerateFutureInstallments(final AccountActionDateEntity nextInstallment,
            final List<Days> workingDays, final List<Holiday> holidays) throws AccountException {
    }

    protected List<FeeInstallment> handlePeriodic(final AccountFeesEntity accountFees,
            final List<InstallmentDate> installmentDates, final List<InstallmentDate> nonAdjustedInstallmentDates)
            throws AccountException {
        return null;
    }

    /**
     * {@link AccountPaymentEntity} and not {@link PaymentData} dto
     */
    protected AccountPaymentEntity makePayment(final PaymentData accountPaymentData) throws AccountException {
        return null;
    }

    protected void updateTotalFeeAmount(final Money totalFeeAmount) {
    }

    protected void updateTotalPenaltyAmount(final Money totalPenaltyAmount) {
    }

    protected Money updateAccountActionDateEntity(final List<Short> intallmentIdList, final Short feeId) {
        return new Money(getCurrency());
    }

    protected boolean isAdjustPossibleOnLastTrxn() {
        return false;
    }

    public void removeFeesAssociatedWithUpcomingAndAllKnownFutureInstallments(final Short feeId,
            final Short personnelId) throws AccountException {
    }

    protected void activationDateHelper(final Short newStatusId) throws AccountException {
    }

    /**
     * Return list of unpaid AccountActionDateEntities occurring on or after today
     */
    protected List<Short> getApplicableInstallmentIdsForRemoveFees() {
        List<Short> installmentIdList = new ArrayList<Short>();
        for (AccountActionDateEntity accountActionDateEntity : getApplicableIdsForFutureInstallments()) {
            installmentIdList.add(accountActionDateEntity.getInstallmentId());
        }
        AccountActionDateEntity accountActionDateEntity = getDetailsOfNextInstallment();
        if (accountActionDateEntity != null) {
            installmentIdList.add(accountActionDateEntity.getInstallmentId());
        }

        return installmentIdList;
    }

    private void setFinancialEntries(final FinancialTransactionBO financialTrxn,
            final TransactionHistoryDto transactionHistory) {
        String debit = "-";
        String credit = "-";
        String notes = "-";
        if (financialTrxn.isDebitEntry()) {
            debit = String.valueOf(removeSign(financialTrxn.getPostedAmount()));
        } else if (financialTrxn.isCreditEntry()) {
            credit = String.valueOf(removeSign(financialTrxn.getPostedAmount()));
        }
        Short entityId = financialTrxn.getAccountTrxn().getAccountActionEntity().getId();
        if (financialTrxn.getNotes() != null && !financialTrxn.getNotes().equals("")
                && !entityId.equals(AccountActionTypes.CUSTOMER_ACCOUNT_REPAYMENT.getValue())
                && !entityId.equals(AccountActionTypes.LOAN_REPAYMENT.getValue())) {
            notes = financialTrxn.getNotes();
        }

        transactionHistory.setFinancialEnteries(financialTrxn.getTrxnId(), financialTrxn.getActionDate(),
                financialTrxn.getFinancialAction().getName(), financialTrxn.getGlcode().getGlcode(), debit, credit,
                financialTrxn.getPostedDate(), notes);

    }

    private void setAccountingEntries(final AccountTrxnEntity accountTrxn,
            final TransactionHistoryDto transactionHistory) {

        transactionHistory.setAccountingEnteries(accountTrxn.getAccountPayment().getPaymentId(),
                String.valueOf(removeSign(accountTrxn.getAmount())), accountTrxn.getCustomer().getDisplayName(),
                accountTrxn.getPersonnel().getDisplayName());
    }

    private AccountCustomFieldEntity getAccountCustomField(final Short fieldId) {
        if (null != this.accountCustomFields && this.accountCustomFields.size() > 0) {
            for (AccountCustomFieldEntity obj : this.accountCustomFields) {
                if (obj.getFieldId().equals(fieldId)) {
                    return obj;
                }
            }
        }
        return null;
    }

    protected final FeeInstallment handleOneTime(final AccountFeesEntity accountFee,
            final List<InstallmentDate> installmentDates) {
        Money accountFeeAmount = accountFee.getAccountFeeAmount();
        Date feeDate = installmentDates.get(0).getInstallmentDueDate();
        logger.debug("Handling OneTime fee" + feeDate);
        Short installmentId = getMatchingInstallmentId(installmentDates, feeDate);
        logger.debug("OneTime fee applicable installment id " + installmentId);
        return buildFeeInstallment(installmentId, accountFeeAmount, accountFee);
    }

    private void removeInstallmentsNeedNotPay(final Short installmentSkipToStartRepayment,
            final List<InstallmentDate> installmentDates) {
        int removeCounter = 0;
        for (int i = 0; i < installmentSkipToStartRepayment; i++) {
            installmentDates.remove(removeCounter);
        }
        // re-adjust the installment ids
        if (installmentSkipToStartRepayment > 0) {
            int count = installmentDates.size();
            for (int i = 0; i < count; i++) {
                InstallmentDate instDate = installmentDates.get(i);
                instDate.setInstallmentId(new Short(Integer.toString(i + 1)));
            }
        }
    }

    private void validate(final UserContext userContext, final CustomerBO customer, final AccountTypes accountType,
            final AccountState accountState) throws AccountException {
        if (userContext == null || customer == null || accountType == null || accountState == null) {
            throw new AccountException(AccountExceptionConstants.CREATEEXCEPTION);
        }
    }

    private void setFlag(final AccountStateFlagEntity accountStateFlagEntity) {
        Iterator iter = this.getAccountFlags().iterator();
        while (iter.hasNext()) {
            AccountFlagMapping currentFlag = (AccountFlagMapping) iter.next();
            if (!currentFlag.getFlag().isFlagRetained()) {
                iter.remove();
            }
        }
        this.addAccountFlag(accountStateFlagEntity);
    }

    protected void updateCustomFields(final List<CustomFieldDto> customFields) throws InvalidDateException {
        if (customFields == null) {
            return;
        }
        for (CustomFieldDto fieldView : customFields) {
            if (fieldView.getFieldType().equals(CustomFieldType.DATE.getValue())
                    && org.apache.commons.lang.StringUtils.isNotBlank(fieldView.getFieldValue())) {
                SimpleDateFormat format = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT,
                        getUserContext().getPreferredLocale());
                String userfmt = DateUtils.convertToCurrentDateFormat(format.toPattern());
                fieldView.setFieldValue(DateUtils.convertUserToDbFmt(fieldView.getFieldValue(), userfmt));
            }
            if (getAccountCustomFields().size() > 0) {
                for (AccountCustomFieldEntity fieldEntity : getAccountCustomFields()) {
                    if (fieldView.getFieldId().equals(fieldEntity.getFieldId())) {
                        fieldEntity.setFieldValue(fieldView.getFieldValue());
                    }
                }
            } else {
                for (CustomFieldDto view : customFields) {
                    this.getAccountCustomFields()
                            .add(new AccountCustomFieldEntity(this, view.getFieldId(), view.getFieldValue()));
                }
            }
        }
    }

    public Date getAccountApprovalDate() {
        Date approvalDate = null;
        List<AccountStatusChangeHistoryEntity> statusChangeHistory = this.getAccountStatusChangeHistory();

        for (AccountStatusChangeHistoryEntity status : statusChangeHistory) {

            if (status.getNewStatus().isInState(AccountState.LOAN_APPROVED)) {
                approvalDate = status.getCreatedDate();
                break;
            }
        }
        return approvalDate;
    }

    public Integer getOffsettingAllowable() {
        return offsettingAllowable;
    }

    public void setOffsettingAllowable(final Integer offsettingAllowable) {
        this.offsettingAllowable = offsettingAllowable;
    }

    public boolean isInState(final AccountState state) {
        return accountState.isInState(state);
    }

    public boolean isLoanAccount() {
        return isOfType(LOAN_ACCOUNT);
    }

    public boolean isSavingsAccount() {
        return isOfType(SAVINGS_ACCOUNT);
    }

    public boolean isOfType(final AccountTypes accountType) {
        return accountType.equals(getType());
    }

    @Override
    public String toString() {
        return "{" + globalAccountNum + "}";
    }

    public boolean isActiveLoanAccount() {
        return AccountState.fromShort(accountState.getId()).isActiveLoanAccountState();
    }

    public void setExternalId(final String externalId) {
        this.externalId = externalId;
    }

    public String getExternalId() {
        return externalId;
    }

    /**
     * Return true if a given payment amount valid for this account.
     *
     * @param amount
     *            the payment amount to validate.
     *
     */

    public boolean paymentAmountIsValid(final Money amount,
            Set<AccountPaymentParametersDto.PaymentOptions> options) {
        return true;
    }

    protected void updateSchedule(final Short nextInstallmentId, final List<DateTime> meetingDates) {
        short installmentId = nextInstallmentId;
        for (int count = 0; count < meetingDates.size(); count++) {
            AccountActionDateEntity accountActionDate = getAccountActionDate(installmentId);
            if (accountActionDate != null) {
                DateTime meetingDate = meetingDates.get(count);
                accountActionDate.setActionDate(new java.sql.Date(meetingDate.toDate().getTime()));
            }
            installmentId++;
        }
    }

    // To be used strictly from test code
    @Deprecated
    public void setAccountId(Integer accountId) {
        this.accountId = accountId;
    }

    public void setAccountCustomFields(Set<AccountCustomFieldEntity> accountCustomFields) {
        this.accountCustomFields = accountCustomFields;
    }

    public Short getOfficeId() {
        return office.getOfficeId();
    }

    public boolean isCustomerAccount() {
        return AccountTypes.CUSTOMER_ACCOUNT.equals(this.getType());
    }

    private void changeActionDateOfFirstInstallment(Calendar date,
            Set<AccountActionDateEntity> accountActionDates) {
        if (accountActionDates.isEmpty()) {
            return;
        }
        java.sql.Date actionDate = new java.sql.Date(date.getTimeInMillis());
        accountActionDates.toArray(new AccountActionDateEntity[accountActionDates.size()])[0]
                .setActionDate(actionDate);
    }

    public void changeFirstInstallmentDateBy(final int numberOfDays) {
        Calendar currentDateCalendar = new GregorianCalendar();
        int year = currentDateCalendar.get(Calendar.YEAR);
        int month = currentDateCalendar.get(Calendar.MONTH);
        int day = currentDateCalendar.get(Calendar.DAY_OF_MONTH);
        currentDateCalendar = new GregorianCalendar(year, month, day + numberOfDays);
        changeActionDateOfFirstInstallment(currentDateCalendar, getAccountActionDates());
    }
}