org.mifos.accounts.savings.business.SavingsBO.java Source code

Java tutorial

Introduction

Here is the source code for org.mifos.accounts.savings.business.SavingsBO.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.savings.business;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifos.accounts.business.AccountActionDateEntity;
import org.mifos.accounts.business.AccountActionEntity;
import org.mifos.accounts.business.AccountBO;
import org.mifos.accounts.business.AccountCustomFieldEntity;
import org.mifos.accounts.business.AccountNotesEntity;
import org.mifos.accounts.business.AccountPaymentEntity;
import org.mifos.accounts.business.AccountStateEntity;
import org.mifos.accounts.business.AccountStatusChangeHistoryEntity;
import org.mifos.accounts.business.AccountTrxnEntity;
import org.mifos.accounts.exceptions.AccountException;
import org.mifos.accounts.financial.business.service.activity.BaseFinancialActivity;
import org.mifos.accounts.financial.business.service.activity.SavingsInterestPostingFinancialActivity;
import org.mifos.accounts.financial.exceptions.FinancialException;
import org.mifos.accounts.productdefinition.business.InterestCalcTypeEntity;
import org.mifos.accounts.productdefinition.business.RecommendedAmntUnitEntity;
import org.mifos.accounts.productdefinition.business.SavingsOfferingBO;
import org.mifos.accounts.productdefinition.business.SavingsTypeEntity;
import org.mifos.accounts.productdefinition.util.helpers.InterestCalcType;
import org.mifos.accounts.productdefinition.util.helpers.RecommendedAmountUnit;
import org.mifos.accounts.savings.interest.CalendarPeriod;
import org.mifos.accounts.savings.interest.InterestPostingPeriodResult;
import org.mifos.accounts.savings.interest.SavingsProductHistoricalInterestDetail;
import org.mifos.accounts.savings.interest.schedule.InterestScheduledEvent;
import org.mifos.accounts.savings.interest.schedule.SavingsInterestScheduledEventFactory;
import org.mifos.accounts.savings.persistence.SavingsDao;
import org.mifos.accounts.savings.persistence.SavingsPersistence;
import org.mifos.accounts.savings.util.helpers.SavingsHelper;
import org.mifos.accounts.util.helpers.AccountActionTypes;
import org.mifos.accounts.util.helpers.AccountExceptionConstants;
import org.mifos.accounts.util.helpers.AccountPaymentData;
import org.mifos.accounts.util.helpers.AccountState;
import org.mifos.accounts.util.helpers.AccountStates;
import org.mifos.accounts.util.helpers.AccountTypes;
import org.mifos.accounts.util.helpers.PaymentData;
import org.mifos.accounts.util.helpers.PaymentStatus;
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.PaymentTypeEntity;
import org.mifos.application.meeting.business.MeetingBO;
import org.mifos.application.servicefacade.ApplicationContextProvider;
import org.mifos.calendar.CalendarEvent;
import org.mifos.clientportfolio.newloan.domain.CreationDetail;
import org.mifos.config.AccountingRules;
import org.mifos.config.ProcessFlowRules;
import org.mifos.core.MifosRuntimeException;
import org.mifos.customers.api.CustomerLevel;
import org.mifos.customers.business.CustomerBO;
import org.mifos.customers.client.business.ClientBO;
import org.mifos.customers.exceptions.CustomerException;
import org.mifos.customers.office.business.OfficeBO;
import org.mifos.customers.persistence.CustomerPersistence;
import org.mifos.customers.personnel.business.PersonnelBO;
import org.mifos.customers.personnel.persistence.LegacyPersonnelDao;
import org.mifos.customers.util.helpers.ChildrenStateType;
import org.mifos.customers.util.helpers.CustomerStatus;
import org.mifos.dto.domain.CustomFieldDto;
import org.mifos.dto.domain.CustomerNoteDto;
import org.mifos.dto.domain.SavingsAccountDetailDto;
import org.mifos.dto.domain.SavingsPerformanceHistoryDto;
import org.mifos.dto.screen.SavingsRecentActivityDto;
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;

/**
 * A savings account is ...
 */
public class SavingsBO extends AccountBO {

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

    private Money recommendedAmount;
    private RecommendedAmntUnitEntity recommendedAmntUnit;

    private Money savingsBalance;
    private Date activationDate;
    private Money interestToBePosted;
    private Date lastIntPostDate;
    private Date nextIntPostDate;

    private SavingsOfferingBO savingsOffering;
    private SavingsPerformanceEntity savingsPerformance;

    // the follow three fields cant be null at present on savings_account table so populate them for now but dont use em.
    private SavingsTypeEntity savingsType;
    private InterestCalcTypeEntity interestCalcType;
    private Double interestRate;

    private Set<SavingsActivityEntity> savingsActivityDetails = new LinkedHashSet<SavingsActivityEntity>();

    private final SavingsHelper helper = new SavingsHelper();
    private SavingsTransactionActivityHelper savingsTransactionActivityHelper = new SavingsTransactionActivityHelperImpl();
    private SavingsPaymentStrategy savingsPaymentStrategy = new SavingsPaymentStrategyImpl(
            savingsTransactionActivityHelper);
    private SavingsPersistence savingsPersistence = null;

    @Deprecated
    public SavingsPersistence getSavingsPersistence() {
        if (null == savingsPersistence) {
            savingsPersistence = new SavingsPersistence();
        }
        return savingsPersistence;
    }

    @Deprecated
    public void setSavingsPersistence(final SavingsPersistence savingsPersistence) {
        this.savingsPersistence = savingsPersistence;
    }

    private LegacyPersonnelDao personnelPersistence = null;

    @Deprecated
    public LegacyPersonnelDao getPersonnelPersistence() {
        if (null == personnelPersistence) {
            personnelPersistence = ApplicationContextProvider.getBean(LegacyPersonnelDao.class);
        }
        return personnelPersistence;
    }

    @Deprecated
    public void setPersonnelPersistence(final LegacyPersonnelDao personnelPersistence) {
        this.personnelPersistence = personnelPersistence;
    }

    private CustomerPersistence customerPersistence;

    @Deprecated
    public CustomerPersistence getCustomerPersistence() {
        if (null == customerPersistence) {
            customerPersistence = new CustomerPersistence();
        }
        return customerPersistence;
    }

    @Deprecated
    public void setCustomerPersistence(final CustomerPersistence customerPersistence) {
        this.customerPersistence = customerPersistence;
    }

    /**
     * Responsible for creating savings account in valid initial state.
     */
    public static SavingsBO createOpeningBalanceIndividualSavingsAccount(CustomerBO customer,
            SavingsOfferingBO savingsProduct, Money recommendedOrMandatoryAmount, AccountState savingsAccountState,
            LocalDate createdDate, Integer createdById, SavingsAccountActivationDetail activationDetails,
            PersonnelBO createdBy, Money openingBalance) {

        RecommendedAmountUnit recommendedAmountUnit = RecommendedAmountUnit.COMPLETE_GROUP;
        CreationDetail creationDetail = new CreationDetail(createdDate.toDateMidnight().toDateTime(), createdById);
        SavingsBO savingsAccount = new SavingsBO(savingsAccountState, customer, activationDetails, creationDetail,
                savingsProduct, recommendedAmountUnit, recommendedOrMandatoryAmount, createdBy, openingBalance);

        return savingsAccount;
    }

    /**
     * Responsible for creating savings account in valid initial state.
     */
    public static SavingsBO createIndividalSavingsAccount(CustomerBO customer, SavingsOfferingBO savingsProduct,
            Money recommendedOrMandatoryAmount, AccountState savingsAccountState, LocalDate createdDate,
            Integer createdById, CalendarEvent calendarEvents, PersonnelBO createdBy) {

        LocalDate activationDate = new LocalDate();
        SavingsAccountActivationDetail activationDetails = determineAccountActivationDetails(customer,
                savingsProduct, recommendedOrMandatoryAmount, savingsAccountState, calendarEvents, activationDate);

        Money startingBalance = Money.zero(savingsProduct.getCurrency());
        RecommendedAmountUnit recommendedAmountUnit = RecommendedAmountUnit.COMPLETE_GROUP;
        CreationDetail creationDetail = new CreationDetail(createdDate.toDateMidnight().toDateTime(), createdById);
        SavingsBO savingsAccount = new SavingsBO(savingsAccountState, customer, activationDetails, creationDetail,
                savingsProduct, recommendedAmountUnit, recommendedOrMandatoryAmount, createdBy, startingBalance);

        return savingsAccount;
    }

    public static SavingsBO createJointSavingsAccount(CustomerBO customer, SavingsOfferingBO savingsProduct,
            Money recommendedOrMandatoryAmount, AccountState savingsAccountState, LocalDate createdDate,
            Integer createdById, CalendarEvent calendarEvents, PersonnelBO createdBy,
            List<CustomerBO> activeAndOnHoldClients) {

        SavingsAccountActivationDetail activationDetails = determineAccountActivationDetails(customer,
                savingsProduct, recommendedOrMandatoryAmount, savingsAccountState, calendarEvents,
                activeAndOnHoldClients);

        Money startingBalance = Money.zero(savingsProduct.getCurrency());
        RecommendedAmountUnit recommendedAmountUnit = RecommendedAmountUnit.PER_INDIVIDUAL;
        CreationDetail creationDetail = new CreationDetail(createdDate.toDateMidnight().toDateTime(), createdById);
        SavingsBO savingsAccount = new SavingsBO(savingsAccountState, customer, activationDetails, creationDetail,
                savingsProduct, recommendedAmountUnit, recommendedOrMandatoryAmount, createdBy, startingBalance);

        return savingsAccount;
    }

    /**
     * valid minimal legal constructor
     */
    public SavingsBO(AccountState savingsAccountState, CustomerBO customer,
            SavingsAccountActivationDetail activationDetails, CreationDetail creationDetail,
            SavingsOfferingBO savingsProduct, RecommendedAmountUnit recommendedAmountUnit,
            Money recommendedOrMandatoryAmount, PersonnelBO createdBy, Money startingBalance) {
        super(AccountTypes.SAVINGS_ACCOUNT, savingsAccountState, customer, activationDetails.getScheduledPayments(),
                creationDetail);
        this.savingsOffering = savingsProduct;
        this.recommendedAmntUnit = new RecommendedAmntUnitEntity(recommendedAmountUnit);
        this.recommendedAmount = recommendedOrMandatoryAmount;
        this.savingsBalance = startingBalance;
        this.savingsPerformance = new SavingsPerformanceEntity(this);

        // inherited from savings product for now but should be removed and cleaned up.
        this.interestRate = this.savingsOffering.getInterestRate();
        this.interestCalcType = new InterestCalcTypeEntity(
                InterestCalcType.fromInt(this.savingsOffering.getInterestCalcType().getId()));
        this.savingsType = new SavingsTypeEntity(this.savingsOffering.getSavingsTypeAsEnum());

        if (savingsAccountState.isActiveSavingsAccountState()) {
            this.activationDate = activationDetails.getActivationDate().toDateMidnight().toDate();
            this.nextIntPostDate = activationDetails.getNextInterestPostingDate().toDateMidnight().toDate();
        }

        AccountStateEntity newStatus = new AccountStateEntity(savingsAccountState);
        AccountStatusChangeHistoryEntity statusChange = new AccountStatusChangeHistoryEntity(null, newStatus,
                createdBy, this);
        this.accountStatusChangeHistory.add(statusChange);
    }

    /**
     * default constructor for hibernate usage
     */
    protected SavingsBO() {
        // default constructor for hibernate
    }

    public static SavingsAccountActivationDetail determineAccountActivationDetails(CustomerBO customer,
            SavingsOfferingBO savingsProduct, Money recommendedOrMandatoryAmount, AccountState savingsAccountState,
            CalendarEvent calendarEvents, List<CustomerBO> activeAndOnHoldClients) {

        List<AccountActionDateEntity> scheduledPayments = new ArrayList<AccountActionDateEntity>();
        LocalDate activationDate = new LocalDate();
        LocalDate nextInterestPostingDate = new LocalDate();

        if (savingsAccountState.isActiveSavingsAccountState()) {
            activationDate = new LocalDate();
            ScheduledEvent scheduledEvent = ScheduledEventFactory
                    .createScheduledEventFrom(customer.getCustomerMeetingValue());
            ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
                    calendarEvents.getWorkingDays(), calendarEvents.getHolidays());

            for (CustomerBO client : activeAndOnHoldClients) {
                List<DateTime> depositDates = dateGeneration.generateScheduledDates(10,
                        activationDate.toDateTimeAtStartOfDay(), scheduledEvent, false);

                short installmentNumber = 1;
                for (DateTime date : depositDates) {
                    java.sql.Date depositDueDate = new java.sql.Date(date.toDate().getTime());
                    AccountActionDateEntity scheduledSavingsDeposit = new SavingsScheduleEntity(client,
                            installmentNumber, depositDueDate, PaymentStatus.UNPAID, recommendedOrMandatoryAmount,
                            savingsProduct.getCurrency());
                    scheduledPayments.add(scheduledSavingsDeposit);
                }
            }

            InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory()
                    .createScheduledEventFrom(savingsProduct.getFreqOfPostIntcalc().getMeeting());
            nextInterestPostingDate = interestPostingEvent.nextMatchingDateAfter(new LocalDate(startOfFiscalYear()),
                    activationDate);
        }

        return new SavingsAccountActivationDetail(activationDate, nextInterestPostingDate, scheduledPayments);

    }

    public static SavingsAccountActivationDetail determineAccountActivationDetails(CustomerBO customer,
            SavingsOfferingBO savingsProduct, Money recommendedOrMandatoryAmount, AccountState savingsAccountState,
            CalendarEvent calendarEvents, LocalDate activationDate) {

        List<AccountActionDateEntity> scheduledPayments = new ArrayList<AccountActionDateEntity>();
        LocalDate nextInterestPostingDate = new LocalDate();

        if (savingsAccountState.isActiveSavingsAccountState()) {
            ScheduledEvent scheduledEvent = ScheduledEventFactory
                    .createScheduledEventFrom(customer.getCustomerMeetingValue());
            ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
                    calendarEvents.getWorkingDays(), calendarEvents.getHolidays());

            List<DateTime> depositDates = dateGeneration.generateScheduledDates(10,
                    activationDate.toDateTimeAtStartOfDay(), scheduledEvent, false);

            short installmentNumber = 1;
            for (DateTime date : depositDates) {
                java.sql.Date depositDueDate = new java.sql.Date(date.toDate().getTime());
                AccountActionDateEntity scheduledSavingsDeposit = new SavingsScheduleEntity(customer,
                        installmentNumber, depositDueDate, PaymentStatus.UNPAID, recommendedOrMandatoryAmount,
                        savingsProduct.getCurrency());
                scheduledPayments.add(scheduledSavingsDeposit);
            }

            InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory()
                    .createScheduledEventFrom(savingsProduct.getFreqOfPostIntcalc().getMeeting());
            nextInterestPostingDate = interestPostingEvent.nextMatchingDateAfter(new LocalDate(startOfFiscalYear()),
                    activationDate);
        }

        return new SavingsAccountActivationDetail(activationDate, nextInterestPostingDate, scheduledPayments);
    }

    public static SavingsAccountActivationDetail generateAccountActivationDetails(CustomerBO customer,
            SavingsOfferingBO savingsProduct, Money recommendedOrMandatoryAmount, AccountState savingsAccountState,
            CalendarEvent calendarEvents, LocalDate activationDate) {

        List<AccountActionDateEntity> scheduledPayments = new ArrayList<AccountActionDateEntity>();
        LocalDate nextInterestPostingDate = new LocalDate();

        if (savingsAccountState.isActiveSavingsAccountState()) {
            ScheduledEvent scheduledEvent = ScheduledEventFactory
                    .createScheduledEventFrom(customer.getCustomerMeetingValue());
            ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
                    calendarEvents.getWorkingDays(), calendarEvents.getHolidays());

            List<DateTime> depositDates = dateGeneration.generateScheduledDates(10,
                    activationDate.toDateTimeAtStartOfDay(), scheduledEvent, false);

            short installmentNumber = 0;
            for (DateTime date : depositDates) {
                java.sql.Date depositDueDate = new java.sql.Date(date.toDate().getTime());
                AccountActionDateEntity scheduledSavingsDeposit = new SavingsScheduleEntity(customer,
                        installmentNumber++, depositDueDate, PaymentStatus.UNPAID, recommendedOrMandatoryAmount,
                        savingsProduct.getCurrency());
                scheduledPayments.add(scheduledSavingsDeposit);
            }

            InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory()
                    .createScheduledEventFrom(savingsProduct.getFreqOfPostIntcalc().getMeeting());
            nextInterestPostingDate = interestPostingEvent.nextMatchingDateAfter(new LocalDate(startOfFiscalYear()),
                    activationDate);
        }

        return new SavingsAccountActivationDetail(activationDate, nextInterestPostingDate, scheduledPayments);
    }

    /**
     * @deprecated use minimal legal constructor from builder
     * create a constructor that doesnt take customFields or delegate to goActiveForFristTimeAndGenerateSavingsSchedule which contains persistence.
     */
    @Deprecated
    public SavingsBO(final UserContext userContext, final SavingsOfferingBO savingsOffering,
            final CustomerBO customer, final AccountState accountState, final Money recommendedAmount,
            final List<CustomFieldDto> customFields) throws AccountException {
        super(userContext, customer, AccountTypes.SAVINGS_ACCOUNT, accountState);

        this.savingsOffering = savingsOffering;
        this.savingsPerformance = createSavingsPerformance();
        this.savingsBalance = Money.zero();

        // inherit details from savings product definition
        this.interestRate = this.savingsOffering.getInterestRate();
        this.interestCalcType = new InterestCalcTypeEntity(
                InterestCalcType.fromInt(this.savingsOffering.getInterestCalcType().getId()));
        this.savingsType = new SavingsTypeEntity(this.savingsOffering.getSavingsTypeAsEnum());

        this.recommendedAmntUnit = savingsOffering.getRecommendedAmntUnit();
        this.recommendedAmount = recommendedAmount;

        // FIXME - keithw - create constructor that does not take DTO of customFields
        try {
            addcustomFields(customFields);
        } catch (InvalidDateException e) {
            throw new AccountException(e);
        }

        // generated the deposit action dates only if savings account is being
        // saved in approved state
        if (isActive()) {
            goActiveForFristTimeAndGenerateSavingsSchedule(customer);
        }
    }

    /**
     * use minimal constructor which generates scheduled savings payments correctly and does not
     * do through this method.
     */
    @Deprecated
    private void goActiveForFristTimeAndGenerateSavingsSchedule(final CustomerBO customer) throws AccountException {

        HolidayDao holidayDao = ApplicationContextProvider.getBean(HolidayDao.class);

        CalendarEvent futureCalendarEventsApplicableToOffice = holidayDao
                .findCalendarEventsForThisYearAndNext(customer.getOfficeId());
        this.activationDate = new DateTime(new DateTimeService().getCurrentJavaDateTime()).toDate();
        List<Days> workingDays = futureCalendarEventsApplicableToOffice.getWorkingDays();
        List<Holiday> holidays = futureCalendarEventsApplicableToOffice.getHolidays();

        logger.debug("In SavingsBO::generateDepositAccountActions()");
        // deposit happens on each meeting date of the customer. If for
        // center/group with individual deposits, insert row for every client
        if (this.getCustomer().getCustomerMeeting() != null
                && this.getCustomer().getCustomerMeeting().getMeeting() != null) {
            MeetingBO depositSchedule = this.getCustomer().getCustomerMeeting().getMeeting();

            if (this.getCustomer().getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue())
                    || this.getCustomer().getCustomerLevel().getId().equals(CustomerLevel.GROUP.getValue())
                            && this.getRecommendedAmntUnit().getId()
                                    .equals(RecommendedAmountUnit.COMPLETE_GROUP.getValue())) {
                this.generateDepositAccountActions(this.getCustomer(), depositSchedule, workingDays, holidays,
                        new DateTime(this.activationDate));
            } else {
                List<CustomerBO> children;
                try {
                    children = this.getCustomer().getChildren(CustomerLevel.CLIENT,
                            ChildrenStateType.ACTIVE_AND_ONHOLD);
                } catch (CustomerException ce) {
                    throw new AccountException(ce);
                }
                for (CustomerBO customer1 : children) {
                    this.generateDepositAccountActions(customer1, depositSchedule, workingDays, holidays,
                            new DateTime(this.activationDate));
                }
            }
        }

        InterestScheduledEvent interestPostingEvent = new SavingsInterestScheduledEventFactory()
                .createScheduledEventFrom(this.savingsOffering.getFreqOfPostIntcalc().getMeeting());
        this.nextIntPostDate = interestPostingEvent
                .nextMatchingDateAfter(new LocalDate(startOfFiscalYear()), new LocalDate(this.activationDate))
                .toDateMidnight().toDate();
    }

    public Money getRecommendedAmount() {
        return recommendedAmount;
    }

    public void setRecommendedAmount(final Money recommendedAmount) {
        this.recommendedAmount = recommendedAmount;
    }

    public Money getSavingsBalance() {
        return savingsBalance;
    }

    public void setSavingsBalance(final Money savingsBalance) {
        this.savingsBalance = savingsBalance;
    }

    public SavingsOfferingBO getSavingsOffering() {
        return savingsOffering;
    }

    void setSavingsOffering(final SavingsOfferingBO savingsOffering) {
        this.savingsOffering = savingsOffering;
    }

    public SavingsPerformanceEntity getSavingsPerformance() {
        return savingsPerformance;
    }

    public Date getActivationDate() {
        return activationDate;
    }

    public void setActivationDate(final Date activationDate) {
        this.activationDate = activationDate;
    }

    /**
     * Most callers will want to call {@link #getRecommendedAmountUnit()}.
     */
    public RecommendedAmntUnitEntity getRecommendedAmntUnit() {
        return recommendedAmntUnit;
    }

    void setRecommendedAmntUnit(final RecommendedAmntUnitEntity recommendedAmntUnit) {
        this.recommendedAmntUnit = recommendedAmntUnit;
    }

    public RecommendedAmountUnit getRecommendedAmountUnit() {
        return RecommendedAmountUnit.fromInt(recommendedAmntUnit.getId());
    }

    public void setRecommendedAmountUnit(final RecommendedAmountUnit unit) {
        this.recommendedAmntUnit = new RecommendedAmntUnitEntity(unit);
    }

    public Money getInterestToBePosted() {
        return interestToBePosted;
    }

    public void setInterestToBePosted(final Money interestToBePosted) {
        this.interestToBePosted = interestToBePosted;
    }

    public Date getLastIntPostDate() {
        return lastIntPostDate;
    }

    void setLastIntPostDate(final Date lastIntPostDate) {
        this.lastIntPostDate = lastIntPostDate;
    }

    public Date getNextIntPostDate() {
        return nextIntPostDate;
    }

    public void setNextIntPostDate(final Date nextIntPostDate) {
        this.nextIntPostDate = nextIntPostDate;
    }

    public Set<SavingsActivityEntity> getSavingsActivityDetails() {
        return savingsActivityDetails;
    }

    public void setSavingsActivityDetails(final Set<SavingsActivityEntity> savingsActivityDetails) {
        this.savingsActivityDetails = savingsActivityDetails;
    }

    public void addSavingsActivityDetails(final SavingsActivityEntity savingsActivity) {
        savingsActivityDetails.add(savingsActivity);
    }

    @Override
    public AccountTypes getType() {
        return AccountTypes.SAVINGS_ACCOUNT;
    }

    @Override
    public boolean isOpen() {
        return !(getAccountState().getId().equals(AccountState.SAVINGS_CANCELLED.getValue())
                || getAccountState().getId().equals(AccountState.SAVINGS_CLOSED.getValue()));
    }

    /**
     * @deprecated use {@link SavingsDao#save(SavingsBO)} to persist savings account.
     */
    @Deprecated
    public void save() throws AccountException {
        logger.info("In SavingsBO::save(), Before Saving , accountId: " + getAccountId());

        try {
            this.addAccountStatusChangeHistory(new AccountStatusChangeHistoryEntity(this.getAccountState(),
                    this.getAccountState(), getPersonnelPersistence().getPersonnel(userContext.getId()), this));

            getSavingsPersistence().createOrUpdate(this);
            OfficeBO branch = getSavingsPersistence().getPersistentObject(OfficeBO.class,
                    userContext.getBranchId());
            this.globalAccountNum = generateId(branch.getGlobalOfficeNum());

            getSavingsPersistence().createOrUpdate(this);
        } catch (PersistenceException e) {
            throw new AccountException(e);
        }
        logger.info("In SavingsBO::save(), Successfully saved , accountId: " + getAccountId());
    }

    public void update(final Money recommendedAmount, final Set<AccountCustomFieldEntity> accountCustomFields) {

        if (isDepositScheduleBeRegenerated()) {
            if (this.recommendedAmount != null && recommendedAmount != null
                    && !this.recommendedAmount.equals(recommendedAmount)) {
                for (AccountActionDateEntity scheduledDeposit : this.getAccountActionDates()) {
                    if (scheduledDeposit.isOnOrAfter(new LocalDate())) {
                        ((SavingsScheduleEntity) scheduledDeposit).setDeposit(recommendedAmount);
                    }
                }
            }
        }
        this.recommendedAmount = recommendedAmount;
        this.setAccountCustomFields(accountCustomFields);
    }

    public boolean isMandatory() {
        return this.savingsOffering.isMandatory();
    }

    public boolean isVoluntary() {
        return this.savingsOffering.isVoluntary();
    }

    public boolean isDepositScheduleBeRegenerated() {
        return getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_APPROVED
                || getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_INACTIVE;
    }

    public boolean isActive() {
        return AccountState.SAVINGS_ACTIVE.getValue().equals(this.getAccountState().getId());
    }

    public boolean isInActive() {
        return AccountState.SAVINGS_INACTIVE.getValue().equals(this.getAccountState().getId());
    }

    public void postInterest(InterestScheduledEvent postingSchedule,
            InterestPostingPeriodResult interestPostingPeriodResult, PersonnelBO createdBy) {

        Money actualInterestToBePosted = interestPostingPeriodResult.getDifferenceInInterest();

        LocalDate currentPostingDate = interestPostingPeriodResult.getPostingPeriod().getEndDate();
        LocalDate nextPostingDate = postingSchedule.nextMatchingDateFromAlreadyMatchingDate(currentPostingDate);

        doPostInterest(currentPostingDate, actualInterestToBePosted, createdBy);
        updatePostingDetails(nextPostingDate);
    }

    private void doPostInterest(LocalDate currentPostingDate, Money actualInterestToBePosted,
            PersonnelBO loggedInUser) {
        this.savingsBalance = this.savingsBalance.add(actualInterestToBePosted);
        this.savingsPerformance.setTotalInterestDetails(actualInterestToBePosted);

        SavingsActivityEntity savingsActivity = SavingsActivityEntity.savingsInterestPosting(this, personnel,
                this.savingsBalance, actualInterestToBePosted, currentPostingDate.toDateMidnight().toDate());
        savingsActivityDetails.add(savingsActivity);

        AccountPaymentEntity interestPayment = AccountPaymentEntity.savingsInterestPosting(this,
                actualInterestToBePosted, currentPostingDate.toDateMidnight().toDate(), loggedInUser);

        DateTime dueDate = new DateTime();
        SavingsTrxnDetailEntity interestPostingTransaction = SavingsTrxnDetailEntity.savingsInterestPosting(
                interestPayment, this.customer, this.savingsBalance, currentPostingDate.toDateMidnight().toDate(),
                dueDate, loggedInUser);

        interestPayment.addAccountTrxn(interestPostingTransaction);
        this.addAccountPayment(interestPayment);

        // NOTE: financial Transaction Processing should be decoupled from application domain model.
        try {
            BaseFinancialActivity baseFinancialActivity = new SavingsInterestPostingFinancialActivity(
                    interestPostingTransaction);
            baseFinancialActivity.buildAccountEntries();
        } catch (FinancialException e) {
            throw new MifosRuntimeException(e);
        }
    }

    public void updatePostingDetails(LocalDate nextPostingDate) {
        this.lastIntPostDate = this.nextIntPostDate;
        this.nextIntPostDate = nextPostingDate.toDateMidnight().toDate();
        this.interestToBePosted = Money.zero(this.getCurrency());
    }

    public void closeAccount(final AccountPaymentEntity payment, final AccountNotesEntity notes,
            final CustomerBO customer, PersonnelBO loggedInUser) {

        AccountStateEntity previousAccountState = this.getAccountState();
        AccountStateEntity closedAccountState = new AccountStateEntity(AccountState.SAVINGS_CLOSED);

        AccountStatusChangeHistoryEntity statusChangeHistory = new AccountStatusChangeHistoryEntity(
                previousAccountState, closedAccountState, loggedInUser, this);
        this.addAccountStatusChangeHistory(statusChangeHistory);
        this.setAccountState(closedAccountState);

        Money interestOutstanding = payment.getAmount().subtract(this.savingsBalance);

        if (interestOutstanding.isGreaterThanZero()) {
            LocalDate currentPostingDate = new LocalDate(payment.getPaymentDate());
            LocalDate nextPostingDate = new LocalDate();
            doPostInterest(currentPostingDate, interestOutstanding, loggedInUser);
            updatePostingDetails(nextPostingDate);
        }

        Date transactionDate = new DateTimeService().getCurrentDateMidnight().toDate();
        if (payment.getAmount().isGreaterThanZero()) {

            SavingsTrxnDetailEntity withdrawal = SavingsTrxnDetailEntity.savingsWithdrawal(payment, customer,
                    this.savingsBalance, payment.getAmount(), loggedInUser, transactionDate, transactionDate,
                    transactionDate);

            payment.addAccountTrxn(withdrawal);

            this.addAccountPayment(payment);

            SavingsActivityEntity interestPostingActivity = SavingsActivityEntity.savingsWithdrawal(this,
                    loggedInUser, this.savingsBalance, payment.getAmount(), payment.getPaymentDate());
            savingsActivityDetails.add(interestPostingActivity);

            this.savingsPerformance
                    .setTotalWithdrawals(this.savingsPerformance.getTotalWithdrawals().add(payment.getAmount()));

            try {
                buildFinancialEntries(payment.getAccountTrxns());
            } catch (AccountException e) {
                throw new BusinessRuleException(e.getKey(), e);
            }
        }

        this.addAccountNotes(notes);
        //        this.lastIntCalcDate = transactionDate;
        this.lastIntPostDate = transactionDate;
        //        this.interIntCalcDate = null;
        this.savingsBalance = new Money(getCurrency());
        this.interestToBePosted = new Money(getCurrency());
        this.setClosedDate(new DateTimeService().getCurrentJavaDateTime());
    }

    /**
     * remove after usuage in constructor is removed.
     */
    @Deprecated
    public void generateAndUpdateDepositActionsForClient(final ClientBO client, final List<Days> workingDays,
            final List<Holiday> holidays) throws AccountException {

        if (client.getCustomerMeeting().getMeeting() != null) {
            if (!(getCustomer().getLevel() == CustomerLevel.GROUP
                    && getRecommendedAmntUnit().getId().equals(RecommendedAmountUnit.COMPLETE_GROUP.getValue()))) {
                DateTime today = new DateTime().toDateMidnight().toDateTime();
                generateDepositAccountActions(client, client.getCustomerMeeting().getMeeting(), workingDays,
                        holidays, today);
                this.update();
            }
        }
    }

    @Deprecated
    public void generateDepositAccountActions(final CustomerBO customer, final MeetingBO meeting,
            final List<Days> workingDays, final List<Holiday> holidays, final DateTime startingFrom) {

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

        List<DateTime> depositDates = dateGeneration.generateScheduledDates(10, startingFrom, scheduledEvent,
                false);

        short installmentNumber = 1;
        for (DateTime date : depositDates) {
            AccountActionDateEntity actionDate = helper.createActionDateObject(this, customer, installmentNumber++,
                    date.toDate(), userContext.getId(), getRecommendedAmount());
            addAccountActionDate(actionDate);
            logger.debug(
                    "In SavingsBO::generateDepositAccountActions(), Successfully added account action on date: "
                            + date);
        }
    }

    private void generateDepositAccountActions(final CustomerBO customer, final MeetingBO meeting,
            final AccountActionDateEntity lastInstallment, final List<Days> workingDays,
            final List<Holiday> holidays) {

        DateTime startFromDayAfterLastKnownInstallmentDate = new DateTime(lastInstallment.getActionDate())
                .plusDays(1);

        ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(meeting);
        ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
                workingDays, holidays);
        List<DateTime> depositDates = dateGeneration.generateScheduledDates(10,
                startFromDayAfterLastKnownInstallmentDate, scheduledEvent, false);

        short installmentNumber = lastInstallment.getInstallmentId();
        for (DateTime depositDate : depositDates) {
            AccountActionDateEntity actionDate = helper.createActionDateObject(this, customer, ++installmentNumber,
                    depositDate.toDate(), (short) 1, getRecommendedAmount());

            addAccountActionDate(actionDate);
            logger.debug(
                    "In SavingsBO::generateDepositAccountActions(), Successfully added account action on date: "
                            + depositDate);
        }
    }

    /**
     * @deprecated for deposits use {@link SavingsBO#deposit(AccountPaymentEntity, Integer)} and withdrawals use
     *             {@link SavingsBO#withdraw(AccountPaymentEntity)}
     */
    @Deprecated
    @Override
    protected AccountPaymentEntity makePayment(final PaymentData paymentData) throws AccountException {
        Money totalAmount = paymentData.getTotalAmount();
        Money enteredAmount = totalAmount;
        Date transactionDate = paymentData.getTransactionDate();
        List<AccountPaymentData> accountPayments = paymentData.getAccountPayments();

        if (paymentData.getCustomer() == null) {
            throw new NullPointerException("Customer in payment data during payment should not be null");
        }

        CustomerBO customer = paymentData.getCustomer();
        AccountPaymentEntity accountPayment = new AccountPaymentEntity(this, totalAmount,
                paymentData.getReceiptNum(), paymentData.getReceiptDate(),
                getPaymentTypeEntity(paymentData.getPaymentTypeId()), transactionDate);
        accountPayment.setCreatedByUser(paymentData.getPersonnel());
        accountPayment.setComment(paymentData.getComment());

        // make savings account active if inactive
        if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
            this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment",
                    paymentData.getPersonnel());
        }

        if (totalAmount.isGreaterThanZero() && paymentData.getAccountPayments().size() <= 0) {
            SavingsTrxnDetailEntity accountTrxn = buildUnscheduledDeposit(accountPayment, totalAmount,
                    paymentData.getPersonnel(), customer, transactionDate);
            accountPayment.addAccountTrxn(accountTrxn);
            addSavingsActivityDetails(buildSavingsActivity(totalAmount, getSavingsBalance(),
                    AccountActionTypes.SAVINGS_DEPOSIT.getValue(), transactionDate, paymentData.getPersonnel()));
            return accountPayment;
        }

        for (AccountPaymentData accountPaymentData : accountPayments) {
            SavingsScheduleEntity accountAction = (SavingsScheduleEntity) getAccountActionDate(
                    accountPaymentData.getInstallmentId(), customer.getCustomerId());
            if (accountAction != null && depositAmountIsInExcess(enteredAmount)) {
                if (accountAction.isPaid()) {
                    throw new AccountException("errors.update", new String[] { getGlobalAccountNum() });
                }
                Money depositAmount = new Money(getCurrency());
                PaymentStatus paymentStatus = PaymentStatus.UNPAID;
                if (enteredAmount.isGreaterThanOrEqual(accountAction.getTotalDepositDue())) {
                    depositAmount = accountAction.getTotalDepositDue();
                    enteredAmount = enteredAmount.subtract(accountAction.getTotalDepositDue());
                    paymentStatus = PaymentStatus.PAID;
                } else {
                    depositAmount = enteredAmount;
                    enteredAmount = new Money(getCurrency());
                }
                if (this.isVoluntary() && depositAmountIsInExcess(depositAmount)) {
                    paymentStatus = PaymentStatus.PAID;
                }
                savingsBalance = savingsBalance.add(depositAmount);

                savingsPerformance.setPaymentDetails(depositAmount);
                accountAction.setPaymentDetails(depositAmount, paymentStatus,
                        new java.sql.Date(transactionDate.getTime()));

                Short installmentId = accountAction.getInstallmentId();
                SavingsTrxnDetailEntity accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(accountPayment,
                        customer, this.savingsBalance, depositAmount, paymentData.getPersonnel(),
                        accountAction.getActionDate(), paymentData.getTransactionDate(),
                        paymentData.getTransactionDate(), installmentId);

                accountPayment.addAccountTrxn(accountTrxn);
            }
        }

        if (depositAmountIsInExcess(enteredAmount)) {
            SavingsTrxnDetailEntity accountTrxn = buildUnscheduledDeposit(accountPayment, enteredAmount,
                    paymentData.getPersonnel(), customer, transactionDate);
            accountPayment.addAccountTrxn(accountTrxn);
        }
        addSavingsActivityDetails(buildSavingsActivity(totalAmount, getSavingsBalance(),
                AccountActionTypes.SAVINGS_DEPOSIT.getValue(), transactionDate, paymentData.getPersonnel()));
        return accountPayment;
    }

    private SavingsTrxnDetailEntity buildUnscheduledDeposit(final AccountPaymentEntity accountPayment,
            final Money depositAmount, final PersonnelBO personnel, final CustomerBO customer,
            final Date transactionDate) {

        savingsBalance = savingsBalance.add(depositAmount);
        savingsPerformance.setPaymentDetails(depositAmount);

        Short installmentId = null;
        return SavingsTrxnDetailEntity.savingsDeposit(accountPayment, customer, this.savingsBalance, depositAmount,
                personnel, transactionDate, transactionDate, transactionDate, installmentId);
    }

    public void deposit(final AccountPaymentEntity payment, final CustomerBO payingCustomer)
            throws AccountException {

        final Money amountToDeposit = payment.getAmount();
        if (amountToDeposit.isLessThanOrEqualZero()) {
            return;
        }

        final Money savingsBalanceBeforeDeposit = new Money(getCurrency(), this.savingsBalance.getAmount());

        savingsBalance = savingsBalance.add(amountToDeposit);
        savingsPerformance.setPaymentDetails(amountToDeposit);

        final Date transactionDate = payment.getPaymentDate();
        final SavingsActivityEntity savingsActivity = savingsTransactionActivityHelper
                .createSavingsActivityForDeposit(payment.getCreatedByUser(), amountToDeposit, this.savingsBalance,
                        transactionDate, this);

        addSavingsActivityDetails(savingsActivity);
        addAccountPayment(payment);

        // make savings account active if inactive
        if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
            this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment",
                    payment.getCreatedByUser());
        }

        final List<SavingsScheduleEntity> unpaidDepositsForPayingCustomer = findAllUnpaidInstallmentsForPayingCustomerUpTo(
                transactionDate, payingCustomer.getCustomerId());

        // make scheduled payments (if any) and make an unscheduled payment
        // with any amount remaining
        final Money amountRemaining = this.savingsPaymentStrategy.makeScheduledPayments(payment,
                unpaidDepositsForPayingCustomer, payingCustomer, this.savingsOffering.getSavingsTypeAsEnum(),
                savingsBalanceBeforeDeposit);

        if (depositAmountIsInExcess(amountRemaining)) {
            final SavingsTrxnDetailEntity excessDepositTrxn = this.savingsTransactionActivityHelper
                    .createSavingsTrxnForDeposit(payment, amountRemaining, payingCustomer, null, savingsBalance);
            payment.addAccountTrxn(excessDepositTrxn);
        }

        buildFinancialEntries(payment.getAccountTrxns());
    }

    public void withdraw(final AccountPaymentEntity payment, final CustomerBO payingCustomer)
            throws AccountException {

        final Money amountToWithdraw = payment.getAmount();
        if (amountToWithdraw.isGreaterThan(savingsBalance)) {
            throw new AccountException("errors.insufficentbalance", new String[] { getGlobalAccountNum() });
        }
        final Money maxWithdrawAmount = getSavingsOffering().getMaxAmntWithdrawl();
        if (maxWithdrawAmount != null && maxWithdrawAmount.isNonZero()
                && amountToWithdraw.isGreaterThan(maxWithdrawAmount)) {
            throw new AccountException("errors.exceedmaxwithdrawal", new String[] { getGlobalAccountNum() });
        }

        // make savings account active if inactive
        if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
            this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment",
                    payment.getCreatedByUser());
        }
        this.addAccountPayment(payment);

        savingsBalance = savingsBalance.subtract(amountToWithdraw);
        savingsPerformance.setWithdrawDetails(amountToWithdraw);

        final SavingsActivityEntity savingsActivity = this.savingsTransactionActivityHelper
                .createSavingsActivityForWithdrawal(payment, this.savingsBalance, this);
        addSavingsActivityDetails(savingsActivity);

        final SavingsTrxnDetailEntity accountTrxnBO = this.savingsTransactionActivityHelper
                .createSavingsTrxnForWithdrawal(payment, amountToWithdraw, payingCustomer, this.savingsBalance);
        payment.addAccountTrxn(accountTrxnBO);

        buildFinancialEntries(payment.getAccountTrxns());
    }

    /**
     * @deprecated use {@link SavingsBO#withdraw(AccountPaymentEntity, CustomerBO)} instead and use save from
     *             DAO/Persistence for {@link SavingsBO}.
     */
    @Deprecated
    public AccountPaymentEntity withdraw(final PaymentData accountPaymentData, final boolean persist)
            throws AccountException {
        Money totalAmount = accountPaymentData.getTotalAmount();
        if (totalAmount.isGreaterThan(savingsBalance)) {
            throw new AccountException("errors.insufficentbalance", new String[] { getGlobalAccountNum() });
        }
        Money maxWithdrawAmount = getSavingsOffering().getMaxAmntWithdrawl();
        if (maxWithdrawAmount != null && maxWithdrawAmount.isNonZero()
                && totalAmount.isGreaterThan(maxWithdrawAmount)) {
            throw new AccountException("errors.exceedmaxwithdrawal", new String[] { getGlobalAccountNum() });
        }
        savingsBalance = savingsBalance.subtract(totalAmount);
        savingsPerformance.setWithdrawDetails(totalAmount);
        CustomerBO customer = accountPaymentData.getCustomer();
        AccountPaymentEntity accountPayment = new AccountPaymentEntity(this, totalAmount,
                accountPaymentData.getReceiptNum(), accountPaymentData.getReceiptDate(),
                getPaymentTypeEntity(accountPaymentData.getPaymentTypeId()),
                accountPaymentData.getTransactionDate());

        SavingsTrxnDetailEntity accountTrxnBO = SavingsTrxnDetailEntity.savingsWithdrawal(accountPayment, customer,
                this.savingsBalance, totalAmount, accountPaymentData.getPersonnel(),
                accountPaymentData.getTransactionDate(), accountPaymentData.getTransactionDate(),
                accountPaymentData.getTransactionDate());
        accountPayment.addAccountTrxn(accountTrxnBO);

        addAccountPayment(accountPayment);
        addSavingsActivityDetails(buildSavingsActivity(totalAmount, getSavingsBalance(),
                AccountActionTypes.SAVINGS_WITHDRAWAL.getValue(), accountPaymentData.getTransactionDate(),
                accountPaymentData.getPersonnel()));
        buildFinancialEntries(accountPayment.getAccountTrxns());

        // make savings account active if inactive
        if (this.getState().getValue().equals(AccountState.SAVINGS_INACTIVE.getValue())) {
            this.changeStatus(AccountState.SAVINGS_ACTIVE, null, "Account Made Active Due to Payment",
                    accountPayment.getCreatedByUser());
        }

        if (persist) {
            try {
                getSavingsPersistence().createOrUpdate(this);
            } catch (PersistenceException e) {
                throw new AccountException(e);
            }
        }
        return accountPayment;
    }

    @Override
    protected void activationDateHelper(final Short newStatusId) throws AccountException {

        if (ProcessFlowRules.isSavingsPendingApprovalStateEnabled()) {
            if (this.getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_PENDINGAPPROVAL
                    && newStatusId.shortValue() == AccountStates.SAVINGS_ACC_APPROVED) {

                goActiveForFristTimeAndGenerateSavingsSchedule(customer);
            }
        } else {
            if (this.getAccountState().getId().shortValue() == AccountStates.SAVINGS_ACC_PARTIALAPPLICATION
                    && newStatusId.shortValue() == AccountStates.SAVINGS_ACC_APPROVED) {
                goActiveForFristTimeAndGenerateSavingsSchedule(customer);
            }
        }
    }

    public AccountPaymentEntity adjustLastUserAction(Money amountAdjustedTo, String adjustmentNote,
            PersonnelBO updatedBy) {
        AccountPaymentEntity lastPayment = getLastPmnt();
        return adjustUserAction(amountAdjustedTo, adjustmentNote, new LocalDate(lastPayment.getPaymentDate()),
                updatedBy, lastPayment);
    }

    public AccountPaymentEntity adjustUserAction(Money amountAdjustedTo, String adjustmentNote,
            LocalDate adjustmentDate, PersonnelBO updatedBy, Integer paymentId) {
        AccountPaymentEntity payment = findPaymentById(paymentId);
        return adjustUserAction(amountAdjustedTo, adjustmentNote, adjustmentDate, updatedBy, payment);
    }

    private AccountPaymentEntity adjustUserAction(Money amountAdjustedTo, String adjustmentNote,
            LocalDate adjustmentDate, PersonnelBO updatedBy, AccountPaymentEntity payment) {
        AccountPaymentEntity newPayment = null;
        try {
            if (!isAdjustPossibleOnTrxn(amountAdjustedTo, payment)) {
                throw new BusinessRuleException(AccountExceptionConstants.CANNOTADJUST);
            }

            AccountActionTypes savingsTransactionType = findFirstDepositOrWithdrawalTransaction(payment);
            Date adjustedOn = new DateTimeService().getCurrentJavaDateTime();

            List<AccountTrxnEntity> reversedTransactions = reverseTransaction(adjustmentNote, updatedBy, payment,
                    savingsTransactionType, adjustedOn);

            buildFinancialEntries(new LinkedHashSet<AccountTrxnEntity>(reversedTransactions));

            if (amountAdjustedTo.isGreaterThanZero()) {
                Set<AccountTrxnEntity> adjustedPaymentTransactions = createNewAccountPaymentWithAdjustedAmount(
                        amountAdjustedTo, updatedBy, payment, savingsTransactionType, adjustedOn, adjustmentDate);

                buildFinancialEntries(adjustedPaymentTransactions);
                newPayment = adjustedPaymentTransactions
                        .toArray(new AccountTrxnEntity[adjustedPaymentTransactions.size()])[0].getAccountPayment();
            }
        } catch (AccountException e) {
            throw new BusinessRuleException(e.getKey(), e);
        }

        goActiveDueToDepositOrWithdrawalOnAccount(updatedBy);
        return newPayment;
    }

    private void goActiveDueToDepositOrWithdrawalOnAccount(PersonnelBO updatedBy) {
        if (!this.isActive()) {
            AccountStateEntity oldAccountState = this.getAccountState();
            AccountStateEntity newAccountState = new AccountStateEntity(AccountState.SAVINGS_ACTIVE);
            this.setAccountState(newAccountState);

            AccountStatusChangeHistoryEntity savingsAccountToActive = new AccountStatusChangeHistoryEntity(
                    oldAccountState, newAccountState, updatedBy, this);
            this.accountStatusChangeHistory.add(savingsAccountToActive);
        }
    }

    private Set<AccountTrxnEntity> createNewAccountPaymentWithAdjustedAmount(Money amountAdjustedTo,
            PersonnelBO updatedBy, AccountPaymentEntity payment, AccountActionTypes savingsTransactionType,
            Date adjustedOn, LocalDate adjustmentDate) {

        AccountPaymentEntity newAccountPayment = new AccountPaymentEntity(this, amountAdjustedTo,
                payment.getReceiptNumber(), payment.getReceiptDate(), payment.getPaymentType(),
                adjustmentDate.toDateMidnight().toDate());
        newAccountPayment.setCreatedByUser(updatedBy);
        newAccountPayment.setAmount(amountAdjustedTo);

        Set<AccountTrxnEntity> accountTrxns = new HashSet<AccountTrxnEntity>();
        if (isMandatory() && savingsTransactionType.equals(AccountActionTypes.SAVINGS_DEPOSIT)) {
            accountTrxns = createDepositTrxnsForMandatoryAccountsAfterAdjust(newAccountPayment, payment,
                    amountAdjustedTo, adjustmentDate, updatedBy);
        } else if (isVoluntary() && savingsTransactionType.equals(AccountActionTypes.SAVINGS_DEPOSIT)) {
            accountTrxns = createDepositTrxnsForVolAccountsAfterAdjust(newAccountPayment, payment, amountAdjustedTo,
                    adjustmentDate, updatedBy);
        } else {
            accountTrxns = createWithdrawalTrxnsAfterAdjust(newAccountPayment, payment, amountAdjustedTo,
                    adjustmentDate, updatedBy);
        }

        for (AccountTrxnEntity accountTrxn : accountTrxns) {
            newAccountPayment.addAccountTrxn(accountTrxn);
        }

        this.addAccountPayment(newAccountPayment);

        AccountActionEntity depositOrWithdrawalTransactionType = new AccountActionEntity(savingsTransactionType);
        SavingsActivityEntity depositOrWithdrawalActivity = new SavingsActivityEntity(updatedBy,
                depositOrWithdrawalTransactionType, amountAdjustedTo, this.savingsBalance, adjustedOn, this);
        this.savingsActivityDetails.add(depositOrWithdrawalActivity);
        return newAccountPayment.getAccountTrxns();
    }

    private List<AccountTrxnEntity> reverseTransaction(String adjustmentNote, PersonnelBO updatedBy,
            AccountPaymentEntity payment, AccountActionTypes savingsTransactionType, Date adjustedOn)
            throws AccountException {

        for (AccountTrxnEntity accntTrxn : payment.getAccountTrxns()) {
            if (AccountActionTypes.SAVINGS_DEPOSIT.equals(savingsTransactionType)) {
                adjustForDeposit(accntTrxn);
            } else if (AccountActionTypes.SAVINGS_WITHDRAWAL.equals(savingsTransactionType)) {
                adjustForWithdrawal(accntTrxn);
            }
        }

        SavingsActivityEntity adjustment = SavingsActivityEntity.savingsAdjustment(this, updatedBy,
                this.savingsBalance, payment.getAmount(), adjustedOn);
        savingsActivityDetails.add(adjustment);

        return payment.reversalAdjustment(updatedBy, adjustmentNote);
    }

    private AccountActionTypes findFirstDepositOrWithdrawalTransaction(AccountPaymentEntity lastPayment) {
        AccountActionTypes accountActionTypes = AccountActionTypes.SAVINGS_INTEREST_POSTING;
        for (AccountTrxnEntity accntTrxn : lastPayment.getAccountTrxns()) {
            if (!accntTrxn.getAccountActionEntity().isSavingsAdjustment()) {
                accountActionTypes = AccountActionTypes.fromInt(accntTrxn.getAccountActionEntity().getId());
            }
        }
        return accountActionTypes;
    }

    public AccountPaymentEntity findMostRecentDepositOrWithdrawalByDate() {
        AccountPaymentEntity mostRecentPayment = null;
        if (!this.accountPayments.isEmpty()) {
            for (AccountPaymentEntity accountPaymentEntity : this.accountPayments) {
                if (mostRecentPayment == null && accountPaymentEntity.isSavingsDepositOrWithdrawal()) {
                    mostRecentPayment = accountPaymentEntity;
                } else if (mostRecentPayment != null) {
                    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())
                                    && accountPaymentEntity.isSavingsDepositOrWithdrawal()) {
                        mostRecentPayment = accountPaymentEntity;
                    }
                }
            }
        }
        return mostRecentPayment;
    }

    private List<AccountActionDateEntity> getAccountActions(final Date dueDate, final Integer customerId) {
        List<AccountActionDateEntity> accountActions = new ArrayList<AccountActionDateEntity>();
        for (AccountActionDateEntity accountAction : getAccountActionDates()) {
            if (accountAction.getActionDate().compareTo(dueDate) <= 0 && !accountAction.isPaid()
                    && accountAction.getCustomer().getCustomerId() == customerId) {
                accountActions.add(accountAction);
            }
        }
        return accountActions;
    }

    private Set<AccountTrxnEntity> createWithdrawalTrxnsAfterAdjust(final AccountPaymentEntity newAccountPayment,
            final AccountPaymentEntity lastAccountPayment, final Money newAmount, final LocalDate adjustmentDate,
            PersonnelBO loggedInUser) {

        Set<AccountTrxnEntity> newTrxns = new LinkedHashSet<AccountTrxnEntity>();
        SavingsTrxnDetailEntity accountTrxn = null;

        // create transaction for withdrawal
        SavingsTrxnDetailEntity oldSavingsAccntTrxn = null;

        for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {
            oldSavingsAccntTrxn = (SavingsTrxnDetailEntity) oldAccntTrxn;
            break;
        }

        this.savingsBalance = this.savingsBalance.subtract(newAmount);

        Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
        accountTrxn = SavingsTrxnDetailEntity.savingsWithdrawal(newAccountPayment,
                oldSavingsAccntTrxn.getCustomer(), newAmount, newAmount, loggedInUser,
                oldSavingsAccntTrxn.getDueDate(), adjustmentDate.toDateMidnight().toDate(), transactionCreatedDate);

        this.savingsPerformance.setTotalWithdrawals(
                this.savingsPerformance.getTotalWithdrawals().add(accountTrxn.getWithdrawlAmount()));
        newTrxns.add(accountTrxn);
        return newTrxns;
    }

    private Set<AccountTrxnEntity> createDepositTrxnsForMandatoryAccountsAfterAdjust(
            final AccountPaymentEntity newAccountPayment, final AccountPaymentEntity lastAccountPayment,
            Money newAmount, LocalDate adjustmentDate, PersonnelBO createdBy) {

        Set<AccountTrxnEntity> newTrxns = new LinkedHashSet<AccountTrxnEntity>();
        SavingsTrxnDetailEntity accountTrxn = null;
        CustomerBO customer = null;
        Date trxnDate = adjustmentDate.toDateMidnight().toDate();

        for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {
            customer = oldAccntTrxn.getCustomer();
            break;
        }

        List<AccountActionDateEntity> accountActionList = getAccountActions(lastAccountPayment.getPaymentDate(),
                customer.getCustomerId());
        for (AccountActionDateEntity accountActionDateEntity : accountActionList) {
            SavingsScheduleEntity accountAction = (SavingsScheduleEntity) accountActionDateEntity;
            if (newAmount.isZero()) {
                break;
            }
            accountTrxn = null;

            // if payment covers required deposit
            if (accountAction.getDeposit().isLessThanOrEqual(newAmount)) {
                this.savingsBalance = this.savingsBalance.add(accountAction.getDeposit());

                Short installmentId = accountAction.getInstallmentId();
                Date dueDate = accountAction.getActionDate();
                Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
                accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer,
                        this.savingsBalance, accountAction.getDeposit(), createdBy, dueDate, trxnDate,
                        transactionCreatedDate, installmentId);

                newAmount = newAmount.subtract(accountAction.getDeposit());

                accountAction.setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
                accountAction.setPaymentStatus(PaymentStatus.PAID);

            } else {
                this.savingsBalance = this.savingsBalance.add(newAmount);

                Short installmentId = accountAction.getInstallmentId();
                Date dueDate = accountAction.getActionDate();
                Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
                accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer,
                        this.savingsBalance, newAmount, createdBy, dueDate, trxnDate, transactionCreatedDate,
                        installmentId);

                newAmount = newAmount.subtract(newAmount);
                accountAction.setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
                accountAction.setPaymentStatus(PaymentStatus.UNPAID);
            }
            accountAction.setPaymentDate(new DateTimeService().getCurrentJavaSqlDate());
            getSavingsPerformance().setTotalDeposits(
                    getSavingsPerformance().getTotalDeposits().add(accountTrxn.getDepositAmount()));
            newTrxns.add(accountTrxn);
        }

        // add trxn for excess amount
        if (newAmount.isGreaterThanZero()) {
            this.savingsBalance = this.savingsBalance.add(newAmount);
            Short installmentId = null;
            Date dueDate = null;
            Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();
            accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance,
                    newAmount, createdBy, dueDate, trxnDate, transactionCreatedDate, installmentId);

            newAmount = newAmount.subtract(newAmount);
            getSavingsPerformance().setTotalDeposits(
                    getSavingsPerformance().getTotalDeposits().add(accountTrxn.getDepositAmount()));
            newTrxns.add(accountTrxn);
        }
        return newTrxns;
    }

    /*
     * FIXME - keithw - it doesnt make sense to be that voluntary account break up account payments into more than one account transaction
     * just because the amount deposited is greater than the 'recommended' amount.
     *
     * As a result there is no need to make a distinction between the amount deposited (be it less or greater than recommended amount)
     */
    private Set<AccountTrxnEntity> createDepositTrxnsForVolAccountsAfterAdjust(
            final AccountPaymentEntity newAccountPayment, final AccountPaymentEntity lastAccountPayment,
            Money newAmount, LocalDate adjustmentDate, PersonnelBO loggedInUser) {

        Set<AccountTrxnEntity> newTrxns = new LinkedHashSet<AccountTrxnEntity>();
        SavingsTrxnDetailEntity accountTrxn = null;
        CustomerBO customer = null;
        Date trxnDate = adjustmentDate.toDateMidnight().toDate();

        for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {
            customer = oldAccntTrxn.getCustomer();
            break;
        }

        Short installmentId = null;
        Date dueDate = null;
        Date transactionCreatedDate = new DateTimeService().getCurrentJavaDateTime();

        for (AccountTrxnEntity oldAccntTrxn : lastAccountPayment.getAccountTrxns()) {

            if (oldAccntTrxn.getAccountActionEntity().getId()
                    .equals(AccountActionTypes.SAVINGS_DEPOSIT.getValue())) {
                SavingsTrxnDetailEntity oldSavingsAccntTrxn = (SavingsTrxnDetailEntity) oldAccntTrxn;

                if (oldAccntTrxn.getInstallmentId() != null) {
                    SavingsScheduleEntity accountAction = (SavingsScheduleEntity) getAccountActionDate(
                            oldSavingsAccntTrxn.getInstallmentId(),
                            oldSavingsAccntTrxn.getCustomer().getCustomerId());

                    installmentId = accountAction.getInstallmentId();
                    dueDate = accountAction.getActionDate();

                    // if recommended amount is covered by payment
                    if (accountAction.getDeposit().isLessThanOrEqual(newAmount)) {

                        this.savingsBalance = this.savingsBalance.add(accountAction.getDeposit());

                        accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer,
                                this.savingsBalance, accountAction.getDeposit(), loggedInUser, dueDate, trxnDate,
                                transactionCreatedDate, installmentId);

                        newAmount = newAmount.subtract(accountAction.getDeposit());
                        accountAction
                                .setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
                        accountAction.setPaymentStatus(PaymentStatus.PAID);
                        accountAction.setPaymentDate(new DateTimeService().getCurrentJavaSqlDate());
                        this.savingsPerformance.setTotalDeposits(
                                this.savingsPerformance.getTotalDeposits().add(accountTrxn.getDepositAmount()));
                    } else if (newAmount.isNonZero()) {
                        // not zero and amount paid is less that recommended amount
                        this.savingsBalance = this.savingsBalance.add(newAmount);

                        accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer,
                                this.savingsBalance, newAmount, loggedInUser, dueDate, trxnDate,
                                transactionCreatedDate, installmentId);

                        newAmount = newAmount.subtract(newAmount);
                        accountAction
                                .setDepositPaid(accountAction.getDepositPaid().add(accountTrxn.getDepositAmount()));
                        accountAction.setPaymentStatus(PaymentStatus.UNPAID);
                        accountAction.setPaymentDate(new DateTimeService().getCurrentJavaSqlDate());
                        this.savingsPerformance.setTotalDeposits(
                                this.savingsPerformance.getTotalDeposits().add(accountTrxn.getDepositAmount()));
                    }
                    break;
                }
            }
        }

        if (accountTrxn != null) {
            newTrxns.add(accountTrxn);
        }

        // Create a new transaction with remaining amount
        if (newAmount.isGreaterThanZero()) {
            this.savingsBalance = this.savingsBalance.add(newAmount);

            accountTrxn = SavingsTrxnDetailEntity.savingsDeposit(newAccountPayment, customer, this.savingsBalance,
                    newAmount, loggedInUser, dueDate, trxnDate, transactionCreatedDate, installmentId);

            this.savingsPerformance.setTotalDeposits(
                    this.savingsPerformance.getTotalDeposits().add(accountTrxn.getDepositAmount()));
            newTrxns.add(accountTrxn);
        }
        return newTrxns;
    }

    private void adjustForDeposit(final AccountTrxnEntity accntTrxn) {
        SavingsTrxnDetailEntity savingsTrxn = (SavingsTrxnDetailEntity) accntTrxn;
        Money depositAmount = savingsTrxn.getDepositAmount();

        Short installmentId = savingsTrxn.getInstallmentId();
        this.savingsBalance = this.savingsBalance.subtract(depositAmount);

        SavingsScheduleEntity accntActionDate = (SavingsScheduleEntity) getAccountActionDate(installmentId,
                accntTrxn.getCustomer().getCustomerId());
        if (accntActionDate != null) {
            accntActionDate.setDepositPaid(accntActionDate.getDepositPaid().subtract(depositAmount));
            accntActionDate.setPaymentStatus(PaymentStatus.UNPAID);
            accntActionDate.setPaymentDate(null);
        }
        this.savingsPerformance
                .setTotalDeposits(this.savingsPerformance.getTotalDeposits().subtract(depositAmount));
    }

    private void adjustForWithdrawal(final AccountTrxnEntity accntTrxn) {
        SavingsTrxnDetailEntity savingsTrxn = (SavingsTrxnDetailEntity) accntTrxn;
        setSavingsBalance(getSavingsBalance().add(savingsTrxn.getWithdrawlAmount()));
        getSavingsPerformance().setTotalWithdrawals(
                getSavingsPerformance().getTotalWithdrawals().subtract(savingsTrxn.getWithdrawlAmount()));
    }

    public boolean isAdjustPossibleOnTrxn(final Money amountAdjustedTo, AccountPaymentEntity accountPayment) {

        boolean adjustmentIsPossible = false;

        if (this.isActive() || this.isInActive()) {

            if (paymentIsGreaterThanZero(accountPayment) && paymentIsADepositOrWithdrawal(accountPayment)) {
                if (accountPayment.getAmount().equals(amountAdjustedTo)) {
                    adjustmentIsPossible = false;
                } else {
                    adjustmentIsPossible = true;
                }

                if (adjustmentIsPossible && withdrawalAdjustmentIsValid(accountPayment, amountAdjustedTo)
                        && withdrawalAdjustmentDoesNotMakeBalanceNegative(accountPayment, amountAdjustedTo)) {
                    adjustmentIsPossible = true;
                } else {
                    adjustmentIsPossible = false;
                }
            }
        }

        return adjustmentIsPossible;
    }

    private boolean paymentIsADepositOrWithdrawal(AccountPaymentEntity accountPayment) {
        return accountPayment.isSavingsDepositOrWithdrawal();
    }

    private boolean paymentIsGreaterThanZero(AccountPaymentEntity accountPayment) {
        return accountPayment != null && accountPayment.getAmount().isGreaterThanZero();
    }

    private boolean withdrawalAdjustmentIsValid(final AccountPaymentEntity accountPayment,
            final Money withdrawalAmount) {
        boolean withdrawalAdjustmentIsValid = true;

        if (accountPayment.isSavingsWithdrawal() && withdrawalAmount != null && withdrawalAmount.isNonZero()
                && this.savingsOffering.isMaxWithdrawalAmountExceeded(withdrawalAmount)) {
            withdrawalAdjustmentIsValid = false;
        }
        return withdrawalAdjustmentIsValid;
    }

    private boolean withdrawalAdjustmentDoesNotMakeBalanceNegative(final AccountPaymentEntity accountPayment,
            final Money amountAdjustedTo) {

        boolean balanceIsPositive = true;
        Money balanceAfterAdjust = this.savingsBalance;

        for (AccountTrxnEntity accntTrxn : accountPayment.getAccountTrxns()) {
            SavingsTrxnDetailEntity savingsTrxn = (SavingsTrxnDetailEntity) accntTrxn;

            if (accountPayment.isSavingsWithdrawal()
                    && amountAdjustedTo.isGreaterThan(savingsTrxn.getWithdrawlAmount())) {
                balanceAfterAdjust = balanceAfterAdjust
                        .subtract(amountAdjustedTo.subtract(savingsTrxn.getWithdrawlAmount()));
                if (balanceAfterAdjust.isLessThanZero()) {
                    balanceIsPositive = false;
                }
            }
        }
        return balanceIsPositive;
    }

    public AccountNotesEntity createAccountNotes(final String comment) throws AccountException {
        try {
            AccountNotesEntity accountNotes = new AccountNotesEntity(new DateTimeService().getCurrentJavaSqlDate(),
                    comment, getPersonnelPersistence().getPersonnel(userContext.getId()), this);
            return accountNotes;

        } catch (PersistenceException e) {
            throw new AccountException(e);
        }
    }

    public Money getOverDueDepositAmount(final java.sql.Date meetingDate) {
        Money overdueAmount = new Money(getCurrency());
        if (isMandatory()) {
            for (AccountActionDateEntity accountActionDate : getAccountActionDates()) {
                if (!accountActionDate.isPaid() && accountActionDate.getActionDate().before(meetingDate)) {
                    overdueAmount = overdueAmount
                            .add(((SavingsScheduleEntity) accountActionDate).getTotalDepositDue());
                }
            }
        }
        return overdueAmount;
    }

    public List<SavingsRecentActivityDto> getRecentAccountActivity(final Integer count) {
        List<SavingsRecentActivityDto> accountActivityList = new ArrayList<SavingsRecentActivityDto>();
        int activitiesAdded = 0;
        for (SavingsActivityEntity activity : getSavingsActivityDetails()) {
            if (count == null || activitiesAdded < count.intValue()) {
                accountActivityList.add(createSavingsRecentActivityView(activity));
                activitiesAdded++;
            }
        }
        return accountActivityList;
    }

    private SavingsRecentActivityDto createSavingsRecentActivityView(final SavingsActivityEntity savingActivity) {
        SavingsRecentActivityDto savingsRecentActivityDto = new SavingsRecentActivityDto();
        savingsRecentActivityDto.setAccountTrxnId(savingActivity.getId());
        savingsRecentActivityDto.setActionDate(savingActivity.getTrxnCreatedDate());
        String preferredDate = DateUtils.getUserLocaleDate(this.userContext.getPreferredLocale(),
                savingActivity.getTrxnCreatedDate().toString());
        savingsRecentActivityDto.setUserPrefferedDate(preferredDate);
        savingsRecentActivityDto.setAmount(removeSign(savingActivity.getAmount()).toString());
        savingsRecentActivityDto.setActivity(savingActivity.getActivity().getName());
        savingsRecentActivityDto.setRunningBalance(savingActivity.getBalanceAmount().toString());
        return savingsRecentActivityDto;
    }

    @Override
    protected Money getDueAmount(final AccountActionDateEntity installment) {
        return ((SavingsScheduleEntity) installment).getTotalDepositDue();
    }

    @Override
    public Money getTotalAmountDue() {
        return getTotalAmountInArrears().add(getTotalAmountDueForNextInstallment());
    }

    @Override
    public Money getTotalAmountInArrears() {
        List<AccountActionDateEntity> installmentsInArrears = getDetailsOfInstallmentsInArrears();
        Money totalAmount = new Money(getCurrency());
        if (installmentsInArrears != null && installmentsInArrears.size() > 0) {
            for (AccountActionDateEntity accountAction : installmentsInArrears) {
                if (!(accountAction.getCustomer().getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue())
                        && accountAction.getCustomer().getStatus().equals(CustomerStatus.CLIENT_CLOSED))) {
                    totalAmount = totalAmount.add(getDueAmount(accountAction));
                }
            }
        }
        return totalAmount;
    }

    public Money getTotalAmountDueForNextInstallment() {
        AccountActionDateEntity nextAccountAction = getDetailsOfNextInstallment();
        Money totalAmount = new Money(getCurrency());
        if (nextAccountAction != null) {
            if (null != getAccountActionDates() && getAccountActionDates().size() > 0) {
                for (AccountActionDateEntity accntActionDate : getAccountActionDates()) {
                    if (accntActionDate.getInstallmentId().equals(nextAccountAction.getInstallmentId())
                            && !accntActionDate.isPaid()) {
                        if (!(accntActionDate.getCustomer().getCustomerLevel().getId()
                                .equals(CustomerLevel.CLIENT.getValue())
                                && accntActionDate.getCustomer().getStatus()
                                        .equals(CustomerStatus.CLIENT_CLOSED))) {
                            totalAmount = totalAmount
                                    .add(((SavingsScheduleEntity) accntActionDate).getTotalDepositDue());
                        }
                    }
                }
            }
        }
        return totalAmount;
    }

    /*
     * private Money getTotalAmountDueForInstallment(Short installmentId) { Money totalAmount = new
     * Money(getCurrency()); if (null != getAccountActionDates() && getAccountActionDates().size() > 0) { for
     * (AccountActionDateEntity accntActionDate : getAccountActionDates()) { if
     * (accntActionDate.getInstallmentId().equals(installmentId) && accntActionDate.getPaymentStatus().equals(
     * PaymentStatus.UNPAID.getValue())) { totalAmount = totalAmount .add(((SavingsScheduleEntity) accntActionDate)
     * .getTotalDepositDue()); } } }
     *
     * return totalAmount; }
     *
     * public Money getTotalAmountDueForNextInstallment() { AccountActionDateEntity nextAccountAction =
     * getDetailsOfNextInstallment(); if (nextAccountAction != null) return
     * getTotalAmountDueForInstallment(nextAccountAction .getInstallmentId()); return new Money(getCurrency()); }
     */

    private List<AccountActionDateEntity> getNextInstallment() {
        List<AccountActionDateEntity> nextInstallment = new ArrayList<AccountActionDateEntity>();
        AccountActionDateEntity nextAccountAction = getDetailsOfNextInstallment();
        if (nextAccountAction != null && null != getAccountActionDates() && getAccountActionDates().size() > 0) {
            for (AccountActionDateEntity accntActionDate : getAccountActionDates()) {
                if (accntActionDate.getInstallmentId().equals(nextAccountAction.getInstallmentId())
                        && !accntActionDate.isPaid()) {
                    nextInstallment.add(accntActionDate);
                }
            }
        }
        return nextInstallment;
    }

    public void waiveNextDepositAmountDue(PersonnelBO loggedInUser) {

        SavingsActivityEntity savingsActivity = SavingsActivityEntity.savingsWaiveAmountDueOnNextDeposit(this,
                loggedInUser, this.savingsBalance, getTotalAmountDueForNextInstallment(), new DateTime().toDate());
        savingsActivityDetails.add(savingsActivity);

        List<AccountActionDateEntity> nextInstallments = getNextInstallment();
        for (AccountActionDateEntity accountActionDate : nextInstallments) {
            ((SavingsScheduleEntity) accountActionDate).waiveDepositDue();
        }
    }

    public void waiveAmountOverDue(PersonnelBO loggedInUser) {

        SavingsActivityEntity savingsActivity = SavingsActivityEntity.savingsWaiveDepositAmountOverdue(this,
                loggedInUser, this.savingsBalance, getTotalAmountInArrears(), new DateTime().toDate());
        savingsActivityDetails.add(savingsActivity);

        List<AccountActionDateEntity> installmentsInArrears = getDetailsOfInstallmentsInArrears();
        for (AccountActionDateEntity accountActionDate : installmentsInArrears) {
            ((SavingsScheduleEntity) accountActionDate).waiveDepositDue();
        }
    }

    /**
     */
    @Deprecated
    private SavingsActivityEntity buildSavingsActivity(final Money amount, final Money balanceAmount,
            final short acccountActionId, final Date trxnDate, final PersonnelBO personnel)
            throws AccountException {
        AccountActionEntity accountAction;
        try {
            accountAction = getSavingsPersistence().getPersistentObject(AccountActionEntity.class,
                    acccountActionId);
        } catch (PersistenceException e) {
            throw new AccountException(e);
        }
        return new SavingsActivityEntity(personnel, accountAction, amount, balanceAmount, trxnDate, this);
    }

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

        MeetingBO customerMeeting = getCustomer().getCustomerMeetingValue();
        ScheduledEvent scheduledEvent = ScheduledEventFactory.createScheduledEventFrom(customerMeeting);
        LocalDate currentDate = new LocalDate();
        LocalDate thisIntervalStartDate = customerMeeting.startDateForMeetingInterval(currentDate);
        LocalDate nextMatchingDate = new LocalDate(
                scheduledEvent.nextEventDateAfter(thisIntervalStartDate.toDateTimeAtStartOfDay()));
        DateTime futureIntervalStartDate = customerMeeting.startDateForMeetingInterval(nextMatchingDate)
                .toDateTimeAtStartOfDay();

        ScheduledDateGeneration dateGeneration = new HolidayAndWorkingDaysAndMoratoriaScheduledDateGeneration(
                workingDays, holidays);

        int numberOfInstallmentsToGenerate = getLastInstallmentId();
        List<DateTime> meetingDates = dateGeneration.generateScheduledDates(numberOfInstallmentsToGenerate,
                futureIntervalStartDate, scheduledEvent, false);

        if (getCustomer().getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue()) || getCustomer()
                .getCustomerLevel().getId().equals(CustomerLevel.GROUP.getValue())
                && getRecommendedAmntUnit().getId().equals(RecommendedAmountUnit.COMPLETE_GROUP.getValue())) {
            updateSchedule(nextInstallment.getInstallmentId(), meetingDates);
        } else {
            List<CustomerBO> children;
            try {
                children = getCustomer().getChildren(CustomerLevel.CLIENT, ChildrenStateType.OTHER_THAN_CLOSED);
            } catch (CustomerException ce) {
                throw new AccountException(ce);
            }
            updateSavingsSchedule(nextInstallment.getInstallmentId(), meetingDates, children);
        }

    }

    private void updateSavingsSchedule(final Short nextInstallmentId, final List<DateTime> meetingDates,
            final List<CustomerBO> children) {
        short installmentId = nextInstallmentId;

        for (int count = 0; count < meetingDates.size(); count++) {
            for (CustomerBO customer : children) {
                AccountActionDateEntity accountActionDate = getAccountActionDate(installmentId,
                        customer.getCustomerId());

                if (accountActionDate != null) {
                    Date meetingDate = meetingDates.get(count).toDate();
                    ((SavingsScheduleEntity) accountActionDate)
                            .setActionDate(new java.sql.Date(meetingDate.getTime()));
                }
            }
            installmentId++;
        }
    }

    public Money getTotalPaymentDue(final Integer customerId) {
        return isMandatory() ? getTotalPaymentDueForManAccount(customerId)
                : getTotalPaymentDueForVolAccount(customerId);
    }

    public List<AccountActionDateEntity> getTotalInstallmentsDue(final Integer customerId) {
        return isMandatory() ? getInstallmentsDueForManAccount(customerId)
                : getInstallmentDueForVolAccount(customerId);
    }

    private List<AccountActionDateEntity> getInstallmentsDueForManAccount(final Integer customerId) {
        List<AccountActionDateEntity> dueInstallements = 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()
                        && accountAction.getCustomer().getCustomerId().equals(customerId)) {
                    dueInstallements.add(accountAction);
                }
            }
        }
        return dueInstallements;
    }

    private List<AccountActionDateEntity> getInstallmentDueForVolAccount(final Integer customerId) {
        List<AccountActionDateEntity> dueInstallments = new ArrayList<AccountActionDateEntity>();
        List<AccountActionDateEntity> installments = getInstallmentsDueForManAccount(customerId);
        if (installments != null && installments.size() > 0) {
            dueInstallments.add(installments.get(installments.size() - 1));
        }
        return dueInstallments;

    }

    private Money getTotalPaymentDueForManAccount(final Integer customerId) {
        List<AccountActionDateEntity> dueInstallments = getInstallmentsDueForManAccount(customerId);
        Money dueAmount = new Money(getCurrency());
        if (dueInstallments != null && dueInstallments.size() > 0) {
            for (AccountActionDateEntity installment : dueInstallments) {
                dueAmount = dueAmount.add(getDueAmount(installment));
            }
        }
        return dueAmount;
    }

    private Money getTotalPaymentDueForVolAccount(final Integer customerId) {
        List<AccountActionDateEntity> dueInstallments = getInstallmentDueForVolAccount(customerId);
        Money dueAmount = new Money(getCurrency());
        if (dueInstallments != null && dueInstallments.size() > 0) {
            dueAmount = getDueAmount(dueInstallments.get(0));
        }
        return dueAmount;
    }

    public void getSavingPerformanceHistory() throws AccountException {
        try {
            String systemDate = DateUtils.getCurrentDate();
            java.sql.Date currentDate = DateUtils.getLocaleDate(systemDate);
            getSavingsPerformance()
                    .addMissedDeposits(getSavingsPersistence().getMissedDeposits(getAccountId(), currentDate));
            getSavingsPerformance()
                    .addMissedDeposits(getSavingsPersistence().getMissedDepositsPaidAfterDueDate(getAccountId()));
        } catch (InvalidDateException ide) {
            throw new AccountException(ide);
        } catch (PersistenceException e) {
            throw new AccountException(e);
        }
    }

    public void generateNextSetOfMeetingDates(final List<Days> workingDays, final List<Holiday> holidays)
            throws AccountException {
        CustomerBO customerBO = getCustomer();
        if (customerBO.getCustomerMeeting() != null && customerBO.getCustomerMeeting().getMeeting() != null) {

            MeetingBO depositSchedule = customerBO.getCustomerMeeting().getMeeting();

            Date oldMeetingDate = depositSchedule.getStartDate();
            Short lastInstallmentId = getLastInstallmentId();
            AccountActionDateEntity lastInstallment = getAccountActionDate(lastInstallmentId);
            if (lastInstallment == null) { // a special workaround for MIFOS-5107
                lastInstallment = new SavingsScheduleEntity(this, this.getCustomer(), (short) 0,
                        new java.sql.Date(new LocalDate().minusDays(1).toDateMidnight().getMillis()),
                        PaymentStatus.UNPAID, new Money(Money.getDefaultCurrency(), 0.0));
            }
            depositSchedule.setMeetingStartDate(lastInstallment.getActionDate());

            if (customerBO.getCustomerLevel().getId().equals(CustomerLevel.CLIENT.getValue()) || customerBO
                    .getCustomerLevel().getId().equals(CustomerLevel.GROUP.getValue())
                    && getRecommendedAmntUnit().getId().equals(RecommendedAmountUnit.COMPLETE_GROUP.getValue())) {
                generateDepositAccountActions(customerBO, depositSchedule, lastInstallment, workingDays, holidays);
            } else {
                List<CustomerBO> children;
                try {
                    children = getCustomer().getChildren(CustomerLevel.CLIENT, ChildrenStateType.OTHER_THAN_CLOSED);
                } catch (CustomerException ce) {
                    throw new AccountException(ce);
                }
                for (CustomerBO customer : children) {
                    generateDepositAccountActions(customer, depositSchedule, lastInstallment, workingDays,
                            holidays);
                }
            }
            depositSchedule.setStartDate(oldMeetingDate);
        }
    }

    @Override
    public Date getNextMeetingDate() {
        AccountActionDateEntity nextAccountAction = getDetailsOfNextInstallment();

        return nextAccountAction != null ? nextAccountAction.getActionDate() : null;
    }

    @Override
    public boolean isTrxnDateValid(Date trxnDate, Date lastMeetingDate,
            boolean repaymentIndependentOfMeetingEnabled) {
        LocalDate transactionLocalDate = new LocalDate(trxnDate);
        LocalDate today = new LocalDate();

        if (AccountingRules.isBackDatedTxnAllowed()) {

            if (repaymentIndependentOfMeetingEnabled) {
                Date activationDate = this.getActivationDate();
                return trxnDate.compareTo(DateUtils.getDateWithoutTimeStamp(activationDate)) >= 0;
            }

            InterestScheduledEvent postingEvent = new SavingsInterestScheduledEventFactory()
                    .createScheduledEventFrom(this.getInterestPostingMeeting());
            LocalDate nextPostingDate = new LocalDate(this.nextIntPostDate);
            LocalDate currentPostingPeriodStartDate = postingEvent
                    .findFirstDateOfPeriodForMatchingDate(nextPostingDate);

            if (getInterestPostingMeeting().isDaily()) {
                if (lastIntPostDate == null) {
                    currentPostingPeriodStartDate = new LocalDate(activationDate);
                } else {
                    currentPostingPeriodStartDate = new LocalDate(lastIntPostDate).plusDays(1);
                }
            }

            // FIXME throw an exception with the correct reason instead of returning false
            if (transactionLocalDate.isBefore(currentPostingPeriodStartDate)) {
                return false;
            }

            LocalDate activationDate = new LocalDate(this.activationDate);

            if (lastMeetingDate != null) {
                LocalDate meetingDate = new LocalDate(lastMeetingDate);
                return (transactionLocalDate.isAfter(meetingDate) || transactionLocalDate.isEqual(meetingDate))
                        && (transactionLocalDate.isAfter(activationDate)
                                || transactionLocalDate.isEqual(activationDate));
            }

            return (transactionLocalDate.isAfter(activationDate) || transactionLocalDate.isEqual(activationDate))
                    && (transactionLocalDate.isBefore(today) || transactionLocalDate.isEqual(today));
        }
        return transactionLocalDate.isEqual(today);
    }

    public boolean isOfProductOffering(final SavingsOfferingBO productOffering) {
        return savingsOffering.equals(productOffering);
    }

    private boolean depositAmountIsInExcess(final Money amountRemaining) {
        return amountRemaining != null && amountRemaining.isGreaterThanZero();
    }

    private List<SavingsScheduleEntity> findAllUnpaidInstallmentsForPayingCustomerUpTo(final Date transactionDate,
            final Integer customerId) {

        final List<SavingsScheduleEntity> customerSchedulePayments = new ArrayList<SavingsScheduleEntity>();
        for (AccountActionDateEntity accountActionDateEntity : getAccountActionDates()) {
            if (accountActionDateEntity != null && !accountActionDateEntity.isPaid()
                    && !accountActionDateEntity.getActionDate().after(transactionDate)) {

                if (customerId == accountActionDateEntity.getCustomer().getCustomerId()) {
                    customerSchedulePayments.add((SavingsScheduleEntity) accountActionDateEntity);
                }
            }
        }

        return customerSchedulePayments;
    }

    /*
     * In order to do audit logging, we need to get the name of the PaymentTypeEntity. A new instance constructed with
     * the paymentTypeId is not good enough for this, we need to get the lookup value loaded so that we can resolve the
     * name of the PaymentTypeEntity.
     */
    private PaymentTypeEntity getPaymentTypeEntity(final short paymentTypeId) {
        return getSavingsPersistence().loadPersistentObject(PaymentTypeEntity.class, paymentTypeId);
    }

    @Override
    public MeetingBO getMeetingForAccount() {
        return getCustomer().getCustomerMeetingValue();
    }

    public void removeRecommendedAmountOnFutureInstallments() {
        Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
        if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
            for (AccountActionDateEntity accountAction : getAccountActionDates()) {
                if (accountAction.getActionDate().compareTo(currentDate) >= 0 && !accountAction.isPaid()) {
                    SavingsScheduleEntity savingsSchedule = (SavingsScheduleEntity) accountAction;
                    if (savingsSchedule.getDepositPaid().isGreaterThanZero()) {
                        savingsSchedule.setDeposit(savingsSchedule.getDepositPaid());
                        savingsSchedule.setPaymentStatus(PaymentStatus.PAID);
                    } else {
                        savingsSchedule.setDeposit(new Money(this.getCurrency()));
                    }
                }
            }
        }
    }

    public void resetRecommendedAmountOnFutureInstallments() {
        Date currentDate = DateUtils.getCurrentDateWithoutTimeStamp();
        if (getAccountActionDates() != null && getAccountActionDates().size() > 0) {
            for (AccountActionDateEntity accountAction : getAccountActionDates()) {
                if (accountAction.getActionDate().compareTo(currentDate) >= 0 && !accountAction.isPaid()) {
                    SavingsScheduleEntity savingsSchedule = (SavingsScheduleEntity) accountAction;
                    savingsSchedule.setDeposit(this.getRecommendedAmount());
                }
            }
        }
    }

    public MeetingBO getInterestCalculationMeeting() {
        return this.savingsOffering.getTimePerForInstcalc().getMeeting();
    }

    public MeetingBO getInterestPostingMeeting() {
        return this.savingsOffering.getFreqOfPostIntcalc().getMeeting();
    }

    public boolean isGroupModelWithIndividualAccountability() {

        return this.customer.isCenter() || (this.customer.isGroup() && this.recommendedAmntUnit.isPerIndividual());
    }

    public InterestCalcType getInterestCalcType() {
        return InterestCalcType.fromInt(this.savingsOffering.getInterestCalcType().getId());
    }

    public Double getInterestRate() {
        return this.savingsOffering.getInterestRate();
    }

    public Money getMinAmntForInt() {
        return this.savingsOffering.getMinAmntForInt();
    }

    public List<SavingsProductHistoricalInterestDetail> getHistoricalInterestDetailsForPeriod(
            CalendarPeriod period) {

        List<SavingsProductHistoricalInterestDetail> validHistoricalDetails = new ArrayList<SavingsProductHistoricalInterestDetail>();

        List<SavingsProductHistoricalInterestDetail> allHistoricalDetails = this.savingsOffering
                .getHistoricalInterestDetails();
        for (SavingsProductHistoricalInterestDetail interestDetail : allHistoricalDetails) {
            if (period.contains(interestDetail.getStartDate())) {
                validHistoricalDetails.add(interestDetail);
            }
        }

        return validHistoricalDetails;
    }

    private static Date startOfFiscalYear() {
        return new LocalDate().withMonthOfYear(1).withDayOfYear(1).toDateMidnight().toDate();
    }

    private void setSavingsPerformance(final SavingsPerformanceEntity savingsPerformance) {
        this.savingsPerformance = savingsPerformance;
    }

    private SavingsPerformanceEntity createSavingsPerformance() {
        SavingsPerformanceEntity savingsPerformance = new SavingsPerformanceEntity(this);
        logger.info("In SavingsBO::createSavingsPerformance(), SavingsPerformanceEntity created successfully ");
        return savingsPerformance;
    }

    public void generateSystemId(String officeGlobalNumber) {
        try {
            this.globalAccountNum = generateId(officeGlobalNumber);
        } catch (AccountException e) {
            throw new BusinessRuleException(e.getKey(), e);
        }
    }

    public SavingsAccountDetailDto toDto() {
        List<SavingsRecentActivityDto> recentActivity = this.getRecentAccountActivity(3);

        List<CustomerNoteDto> recentNoteDtos = new ArrayList<CustomerNoteDto>();
        List<AccountNotesEntity> recentNotes = this.getRecentAccountNotes();
        for (AccountNotesEntity accountNotesEntity : recentNotes) {
            recentNoteDtos.add(new CustomerNoteDto(accountNotesEntity.getCommentDate(),
                    accountNotesEntity.getComment(), accountNotesEntity.getPersonnelName()));
        }

        SavingsPerformanceHistoryDto savingsPerformanceHistoryDto = new SavingsPerformanceHistoryDto(
                getActivationDate(), savingsPerformance.getTotalDeposits().toString(),
                savingsPerformance.getTotalWithdrawals().toString(),
                savingsPerformance.getTotalInterestEarned().toString(),
                savingsPerformance.getMissedDeposits() != null ? savingsPerformance.getMissedDeposits().toString()
                        : "0");
        AccountActionDateEntity nextInstallment = getDetailsOfNextInstallment();

        return new SavingsAccountDetailDto(this.savingsOffering.toFullDto(), recentActivity, recentNoteDtos,
                this.recommendedAmount.toString(), this.globalAccountNum, getAccountId(), getState().getValue(),
                getState().name(), getSavingsBalance().toString(),
                nextInstallment != null ? nextInstallment.getActionDate() : null, getTotalAmountDue().toString(),
                getTotalAmountDueForNextInstallment().toString(), getTotalAmountInArrears().toString(),
                savingsPerformanceHistoryDto, this.savingsOffering.getSavingsTypeAsEnum().name(),
                this.customer.getCustomerId());
    }

    public void setSavingsTransactionActivityHelper(
            SavingsTransactionActivityHelper savingsTransactionActivityHelper) {
        this.savingsTransactionActivityHelper = savingsTransactionActivityHelper;
    }

    public void setSavingsPaymentStrategy(SavingsPaymentStrategy savingsPaymentStrategy) {
        this.savingsPaymentStrategy = savingsPaymentStrategy;
    }

    public List<AccountPaymentEntity> getInterestPostingPaymentsForRemoval(Date fromDate) {

        List<AccountPaymentEntity> paymentsForRemoval = new ArrayList<AccountPaymentEntity>();

        Iterator<AccountPaymentEntity> paymentIter = this.accountPayments.iterator();
        while (paymentIter.hasNext()) {
            AccountPaymentEntity payment = paymentIter.next();
            Date paymentDate = payment.getPaymentDate();
            if (payment.isSavingsInterestPosting() && DateUtils.dateFallsOnOrBeforeDate(fromDate, paymentDate)) {
                this.nextIntPostDate = paymentDate;
                this.savingsBalance = this.savingsBalance.subtract(payment.getAmount());
                this.savingsPerformance.substractFromTotalInterestDetails(payment.getAmount());

                paymentsForRemoval.add(payment);
                paymentIter.remove();
            }
        }
        return paymentsForRemoval;
    }

    public List<SavingsActivityEntity> getInterestPostingActivitesForRemoval(Date fromDate) {

        List<SavingsActivityEntity> activitesForRemoval = new ArrayList<SavingsActivityEntity>();

        Iterator<SavingsActivityEntity> activityIter = this.savingsActivityDetails.iterator();
        while (activityIter.hasNext()) {
            SavingsActivityEntity activity = activityIter.next();
            Date activityDate = activity.getTrxnCreatedDate();
            if (activity.getActivity().isSavingsInterestPosting()
                    && DateUtils.dateFallsOnOrBeforeDate(fromDate, activityDate)) {
                activitesForRemoval.add(activity);
                activityIter.remove();
            }
        }

        return activitesForRemoval;
    }

    public int countInterestPostingsForRecalculation(Date fromDate) {
        int postingsForRecalculation = 0;
        for (AccountPaymentEntity payment : this.accountPayments) {
            if (payment.isSavingsInterestPosting()
                    && DateUtils.dateFallsOnOrBeforeDate(fromDate, payment.getPaymentDate())) {
                postingsForRecalculation++;
            }
        }
        return postingsForRecalculation;
    }

}