org.mifos.application.servicefacade.LoanAccountServiceFacadeWebTier.java Source code

Java tutorial

Introduction

Here is the source code for org.mifos.application.servicefacade.LoanAccountServiceFacadeWebTier.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.application.servicefacade;

import static org.apache.commons.lang.StringUtils.EMPTY;
import static org.mifos.accounts.loan.util.helpers.LoanConstants.MIN_DAYS_BETWEEN_DISBURSAL_AND_FIRST_REPAYMENT_DAY;
import static org.mifos.framework.util.CollectionUtils.collect;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.mifos.accounts.api.AccountService;
import org.mifos.accounts.business.AccountActionDateEntity;
import org.mifos.accounts.business.AccountFeesActionDetailEntity;
import org.mifos.accounts.business.AccountFeesEntity;
import org.mifos.accounts.business.AccountFlagMapping;
import org.mifos.accounts.business.AccountNotesEntity;
import org.mifos.accounts.business.AccountOverpaymentEntity;
import org.mifos.accounts.business.AccountPaymentEntity;
import org.mifos.accounts.business.AccountStateEntity;
import org.mifos.accounts.business.AccountStateFlagEntity;
import org.mifos.accounts.business.AccountStateMachines;
import org.mifos.accounts.business.AccountTrxnEntity;
import org.mifos.accounts.business.AccountPenaltiesEntity;
import org.mifos.accounts.exceptions.AccountException;
import org.mifos.accounts.fees.business.AmountFeeBO;
import org.mifos.accounts.fees.business.FeeBO;
import org.mifos.accounts.fees.business.FeeFrequencyTypeEntity;
import org.mifos.accounts.fees.business.FeePaymentEntity;
import org.mifos.accounts.fees.persistence.FeeDao;
import org.mifos.accounts.fees.util.helpers.FeeFormula;
import org.mifos.accounts.fees.util.helpers.FeeFrequencyType;
import org.mifos.accounts.fees.util.helpers.FeePayment;
import org.mifos.accounts.fund.business.FundBO;
import org.mifos.accounts.fund.persistence.FundDao;
import org.mifos.accounts.fund.servicefacade.FundCodeDto;
import org.mifos.accounts.fund.servicefacade.FundDto;
import org.mifos.accounts.loan.business.LoanActivityEntity;
import org.mifos.accounts.loan.business.LoanBO;
import org.mifos.accounts.loan.business.LoanPerformanceHistoryEntity;
import org.mifos.accounts.loan.business.LoanScheduleEntity;
import org.mifos.accounts.loan.business.MaxMinLoanAmount;
import org.mifos.accounts.loan.business.MaxMinNoOfInstall;
import org.mifos.accounts.loan.business.RepaymentResultsHolder;
import org.mifos.accounts.loan.business.ScheduleCalculatorAdaptor;
import org.mifos.accounts.loan.business.service.LoanBusinessService;
import org.mifos.accounts.loan.business.service.validators.InstallmentValidationContext;
import org.mifos.accounts.loan.business.service.validators.InstallmentsValidator;
import org.mifos.accounts.loan.persistance.LoanDao;
import org.mifos.accounts.loan.struts.action.validate.ProductMixValidator;
import org.mifos.accounts.loan.util.helpers.LoanConstants;
import org.mifos.accounts.loan.util.helpers.MultipleLoanCreationDto;
import org.mifos.accounts.loan.util.helpers.RepaymentScheduleInstallment;
import org.mifos.accounts.penalties.business.AmountPenaltyBO;
import org.mifos.accounts.penalties.business.PenaltyBO;
import org.mifos.accounts.penalties.persistence.PenaltyDao;
import org.mifos.accounts.persistence.LegacyAccountDao;
import org.mifos.accounts.productdefinition.business.AmountRange;
import org.mifos.accounts.productdefinition.business.CashFlowDetail;
import org.mifos.accounts.productdefinition.business.GracePeriodTypeEntity;
import org.mifos.accounts.productdefinition.business.InstallmentRange;
import org.mifos.accounts.productdefinition.business.LoanAmountOption;
import org.mifos.accounts.productdefinition.business.LoanOfferingBO;
import org.mifos.accounts.productdefinition.business.LoanOfferingFundEntity;
import org.mifos.accounts.productdefinition.business.LoanOfferingInstallmentRange;
import org.mifos.accounts.productdefinition.business.VariableInstallmentDetailsBO;
import org.mifos.accounts.productdefinition.persistence.LoanProductDao;
import org.mifos.accounts.productdefinition.util.helpers.InterestType;
import org.mifos.accounts.servicefacade.UserContextFactory;
import org.mifos.accounts.util.helpers.AccountActionTypes;
import org.mifos.accounts.util.helpers.AccountConstants;
import org.mifos.accounts.util.helpers.AccountSearchResultsDto;
import org.mifos.accounts.util.helpers.AccountState;
import org.mifos.accounts.util.helpers.PaymentData;
import org.mifos.application.admin.servicefacade.HolidayServiceFacade;
import org.mifos.application.admin.servicefacade.MonthClosingServiceFacade;
import org.mifos.application.master.MessageLookup;
import org.mifos.application.master.business.CustomValueDto;
import org.mifos.application.master.business.CustomValueListElementDto;
import org.mifos.application.master.business.FundCodeEntity;
import org.mifos.application.master.business.InterestTypesEntity;
import org.mifos.application.master.business.MifosCurrency;
import org.mifos.application.master.business.PaymentTypeEntity;
import org.mifos.application.master.persistence.LegacyMasterDao;
import org.mifos.application.master.util.helpers.MasterConstants;
import org.mifos.application.master.util.helpers.PaymentTypes;
import org.mifos.application.meeting.business.MeetingBO;
import org.mifos.application.meeting.exceptions.MeetingException;
import org.mifos.application.meeting.util.helpers.MeetingHelper;
import org.mifos.application.meeting.util.helpers.MeetingType;
import org.mifos.application.meeting.util.helpers.RankOfDay;
import org.mifos.application.meeting.util.helpers.WeekDay;
import org.mifos.clientportfolio.loan.service.CreateLoanSchedule;
import org.mifos.clientportfolio.loan.service.MonthlyOnDayOfMonthSchedule;
import org.mifos.clientportfolio.loan.service.RecurringSchedule;
import org.mifos.clientportfolio.newloan.applicationservice.CreateGlimLoanAccount;
import org.mifos.clientportfolio.newloan.applicationservice.CreateLoanAccount;
import org.mifos.clientportfolio.newloan.applicationservice.GroupMemberAccountDto;
import org.mifos.clientportfolio.newloan.applicationservice.LoanAccountCashFlow;
import org.mifos.clientportfolio.newloan.applicationservice.LoanApplicationStateDto;
import org.mifos.clientportfolio.newloan.applicationservice.VariableInstallmentWithFeeValidationResult;
import org.mifos.clientportfolio.newloan.domain.CreationDetail;
import org.mifos.clientportfolio.newloan.domain.GroupMemberLoanDetail;
import org.mifos.clientportfolio.newloan.domain.LoanAccountDetail;
import org.mifos.clientportfolio.newloan.domain.LoanDisbursementDateFactory;
import org.mifos.clientportfolio.newloan.domain.LoanDisbursementDateFinder;
import org.mifos.clientportfolio.newloan.domain.LoanDisbursementDateValidator;
import org.mifos.clientportfolio.newloan.domain.LoanDisbursmentDateFactoryImpl;
import org.mifos.clientportfolio.newloan.domain.LoanProductOverridenDetail;
import org.mifos.clientportfolio.newloan.domain.LoanSchedule;
import org.mifos.clientportfolio.newloan.domain.LoanScheduleConfiguration;
import org.mifos.clientportfolio.newloan.domain.service.LoanScheduleService;
import org.mifos.config.AccountingRules;
import org.mifos.config.ClientRules;
import org.mifos.config.FiscalCalendarRules;
import org.mifos.config.ProcessFlowRules;
import org.mifos.config.business.service.ConfigurationBusinessService;
import org.mifos.config.exceptions.ConfigurationException;
import org.mifos.config.persistence.ConfigurationPersistence;
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.group.util.helpers.GroupConstants;
import org.mifos.customers.office.business.OfficeBO;
import org.mifos.customers.office.persistence.OfficeDao;
import org.mifos.customers.office.util.helpers.OfficeLevel;
import org.mifos.customers.persistence.CustomerDao;
import org.mifos.customers.persistence.CustomerPersistence;
import org.mifos.customers.personnel.business.PersonnelBO;
import org.mifos.customers.personnel.persistence.PersonnelDao;
import org.mifos.customers.personnel.util.helpers.PersonnelLevel;
import org.mifos.dto.domain.AccountFeeScheduleDto;
import org.mifos.dto.domain.AccountPaymentParametersDto;
import org.mifos.dto.domain.AccountStatusDto;
import org.mifos.dto.domain.AccountUpdateStatus;
import org.mifos.dto.domain.ApplicationConfigurationDto;
import org.mifos.dto.domain.CashFlowDto;
import org.mifos.dto.domain.CenterCreation;
import org.mifos.dto.domain.CreateAccountFeeDto;
import org.mifos.dto.domain.CreateAccountNote;
import org.mifos.dto.domain.CreateAccountPenaltyDto;
import org.mifos.dto.domain.CreateLoanRequest;
import org.mifos.dto.domain.CustomerDetailDto;
import org.mifos.dto.domain.CustomerDto;
import org.mifos.dto.domain.CustomerNoteDto;
import org.mifos.dto.domain.CustomerSearchDto;
import org.mifos.dto.domain.CustomerSearchResultDto;
import org.mifos.dto.domain.InstallmentDetailsDto;
import org.mifos.dto.domain.LoanAccountDetailsDto;
import org.mifos.dto.domain.LoanActivityDto;
import org.mifos.dto.domain.LoanCreationInstallmentDto;
import org.mifos.dto.domain.LoanInstallmentDetailsDto;
import org.mifos.dto.domain.LoanPaymentDto;
import org.mifos.dto.domain.LoanRepaymentScheduleItemDto;
import org.mifos.dto.domain.MeetingDto;
import org.mifos.dto.domain.MonthlyCashFlowDto;
import org.mifos.dto.domain.OfficeDetailsDto;
import org.mifos.dto.domain.OverpaymentDto;
import org.mifos.dto.domain.PaymentTypeDto;
import org.mifos.dto.domain.PenaltyDto;
import org.mifos.dto.domain.PersonnelDto;
import org.mifos.dto.domain.PrdOfferingDto;
import org.mifos.dto.domain.ProductDetailsDto;
import org.mifos.dto.domain.SurveyDto;
import org.mifos.dto.domain.ValueListElement;
import org.mifos.dto.screen.AccountFeesDto;
import org.mifos.dto.screen.AccountPenaltiesDto;
import org.mifos.dto.screen.CashFlowDataDto;
import org.mifos.dto.screen.ChangeAccountStatusDto;
import org.mifos.dto.screen.ExpectedPaymentDto;
import org.mifos.dto.screen.ListElement;
import org.mifos.dto.screen.LoanAccountDetailDto;
import org.mifos.dto.screen.LoanCreationGlimDto;
import org.mifos.dto.screen.LoanCreationLoanDetailsDto;
import org.mifos.dto.screen.LoanCreationPreviewDto;
import org.mifos.dto.screen.LoanCreationProductDetailsDto;
import org.mifos.dto.screen.LoanCreationResultDto;
import org.mifos.dto.screen.LoanDisbursalDto;
import org.mifos.dto.screen.LoanInformationDto;
import org.mifos.dto.screen.LoanInstallmentsDto;
import org.mifos.dto.screen.LoanPerformanceHistoryDto;
import org.mifos.dto.screen.LoanScheduleDto;
import org.mifos.dto.screen.LoanSummaryDto;
import org.mifos.dto.screen.MultipleLoanAccountDetailsDto;
import org.mifos.dto.screen.RepayLoanDto;
import org.mifos.dto.screen.RepayLoanInfoDto;
import org.mifos.framework.exceptions.HibernateSearchException;
import org.mifos.framework.exceptions.PageExpiredException;
import org.mifos.framework.exceptions.PersistenceException;
import org.mifos.framework.exceptions.PropertyNotFoundException;
import org.mifos.framework.exceptions.ServiceException;
import org.mifos.framework.exceptions.StatesInitializationException;
import org.mifos.framework.exceptions.SystemException;
import org.mifos.framework.hibernate.helper.HibernateTransactionHelper;
import org.mifos.framework.hibernate.helper.HibernateTransactionHelperForStaticHibernateUtil;
import org.mifos.framework.hibernate.helper.QueryResult;
import org.mifos.framework.util.DateTimeService;
import org.mifos.framework.util.helpers.Constants;
import org.mifos.framework.util.helpers.DateUtils;
import org.mifos.framework.util.helpers.Money;
import org.mifos.framework.util.helpers.SessionUtils;
import org.mifos.framework.util.helpers.Transformer;
import org.mifos.platform.cashflow.CashFlowConstants;
import org.mifos.platform.cashflow.CashFlowService;
import org.mifos.platform.cashflow.service.MonthlyCashFlowDetail;
import org.mifos.platform.questionnaire.service.QuestionGroupDetail;
import org.mifos.platform.questionnaire.service.QuestionGroupDetails;
import org.mifos.platform.questionnaire.service.QuestionnaireServiceFacade;
import org.mifos.platform.util.CollectionUtils;
import org.mifos.platform.validations.ErrorEntry;
import org.mifos.platform.validations.Errors;
import org.mifos.security.MifosUser;
import org.mifos.security.rolesandpermission.persistence.LegacyRolesPermissionsDao;
import org.mifos.security.util.ActivityContext;
import org.mifos.security.util.ActivityMapper;
import org.mifos.security.util.SecurityConstants;
import org.mifos.security.util.UserContext;
import org.mifos.service.BusinessRuleException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;

public class LoanAccountServiceFacadeWebTier implements LoanAccountServiceFacade {

    private final OfficeDao officeDao;
    private final LoanProductDao loanProductDao;
    private final CustomerDao customerDao;
    private final PersonnelDao personnelDao;
    private final FundDao fundDao;
    private final LoanDao loanDao;
    private final AccountService accountService;
    private final ScheduleCalculatorAdaptor scheduleCalculatorAdaptor;
    private final LoanBusinessService loanBusinessService;
    private final LoanScheduleService loanScheduleService;
    private final HibernateTransactionHelper transactionHelper;
    private final MonthClosingServiceFacade monthClosingServiceFacade;

    @Autowired
    private FeeDao feeDao;

    @Autowired
    private PenaltyDao penaltyDao;

    @Autowired
    private LegacyAccountDao legacyAccountDao;

    @Autowired
    private LegacyMasterDao legacyMasterDao;

    @Autowired
    private QuestionnaireServiceFacade questionnaireServiceFacade;

    @Autowired
    private LegacyRolesPermissionsDao legacyRolesPermissionsDao;

    @Autowired
    private CashFlowService cashFlowService;
    private final InstallmentsValidator installmentsValidator;
    private final HolidayServiceFacade holidayServiceFacade;

    @Autowired
    public LoanAccountServiceFacadeWebTier(OfficeDao officeDao, LoanProductDao loanProductDao,
            CustomerDao customerDao, PersonnelDao personnelDao, FundDao fundDao, LoanDao loanDao,
            AccountService accountService, ScheduleCalculatorAdaptor scheduleCalculatorAdaptor,
            LoanBusinessService loanBusinessService, LoanScheduleService loanScheduleService,
            InstallmentsValidator installmentsValidator, HolidayServiceFacade holidayServiceFacade,
            MonthClosingServiceFacade monthClosingServiceFacade) {
        this.officeDao = officeDao;
        this.loanProductDao = loanProductDao;
        this.customerDao = customerDao;
        this.personnelDao = personnelDao;
        this.fundDao = fundDao;
        this.loanDao = loanDao;
        this.accountService = accountService;
        this.scheduleCalculatorAdaptor = scheduleCalculatorAdaptor;
        this.loanBusinessService = loanBusinessService;
        this.loanScheduleService = loanScheduleService;
        this.installmentsValidator = installmentsValidator;
        this.holidayServiceFacade = holidayServiceFacade;
        this.transactionHelper = new HibernateTransactionHelperForStaticHibernateUtil();
        this.monthClosingServiceFacade = monthClosingServiceFacade;
    }

    @Override
    public AccountStatusDto retrieveAccountStatuses(Long loanAccountId) {

        LoanBO loanAccount = this.loanDao.findById(loanAccountId.intValue());

        try {
            List<ListElement> loanStatesList = new ArrayList<ListElement>();
            AccountStateMachines.getInstance().initializeLoanStates();

            List<AccountStateEntity> statusList = AccountStateMachines.getInstance()
                    .getLoanStatusList(loanAccount.getAccountState());
            for (AccountStateEntity accountState : statusList) {
                loanStatesList.add(new ListElement(accountState.getId().intValue(), accountState.getName()));
            }

            return new AccountStatusDto(loanStatesList);
        } catch (StatesInitializationException e) {
            throw new MifosRuntimeException(e);
        }
    }

    private UserContext toUserContext(MifosUser user) {
        return new UserContextFactory().create(user);
    }

    @Override
    public String updateLoanAccountStatus(AccountUpdateStatus updateStatus, Date transactionDate) {
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);
        PersonnelBO loggedInUser = this.personnelDao.findPersonnelById(userContext.getId());

        LoanBO loanAccount = this.loanDao.findById(updateStatus.getSavingsId().intValue());
        loanAccount.updateDetails(userContext);
        try {
            this.transactionHelper.startTransaction();
            this.transactionHelper.beginAuditLoggingFor(loanAccount);
            AccountState newStatus = AccountState.fromShort(updateStatus.getNewStatusId());

            loanAccount.changeStatus(newStatus, updateStatus.getFlagId(), updateStatus.getComment(), loggedInUser,
                    transactionDate);
            this.loanDao.save(loanAccount);
            this.transactionHelper.commitTransaction();
            return loanAccount.getGlobalAccountNum();
        } catch (BusinessRuleException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getMessageKey(), e);
        } catch (Exception e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } finally {
            this.transactionHelper.closeSession();
        }
    }

    @Override
    public LoanAccountDetailDto retrieveLoanAccountNotes(Long loanAccountId) {
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);

        LoanBO loanAccount = this.loanDao.findById(loanAccountId.intValue());
        loanAccount.updateDetails(userContext);
        return loanAccount.toDto();
    }

    @Override
    public void addNote(CreateAccountNote accountNote) {
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);

        PersonnelBO createdBy = this.personnelDao.findPersonnelById(accountNote.getCreatedById().shortValue());
        LoanBO loanAccount = this.loanDao.findById(accountNote.getAccountId());

        AccountNotesEntity accountNotes = new AccountNotesEntity(
                new java.sql.Date(accountNote.getCommentDate().toDateMidnight().toDate().getTime()),
                accountNote.getComment(), createdBy, loanAccount);

        try {
            this.transactionHelper.startTransaction();

            loanAccount.updateDetails(userContext);
            loanAccount.addAccountNotes(accountNotes);
            this.loanDao.save(loanAccount);
            this.transactionHelper.commitTransaction();
        } catch (Exception e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } finally {
            this.transactionHelper.closeSession();
        }
    }

    @Override
    public LoanCreationProductDetailsDto retrieveGetProductDetailsForLoanAccountCreation(final Integer customerId) {

        final CustomerBO customer = this.customerDao.findCustomerById(customerId);

        final CustomerDetailDto customerDetailDto = customer.toCustomerDetailDto();

        // FIXME - keithw - below code is not needed when jsp/struts is removed as this is worked out for enter loan account details step.
        final Date nextMeetingDate = customer.getCustomerAccount().getNextMeetingDate();
        final String recurMonth = customer.getCustomerMeeting().getMeeting().getMeetingDetails().getRecurAfter()
                .toString();
        final boolean isGroup = customer.isGroup();
        final boolean isGlimEnabled = new ConfigurationPersistence().isGlimEnabled();
        final boolean isLsimEnabled = new ConfigurationPersistence().isRepaymentIndepOfMeetingEnabled();

        List<PrdOfferingDto> loanProductDtos = retrieveActiveLoanProductsApplicableForCustomer(customer,
                isLsimEnabled);

        LoanCreationGlimDto loanCreationGlimDto = null;
        List<LoanAccountDetailsDto> clientDetails = new ArrayList<LoanAccountDetailsDto>();

        Errors errors = new Errors();
        if (isGroup && isGlimEnabled) {
            final List<ValueListElement> loanPurposes = loanProductDao.findAllLoanPurposes();
            final List<ClientBO> activeClientsOfGroup = customerDao.findActiveClientsUnderGroup(customer);
            loanCreationGlimDto = new LoanCreationGlimDto(loanPurposes);

            if (activeClientsOfGroup == null || activeClientsOfGroup.size() < 2) {
                String defaultMessage = "Group loan is not allowed as there must be at least two active clients within the group when Group loan with individual monitoring (GLIM) is enabled";
                ErrorEntry errorEntry = new ErrorEntry(
                        "createLoanAccount.glim.invalid.less.than.two.active.clients.in.group", "activeClients",
                        defaultMessage);
                errors.addErrors(Arrays.asList(errorEntry));
                loanProductDtos = new ArrayList<PrdOfferingDto>();
            } else {

                for (ClientBO client : activeClientsOfGroup) {
                    LoanAccountDetailsDto clientDetail = new LoanAccountDetailsDto();
                    clientDetail.setClientId(client.getCustomerId().toString());
                    clientDetail.setClientName(client.getDisplayName());
                    clientDetails.add(clientDetail);
                }
            }
        }

        return new LoanCreationProductDetailsDto(loanProductDtos, customerDetailDto, nextMeetingDate, recurMonth,
                isGroup, isGlimEnabled, loanCreationGlimDto, clientDetails, errors);
    }

    private List<PrdOfferingDto> retrieveActiveLoanProductsApplicableForCustomer(final CustomerBO customer,
            boolean lsimEnabled) {

        List<PrdOfferingDto> applicationLoanProductDtos = new ArrayList<PrdOfferingDto>();
        final List<LoanOfferingBO> applicableLoanProducts = new ArrayList<LoanOfferingBO>();

        final List<LoanOfferingBO> loanOfferings = loanProductDao
                .findActiveLoanProductsApplicableToCustomerLevel(customer.getCustomerLevel());

        if (!lsimEnabled) {
            final MeetingBO customerMeeting = customer.getCustomerMeetingValue();
            for (LoanOfferingBO loanProduct : loanOfferings) {
                if (loanProduct.getLoanOfferingMeetingValue().hasSameRecurrenceAs(customerMeeting)
                        && customerMeeting.recursOnMultipleOf(loanProduct.getLoanOfferingMeetingValue())
                        && !loanProduct.isVariableInstallmentsAllowed()) {
                    applicableLoanProducts.add(loanProduct);
                }
            }

            for (LoanOfferingBO loanProduct : applicableLoanProducts) {
                applicationLoanProductDtos.add(loanProduct.toDto());
            }
        } else {
            for (LoanOfferingBO loanProduct : loanOfferings) {
                applicationLoanProductDtos.add(loanProduct.toDto());
            }
        }

        return applicationLoanProductDtos;
    }

    @Override
    public LoanCreationLoanDetailsDto retrieveLoanDetailsForLoanAccountCreation(Integer customerId, Short productId,
            boolean isLoanWithBackdatedPayments) {

        try {
            List<org.mifos.dto.domain.FeeDto> additionalFees = new ArrayList<org.mifos.dto.domain.FeeDto>();
            List<org.mifos.dto.domain.FeeDto> defaultFees = new ArrayList<org.mifos.dto.domain.FeeDto>();
            List<PenaltyDto> defaultPenalties = new ArrayList<PenaltyDto>();

            LoanOfferingBO loanProduct = this.loanProductDao.findById(productId.intValue());

            MeetingBO loanProductMeeting = loanProduct.getLoanOfferingMeetingValue();
            MeetingDto loanOfferingMeetingDto = loanProductMeeting.toDto();

            List<FeeBO> fees = this.feeDao.getAllAppllicableFeeForLoanCreation();

            for (FeeBO fee : fees) {
                if (!fee.isPeriodic() || (MeetingBO.isMeetingMatched(fee.getFeeFrequency().getFeeMeetingFrequency(),
                        loanProductMeeting))) {

                    org.mifos.dto.domain.FeeDto feeDto = fee.toDto();

                    FeeFrequencyType feeFrequencyType = FeeFrequencyType
                            .getFeeFrequencyType(fee.getFeeFrequency().getFeeFrequencyType().getId());

                    FeeFrequencyTypeEntity feeFrequencyEntity = this.loanProductDao
                            .retrieveFeeFrequencyType(feeFrequencyType);
                    String feeFrequencyTypeName = ApplicationContextProvider.getBean(MessageLookup.class)
                            .lookup(feeFrequencyEntity.getLookUpValue());
                    feeDto.setFeeFrequencyType(feeFrequencyTypeName);

                    if (feeDto.getFeeFrequency().isOneTime()) {
                        FeePayment feePayment = FeePayment
                                .getFeePayment(fee.getFeeFrequency().getFeePayment().getId());
                        FeePaymentEntity feePaymentEntity = this.loanProductDao.retrieveFeePaymentType(feePayment);
                        String feePaymentName = ApplicationContextProvider.getBean(MessageLookup.class)
                                .lookup(feePaymentEntity.getLookUpValue());
                        feeDto.getFeeFrequency().setPayment(feePaymentName);
                    }

                    if (loanProduct.isFeePresent(fee)) {
                        defaultFees.add(feeDto);
                    } else {
                        additionalFees.add(feeDto);
                    }
                }
            }

            List<PenaltyBO> penalties = this.penaltyDao.getAllAppllicablePenaltyForLoanCreation();

            for (PenaltyBO penalty : penalties) {
                if (loanProduct.isPenaltyPresent(penalty)) {
                    defaultPenalties.add(penalty.toDto());
                }
            }

            if (AccountingRules.isMultiCurrencyEnabled()) {
                defaultFees = getFilteredFeesByCurrency(defaultFees, loanProduct.getCurrency().getCurrencyId());
                additionalFees = getFilteredFeesByCurrency(additionalFees,
                        loanProduct.getCurrency().getCurrencyId());
            }

            Map<String, String> defaultFeeOptions = new LinkedHashMap<String, String>();
            for (org.mifos.dto.domain.FeeDto feeDto : defaultFees) {
                defaultFeeOptions.put(feeDto.getId(), feeDto.getName());
            }

            Map<String, String> additionalFeeOptions = new LinkedHashMap<String, String>();
            for (org.mifos.dto.domain.FeeDto feeDto : additionalFees) {
                additionalFeeOptions.put(feeDto.getId(), feeDto.getName());
            }

            CustomerBO customer = this.customerDao.findCustomerById(customerId);
            boolean isRepaymentIndependentOfMeetingEnabled = new ConfigurationBusinessService()
                    .isRepaymentIndepOfMeetingEnabled();

            LoanDisbursementDateFactory loanDisbursementDateFactory = new LoanDisbursmentDateFactoryImpl();
            LoanDisbursementDateFinder loanDisbursementDateFinder = loanDisbursementDateFactory.create(customer,
                    loanProduct, isRepaymentIndependentOfMeetingEnabled, isLoanWithBackdatedPayments);
            LocalDate nextPossibleDisbursementDate = loanDisbursementDateFinder
                    .findClosestMatchingDateFromAndInclusiveOf(new LocalDate());

            LoanAmountOption eligibleLoanAmount = loanProduct.eligibleLoanAmount(
                    customer.getMaxLoanAmount(loanProduct), customer.getMaxLoanCycleForProduct(loanProduct));
            LoanOfferingInstallmentRange eligibleNoOfInstall = loanProduct.eligibleNoOfInstall(
                    customer.getMaxLoanAmount(loanProduct), customer.getMaxLoanCycleForProduct(loanProduct));

            Double defaultInterestRate = loanProduct.getDefInterestRate();
            Double maxInterestRate = loanProduct.getMaxInterestRate();
            Double minInterestRate = loanProduct.getMinInterestRate();

            LinkedHashMap<String, String> collateralOptions = new LinkedHashMap<String, String>();
            LinkedHashMap<String, String> purposeOfLoanOptions = new LinkedHashMap<String, String>();

            CustomValueDto customValueDto = legacyMasterDao.getLookUpEntity(MasterConstants.COLLATERAL_TYPES);
            List<CustomValueListElementDto> collateralTypes = customValueDto.getCustomValueListElements();
            for (CustomValueListElementDto element : collateralTypes) {
                collateralOptions.put(element.getId().toString(), element.getName());
            }

            // Business activities got in getPrdOfferings also but only for glim.
            List<ValueListElement> loanPurposes = legacyMasterDao
                    .findValueListElements(MasterConstants.LOAN_PURPOSES);
            for (ValueListElement element : loanPurposes) {
                purposeOfLoanOptions.put(element.getId().toString(), element.getName());
            }

            List<FundDto> fundDtos = new ArrayList<FundDto>();
            List<FundBO> funds = getFunds(loanProduct);
            for (FundBO fund : funds) {
                FundDto fundDto = new FundDto();
                fundDto.setId(Short.toString(fund.getFundId()));
                fundDto.setCode(translateFundCodeToDto(fund.getFundCode()));
                fundDto.setName(fund.getFundName());

                fundDtos.add(fundDto);
            }

            ProductDetailsDto productDto = loanProduct.toDetailsDto();
            CustomerDetailDto customerDetailDto = customer.toCustomerDetailDto();

            Integer gracePeriodInInstallments = loanProduct.getGracePeriodDuration().intValue();

            final List<PrdOfferingDto> loanProductDtos = retrieveActiveLoanProductsApplicableForCustomer(customer,
                    isRepaymentIndependentOfMeetingEnabled);

            InterestType interestType = InterestType.fromInt(loanProduct.getInterestTypes().getId().intValue());
            InterestTypesEntity productInterestType = this.loanProductDao.findInterestType(interestType);
            String interestTypeName = ApplicationContextProvider.getBean(MessageLookup.class)
                    .lookup(productInterestType.getLookUpValue());

            LinkedHashMap<String, String> daysOfTheWeekOptions = new LinkedHashMap<String, String>();
            List<WeekDay> workingDays = new FiscalCalendarRules().getWorkingDays();
            for (WeekDay workDay : workingDays) {
                String weekdayName = ApplicationContextProvider.getBean(MessageLookup.class)
                        .lookup(workDay.getPropertiesKey());
                workDay.setWeekdayName(weekdayName);
                daysOfTheWeekOptions.put(workDay.getValue().toString(), weekdayName);
            }

            LinkedHashMap<String, String> weeksOfTheMonthOptions = new LinkedHashMap<String, String>();
            for (RankOfDay weekOfMonth : RankOfDay.values()) {
                String weekOfMonthName = ApplicationContextProvider.getBean(MessageLookup.class)
                        .lookup(weekOfMonth.getPropertiesKey());
                weeksOfTheMonthOptions.put(weekOfMonth.getValue().toString(), weekOfMonthName);
            }

            boolean variableInstallmentsAllowed = loanProduct.isVariableInstallmentsAllowed();
            boolean fixedRepaymentSchedule = loanProduct.isFixedRepaymentSchedule();
            Integer minGapInDays = Integer.valueOf(0);
            Integer maxGapInDays = Integer.valueOf(0);
            BigDecimal minInstallmentAmount = BigDecimal.ZERO;
            if (variableInstallmentsAllowed) {
                VariableInstallmentDetailsBO variableInstallmentsDetails = loanProduct
                        .getVariableInstallmentDetails();
                minGapInDays = variableInstallmentsDetails.getMinGapInDays();
                maxGapInDays = variableInstallmentsDetails.getMaxGapInDays();
                minInstallmentAmount = variableInstallmentsDetails.getMinInstallmentAmount().getAmount();
            }

            boolean compareCashflowEnabled = loanProduct.isCashFlowCheckEnabled();

            // GLIM specific
            final boolean isGroup = customer.isGroup();
            final boolean isGlimEnabled = new ConfigurationPersistence().isGlimEnabled();

            List<LoanAccountDetailsDto> clientDetails = new ArrayList<LoanAccountDetailsDto>();

            if (isGroup && isGlimEnabled) {
                final List<ClientBO> activeClientsOfGroup = customerDao.findActiveClientsUnderGroup(customer);

                if (activeClientsOfGroup == null || activeClientsOfGroup.isEmpty()) {
                    throw new BusinessRuleException(GroupConstants.IMPOSSIBLE_TO_CREATE_GROUP_LOAN);
                }

                for (ClientBO client : activeClientsOfGroup) {
                    LoanAccountDetailsDto clientDetail = new LoanAccountDetailsDto();
                    clientDetail.setClientId(client.getGlobalCustNum());
                    clientDetail.setClientName(client.getDisplayName());
                    clientDetails.add(clientDetail);
                }
            }
            // end of GLIM specific

            int digitsAfterDecimalForInterest = AccountingRules.getDigitsAfterDecimalForInterest().intValue();
            int digitsBeforeDecimalForInterest = AccountingRules.getDigitsBeforeDecimalForInterest().intValue();
            int digitsAfterDecimalForMonetaryAmounts = AccountingRules.getDigitsAfterDecimal().intValue();
            int digitsBeforeDecimalForMonetaryAmounts = AccountingRules.getDigitsBeforeDecimal().intValue();

            ApplicationConfigurationDto appConfig = new ApplicationConfigurationDto(digitsAfterDecimalForInterest,
                    digitsBeforeDecimalForInterest, digitsAfterDecimalForMonetaryAmounts,
                    digitsBeforeDecimalForMonetaryAmounts);

            Map<String, String> disbursalPaymentTypes = new LinkedHashMap<String, String>();
            try {
                for (PaymentTypeDto paymentTypeDto : accountService.getLoanDisbursementTypes()) {
                    disbursalPaymentTypes.put(paymentTypeDto.getValue().toString(), paymentTypeDto.getName());
                }
            } catch (Exception e) {
                throw new SystemException(e);
            }
            Map<String, String> repaymentpaymentTypes = new LinkedHashMap<String, String>();
            try {
                for (PaymentTypeDto paymentTypeDto : accountService.getLoanPaymentTypes()) {
                    repaymentpaymentTypes.put(paymentTypeDto.getValue().toString(), paymentTypeDto.getName());
                }
            } catch (Exception e) {
                throw new SystemException(e);
            }

            return new LoanCreationLoanDetailsDto(isRepaymentIndependentOfMeetingEnabled, loanOfferingMeetingDto,
                    customer.getCustomerMeetingValue().toDto(), loanPurposes, productDto, gracePeriodInInstallments,
                    customerDetailDto, loanProductDtos, interestTypeName, fundDtos, collateralOptions,
                    purposeOfLoanOptions, defaultFeeOptions, additionalFeeOptions, defaultFees, additionalFees,
                    BigDecimal.valueOf(eligibleLoanAmount.getDefaultLoanAmount()),
                    BigDecimal.valueOf(eligibleLoanAmount.getMaxLoanAmount()),
                    BigDecimal.valueOf(eligibleLoanAmount.getMinLoanAmount()), defaultInterestRate, maxInterestRate,
                    minInterestRate, eligibleNoOfInstall.getDefaultNoOfInstall().intValue(),
                    eligibleNoOfInstall.getMaxNoOfInstall().intValue(),
                    eligibleNoOfInstall.getMinNoOfInstall().intValue(), nextPossibleDisbursementDate,
                    daysOfTheWeekOptions, weeksOfTheMonthOptions, variableInstallmentsAllowed,
                    fixedRepaymentSchedule, minGapInDays, maxGapInDays, minInstallmentAmount,
                    compareCashflowEnabled, isGlimEnabled, isGroup, clientDetails, appConfig, defaultPenalties,
                    disbursalPaymentTypes, repaymentpaymentTypes);

        } catch (SystemException e) {
            throw new MifosRuntimeException(e);
        }
    }

    private FundCodeDto translateFundCodeToDto(FundCodeEntity fundCode) {
        FundCodeDto fundCodeDto = new FundCodeDto();
        fundCodeDto.setId(Short.toString(fundCode.getFundCodeId()));
        fundCodeDto.setValue(fundCode.getFundCodeValue());
        return fundCodeDto;
    }

    private List<FundBO> getFunds(final LoanOfferingBO loanOffering) {
        List<FundBO> funds = new ArrayList<FundBO>();
        if (loanOffering.getLoanOfferingFunds() != null && loanOffering.getLoanOfferingFunds().size() > 0) {
            for (LoanOfferingFundEntity loanOfferingFund : loanOffering.getLoanOfferingFunds()) {
                funds.add(loanOfferingFund.getFund());
            }
        }
        return funds;
    }

    private List<org.mifos.dto.domain.FeeDto> getFilteredFeesByCurrency(
            List<org.mifos.dto.domain.FeeDto> defaultFees, Short currencyId) {
        List<org.mifos.dto.domain.FeeDto> filteredFees = new ArrayList<org.mifos.dto.domain.FeeDto>();
        for (org.mifos.dto.domain.FeeDto feeDto : defaultFees) {
            if (feeDto.isValidForCurrency(currencyId.intValue())) {
                filteredFees.add(feeDto);
            }
        }
        return filteredFees;
    }

    @Override
    public LoanCreationPreviewDto previewLoanCreationDetails(Integer customerId,
            List<LoanAccountDetailsDto> accountDetails, List<String> selectedClientIds) {

        CustomerBO customer = this.customerDao.findCustomerById(customerId);
        final boolean isGroup = customer.isGroup();
        final boolean isGlimEnabled = new ConfigurationPersistence().isGlimEnabled();

        List<LoanAccountDetailsDto> loanAccountDetailsView = new ArrayList<LoanAccountDetailsDto>();

        for (String clientIdAsString : selectedClientIds) {
            if (StringUtils.isNotEmpty(clientIdAsString)) {

                LoanAccountDetailsDto tempLoanAccount = new LoanAccountDetailsDto();
                ClientBO client = (ClientBO) this.customerDao.findCustomerById(Integer.valueOf(clientIdAsString));

                LoanAccountDetailsDto account = null;
                for (LoanAccountDetailsDto tempAccount : accountDetails) {
                    if (tempAccount.getClientId().equals(clientIdAsString)) {
                        account = tempAccount;
                    }
                }
                tempLoanAccount.setClientId(client.getGlobalCustNum().toString());
                tempLoanAccount.setClientName(client.getDisplayName());
                tempLoanAccount.setLoanAmount(
                        (null != account.getLoanAmount() && !EMPTY.equals(account.getLoanAmount().toString())
                                ? account.getLoanAmount()
                                : "0.0"));

                tempLoanAccount.setBusinessActivity(account.getBusinessActivity());
                tempLoanAccount.setGovermentId(
                        (StringUtils.isNotBlank(client.getGovernmentId()) ? client.getGovernmentId() : "-")
                                .toString());

                loanAccountDetailsView.add(tempLoanAccount);
            }
        }

        return new LoanCreationPreviewDto(isGlimEnabled, isGroup, loanAccountDetailsView);
    }

    @Override
    public LoanScheduleDto createLoanSchedule(CreateLoanSchedule createLoanSchedule,
            List<DateTime> loanScheduleDates, List<Number> totalInstallmentAmounts) {

        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);

        // assemble into domain entities
        LoanOfferingBO loanProduct = this.loanProductDao.findById(createLoanSchedule.getProductId());
        CustomerBO customer = this.customerDao.findCustomerById(createLoanSchedule.getCustomerId());

        Money loanAmountDisbursed = new Money(loanProduct.getCurrency(), createLoanSchedule.getLoanAmount());

        List<AccountFeesEntity> accountFeeEntities = assembleAccountFees(
                createLoanSchedule.getAccountFeeEntities());
        LoanProductOverridenDetail overridenDetail = new LoanProductOverridenDetail(loanAmountDisbursed,
                createLoanSchedule.getDisbursementDate(), createLoanSchedule.getInterestRate(),
                createLoanSchedule.getNumberOfInstallments(), createLoanSchedule.getGraceDuration(),
                accountFeeEntities, new ArrayList<AccountPenaltiesEntity>());

        Integer interestDays = Integer.valueOf(AccountingRules.getNumberOfInterestDays().intValue());
        boolean loanScheduleIndependentOfCustomerMeetingEnabled = createLoanSchedule
                .isRepaymentIndependentOfCustomerMeetingSchedule();

        MeetingBO loanMeeting = customer.getCustomerMeetingValue();
        if (loanScheduleIndependentOfCustomerMeetingEnabled) {
            loanMeeting = this.createNewMeetingForRepaymentDay(createLoanSchedule.getDisbursementDate(),
                    createLoanSchedule, customer);

            if (loanProduct.isVariableInstallmentsAllowed()) {
                loanMeeting.setMeetingStartDate(createLoanSchedule.getDisbursementDate().toDateMidnight().toDate());
            }
        }
        LoanScheduleConfiguration configuration = new LoanScheduleConfiguration(
                loanScheduleIndependentOfCustomerMeetingEnabled, interestDays);

        LoanSchedule loanSchedule = this.loanScheduleService.generate(loanProduct, customer, loanMeeting,
                overridenDetail, configuration, accountFeeEntities, createLoanSchedule.getDisbursementDate(),
                loanScheduleDates, totalInstallmentAmounts);

        // translate to DTO form
        List<LoanCreationInstallmentDto> installments = new ArrayList<LoanCreationInstallmentDto>();
        Short digitsAfterDecimal = AccountingRules.getDigitsAfterDecimal();
        for (LoanScheduleEntity loanScheduleEntity : loanSchedule.getRoundedLoanSchedules()) {
            Integer installmentNumber = loanScheduleEntity.getInstallmentId().intValue();
            LocalDate dueDate = new LocalDate(loanScheduleEntity.getActionDate());
            String principal = loanScheduleEntity.getPrincipal().toString(digitsAfterDecimal);
            String interest = loanScheduleEntity.getInterest().toString(digitsAfterDecimal);
            String fees = loanScheduleEntity.getTotalFees().toString(digitsAfterDecimal);
            String penalty = "0.0";
            String total = loanScheduleEntity.getPrincipal().add(loanScheduleEntity.getInterest())
                    .add(loanScheduleEntity.getTotalFees()).toString(digitsAfterDecimal);
            LoanCreationInstallmentDto installment = new LoanCreationInstallmentDto(installmentNumber, dueDate,
                    Double.valueOf(principal), Double.valueOf(interest), Double.valueOf(fees),
                    Double.valueOf(penalty), Double.valueOf(total));
            installments.add(installment);
        }

        return new LoanScheduleDto(customer.getDisplayName(),
                Double.valueOf(createLoanSchedule.getLoanAmount().doubleValue()),
                createLoanSchedule.getDisbursementDate(), loanProduct.getGraceType().getValue().intValue(),
                installments);
    }

    @Override
    public LoanScheduleDto createLoanSchedule(CreateLoanSchedule createLoanSchedule) {

        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);

        // assemble into domain entities
        LoanOfferingBO loanProduct = this.loanProductDao.findById(createLoanSchedule.getProductId());
        CustomerBO customer = this.customerDao.findCustomerById(createLoanSchedule.getCustomerId());

        Money loanAmountDisbursed = new Money(loanProduct.getCurrency(), createLoanSchedule.getLoanAmount());

        List<AccountFeesEntity> accountFeeEntities = assembleAccountFees(
                createLoanSchedule.getAccountFeeEntities());
        LoanProductOverridenDetail overridenDetail = new LoanProductOverridenDetail(loanAmountDisbursed,
                createLoanSchedule.getDisbursementDate(), createLoanSchedule.getInterestRate(),
                createLoanSchedule.getNumberOfInstallments(), createLoanSchedule.getGraceDuration(),
                accountFeeEntities, new ArrayList<AccountPenaltiesEntity>());

        Integer interestDays = Integer.valueOf(AccountingRules.getNumberOfInterestDays().intValue());
        boolean loanScheduleIndependentOfCustomerMeetingEnabled = createLoanSchedule
                .isRepaymentIndependentOfCustomerMeetingSchedule();

        MeetingBO loanMeeting = customer.getCustomerMeetingValue();
        if (loanScheduleIndependentOfCustomerMeetingEnabled) {
            loanMeeting = this.createNewMeetingForRepaymentDay(createLoanSchedule.getDisbursementDate(),
                    createLoanSchedule, customer);

            if (loanProduct.isVariableInstallmentsAllowed()) {
                loanMeeting.setMeetingStartDate(createLoanSchedule.getDisbursementDate().toDateMidnight().toDate());
            }
        }
        LoanScheduleConfiguration configuration = new LoanScheduleConfiguration(
                loanScheduleIndependentOfCustomerMeetingEnabled, interestDays);

        LoanSchedule loanSchedule = this.loanScheduleService.generate(loanProduct, customer, loanMeeting,
                overridenDetail, configuration, userContext.getBranchId(), accountFeeEntities,
                createLoanSchedule.getDisbursementDate());

        // translate to DTO form
        List<LoanCreationInstallmentDto> installments = new ArrayList<LoanCreationInstallmentDto>();
        Short digitsAfterDecimal = AccountingRules.getDigitsAfterDecimal();
        for (LoanScheduleEntity loanScheduleEntity : loanSchedule.getRoundedLoanSchedules()) {
            Integer installmentNumber = loanScheduleEntity.getInstallmentId().intValue();
            LocalDate dueDate = new LocalDate(loanScheduleEntity.getActionDate());
            String principal = loanScheduleEntity.getPrincipal().toString(digitsAfterDecimal);
            String interest = loanScheduleEntity.getInterest().toString(digitsAfterDecimal);
            String fees = loanScheduleEntity.getTotalFees().toString(digitsAfterDecimal);
            String penalty = "0.0";
            String total = loanScheduleEntity.getPrincipal().add(loanScheduleEntity.getInterest())
                    .add(loanScheduleEntity.getTotalFees()).toString(digitsAfterDecimal);
            LoanCreationInstallmentDto installment = new LoanCreationInstallmentDto(installmentNumber, dueDate,
                    Double.valueOf(principal), Double.valueOf(interest), Double.valueOf(fees),
                    Double.valueOf(penalty), Double.valueOf(total));
            installments.add(installment);
        }

        return new LoanScheduleDto(customer.getDisplayName(),
                Double.valueOf(createLoanSchedule.getLoanAmount().doubleValue()),
                createLoanSchedule.getDisbursementDate(), loanProduct.getGraceType().getValue().intValue(),
                installments);
    }

    private LoanAccountDetail assembleLoanAccountDetail(CreateLoanAccount loanAccountInfo) {

        CustomerBO customer = this.customerDao.findCustomerById(loanAccountInfo.getCustomerId());
        LoanOfferingBO loanProduct = this.loanProductDao.findById(loanAccountInfo.getProductId());

        Money loanAmount = new Money(loanProduct.getCurrency(), loanAccountInfo.getLoanAmount());
        AccountState accountStateType = AccountState.fromShort(loanAccountInfo.getAccountState().shortValue());
        FundBO fund = null;
        if (loanAccountInfo.getSourceOfFundId() != null) {
            fund = this.fundDao.findById(loanAccountInfo.getSourceOfFundId().shortValue());
        }

        return new LoanAccountDetail(customer, loanProduct, loanAmount, accountStateType, fund);
    }

    private LoanSchedule assembleLoanSchedule(CustomerBO customer, LoanOfferingBO loanProduct,
            LoanProductOverridenDetail overridenDetail, LoanScheduleConfiguration configuration,
            MeetingBO repaymentDayMeeting, OfficeBO userOffice, List<DateTime> loanScheduleDates,
            LocalDate disbursementDate, List<Number> totalInstallmentAmounts) {

        LoanSchedule loanSchedule = null;
        if (loanScheduleDates.isEmpty() && totalInstallmentAmounts.isEmpty()) {
            loanSchedule = this.loanScheduleService.generate(loanProduct, customer, repaymentDayMeeting,
                    overridenDetail, configuration, userOffice.getOfficeId(),
                    overridenDetail.getAccountFeeEntities(), disbursementDate);
        } else {
            loanSchedule = this.loanScheduleService.generate(loanProduct, customer, repaymentDayMeeting,
                    overridenDetail, configuration, overridenDetail.getAccountFeeEntities(), disbursementDate,
                    loanScheduleDates, totalInstallmentAmounts);
        }

        return loanSchedule;
    }

    @Override
    public LoanCreationResultDto createLoan(CreateLoanAccount loanAccountInfo,
            List<QuestionGroupDetail> questionGroups, LoanAccountCashFlow loanAccountCashFlow) {

        return createLoanAccount(loanAccountInfo, new ArrayList<LoanPaymentDto>(), questionGroups,
                loanAccountCashFlow, new ArrayList<DateTime>(), new ArrayList<Number>(),
                new ArrayList<GroupMemberAccountDto>(), false);
    }

    @Override
    public LoanCreationResultDto createLoan(CreateLoanAccount loanAccountInfo,
            List<QuestionGroupDetail> questionGroups, LoanAccountCashFlow loanAccountCashFlow,
            List<DateTime> loanScheduleInstallmentDates, List<Number> totalInstallmentAmounts) {

        return createLoanAccount(loanAccountInfo, new ArrayList<LoanPaymentDto>(), questionGroups,
                loanAccountCashFlow, loanScheduleInstallmentDates, totalInstallmentAmounts,
                new ArrayList<GroupMemberAccountDto>(), false);
    }

    @Override
    public LoanCreationResultDto createGroupLoanWithIndividualMonitoring(CreateGlimLoanAccount glimLoanAccount,
            List<QuestionGroupDetail> questionGroups, LoanAccountCashFlow loanAccountCashFlow) {

        return createLoanAccount(glimLoanAccount.getGroupLoanAccountDetails(), new ArrayList<LoanPaymentDto>(),
                questionGroups, loanAccountCashFlow, new ArrayList<DateTime>(), new ArrayList<Number>(),
                glimLoanAccount.getMemberDetails(), false);
    }

    @Override
    public LoanCreationResultDto createBackdatedGroupLoanWithIndividualMonitoring(
            CreateGlimLoanAccount glimLoanAccount, List<LoanPaymentDto> backdatedLoanPayments,
            List<QuestionGroupDetail> questionGroups, LoanAccountCashFlow loanAccountCashFlow) {

        CreateLoanAccount loanAccountInfo = glimLoanAccount.getGroupLoanAccountDetails();

        return createLoanAccount(loanAccountInfo, backdatedLoanPayments, questionGroups, loanAccountCashFlow,
                new ArrayList<DateTime>(), new ArrayList<Number>(), glimLoanAccount.getMemberDetails(), true);
    }

    @Override
    public LoanCreationResultDto createBackdatedLoan(CreateLoanAccount loanAccountInfo,
            List<LoanPaymentDto> backdatedLoanPayments, List<QuestionGroupDetail> questionGroups,
            LoanAccountCashFlow loanAccountCashFlow, List<DateTime> loanScheduleInstallmentDates,
            List<Number> installmentPrincipalAmounts) {
        return createLoanAccount(loanAccountInfo, backdatedLoanPayments, questionGroups, loanAccountCashFlow,
                loanScheduleInstallmentDates, installmentPrincipalAmounts, new ArrayList<GroupMemberAccountDto>(),
                true);
    }

    @Override
    public LoanCreationResultDto createBackdatedLoan(CreateLoanAccount loanAccountInfo,
            List<LoanPaymentDto> backdatedLoanPayments, List<QuestionGroupDetail> questionGroups,
            LoanAccountCashFlow loanAccountCashFlow) {
        return createLoanAccount(loanAccountInfo, backdatedLoanPayments, questionGroups, loanAccountCashFlow,
                new ArrayList<DateTime>(), new ArrayList<Number>(), new ArrayList<GroupMemberAccountDto>(), true);
    }

    private LoanCreationResultDto createLoanAccount(CreateLoanAccount loanAccountInfo,
            List<LoanPaymentDto> backdatedLoanPayments, List<QuestionGroupDetail> questionGroups,
            LoanAccountCashFlow loanAccountCashFlow, List<DateTime> loanScheduleInstallmentDates,
            List<Number> totalInstallmentAmounts, List<GroupMemberAccountDto> memberDetails,
            boolean isBackdatedLoan) {

        DateTime creationDate = new DateTime();

        // 0. verify member details for GLIM group accounts
        for (GroupMemberAccountDto groupMemberAccount : memberDetails) {
            ClientBO member = this.customerDao.findClientBySystemId(groupMemberAccount.getGlobalId());
            if (creationDate.isBefore(new DateTime(member.getCreatedDate()))) {
                throw new BusinessRuleException("errors.cannotCreateLoan.because.clientsAreCreatedInFuture");
            }
        }

        // 1. assemble loan details
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);
        OfficeBO userOffice = this.officeDao.findOfficeById(user.getBranchId());
        PersonnelBO createdBy = this.personnelDao.findPersonnelById(userContext.getId());
        CustomerBO customer = this.customerDao.findCustomerById(loanAccountInfo.getCustomerId());
        if (customer.isGroup()) {
            customer = this.customerDao.findGroupBySystemId(customer.getGlobalCustNum());
        }

        // assemble
        LoanAccountDetail loanAccountDetail = assembleLoanAccountDetail(loanAccountInfo);

        List<AccountFeesEntity> accountFeeEntities = assembleAccountFees(loanAccountInfo.getAccountFees());
        List<AccountPenaltiesEntity> accountPenaltyEntities = assembleAccountPenalties(
                loanAccountInfo.getAccountPenalties());
        LoanProductOverridenDetail overridenDetail = new LoanProductOverridenDetail(
                loanAccountDetail.getLoanAmount(), loanAccountInfo.getDisbursementDate(),
                loanAccountInfo.getInterestRate(), loanAccountInfo.getNumberOfInstallments(),
                loanAccountInfo.getGraceDuration(), accountFeeEntities, accountPenaltyEntities);

        Integer interestDays = Integer.valueOf(AccountingRules.getNumberOfInterestDays().intValue());
        boolean loanScheduleIndependentOfCustomerMeetingEnabled = loanAccountInfo
                .isRepaymentScheduleIndependentOfCustomerMeeting();
        LoanScheduleConfiguration configuration = new LoanScheduleConfiguration(
                loanScheduleIndependentOfCustomerMeetingEnabled, interestDays);

        MeetingBO repaymentDayMeeting = loanAccountDetail.getCustomer().getCustomerMeetingValue();
        if (loanScheduleIndependentOfCustomerMeetingEnabled) {
            repaymentDayMeeting = this.createNewMeetingForRepaymentDay(loanAccountInfo.getDisbursementDate(),
                    loanAccountInfo, loanAccountDetail.getCustomer());
        }

        List<DateTime> loanScheduleDates = new ArrayList<DateTime>(loanScheduleInstallmentDates);

        LoanSchedule loanSchedule = assembleLoanSchedule(loanAccountDetail.getCustomer(),
                loanAccountDetail.getLoanProduct(), overridenDetail, configuration, repaymentDayMeeting, userOffice,
                loanScheduleDates, loanAccountInfo.getDisbursementDate(), totalInstallmentAmounts);

        // 2. create loan
        InstallmentRange installmentRange = new MaxMinNoOfInstall(
                loanAccountInfo.getMinAllowedNumberOfInstallments().shortValue(),
                loanAccountInfo.getMaxAllowedNumberOfInstallments().shortValue(), null);
        AmountRange amountRange = new MaxMinLoanAmount(loanAccountInfo.getMaxAllowedLoanAmount().doubleValue(),
                loanAccountInfo.getMinAllowedLoanAmount().doubleValue(), null);

        if (isBackdatedLoan) {
            creationDate = loanAccountInfo.getDisbursementDate().toDateMidnight().toDateTime();
        }
        CreationDetail creationDetail = new CreationDetail(creationDate, Integer.valueOf(user.getUserId()));
        LoanBO loan = LoanBO.openStandardLoanAccount(loanAccountDetail.getLoanProduct(),
                loanAccountDetail.getCustomer(), repaymentDayMeeting, loanSchedule,
                loanAccountDetail.getAccountState(), loanAccountDetail.getFund(), overridenDetail, configuration,
                installmentRange, amountRange, creationDetail, createdBy);
        loan.setBusinessActivityId(loanAccountInfo.getLoanPurposeId());
        loan.setExternalId(loanAccountInfo.getExternalId());
        loan.setCollateralNote(loanAccountInfo.getCollateralNotes());
        loan.setCollateralTypeId(loanAccountInfo.getCollateralTypeId());

        if (isBackdatedLoan) {
            loan.markAsCreatedWithBackdatedPayments();
        }

        try {
            transactionHelper.startTransaction();
            this.loanDao.save(loan);
            transactionHelper.flushSession();
            try {
                loan.setGlobalAccountNum(loan.generateId(userOffice.getGlobalOfficeNum()));
            } catch (AccountException e) {
                throw new BusinessRuleException(e.getMessage());
            }
            this.loanDao.save(loan);
            transactionHelper.flushSession();

            // for GLIM loans only
            List<GroupMemberLoanDetail> individualMembersOfGroupLoan = new ArrayList<GroupMemberLoanDetail>();
            List<BigDecimal> radio = new ArrayList<BigDecimal>(loan.getNoOfInstallments());
            for (GroupMemberAccountDto groupMemberAccount : memberDetails) {
                ClientBO member = this.customerDao.findClientBySystemId(groupMemberAccount.getGlobalId());
                Money loanAmount = new Money(loanAccountDetail.getLoanProduct().getCurrency(),
                        groupMemberAccount.getLoanAmount());
                List<CreateAccountFeeDto> defaultAccountFees = new ArrayList<CreateAccountFeeDto>();
                List<CreateAccountPenaltyDto> defaultAccountPenalties = new ArrayList<CreateAccountPenaltyDto>();

                radio.add(loanAmount.divide(loan.getLoanAmount()));

                for (CreateAccountFeeDto createAccountFeeDto : loanAccountInfo.getAccountFees()) {
                    Integer feeId = createAccountFeeDto.getFeeId();
                    String amount = createAccountFeeDto.getAmount();
                    FeeBO feeBO = this.feeDao.findById(feeId.shortValue());

                    if (feeBO instanceof AmountFeeBO) {
                        amount = String.valueOf(Double.valueOf(createAccountFeeDto.getAmount())
                                * (loanAmount.divide(loanAccountInfo.getLoanAmount()).getAmount().doubleValue()));
                    }

                    defaultAccountFees.add(new CreateAccountFeeDto(feeId, amount));
                }

                for (CreateAccountPenaltyDto createAccountPenaltyDto : loanAccountInfo.getAccountPenalties()) {
                    Integer penaltyId = createAccountPenaltyDto.getPenaltyId();
                    String amount = createAccountPenaltyDto.getAmount();
                    PenaltyBO penaltyBO = this.penaltyDao.findPenaltyById(penaltyId.shortValue());

                    if (penaltyBO instanceof AmountPenaltyBO) {
                        amount = String.valueOf(Double.valueOf(createAccountPenaltyDto.getAmount())
                                * (loanAmount.divide(loanAccountInfo.getLoanAmount()).getAmount().doubleValue()));
                    }

                    defaultAccountPenalties.add(new CreateAccountPenaltyDto(penaltyId, amount));
                }

                List<AccountFeesEntity> feeEntities = assembleAccountFees(defaultAccountFees);
                List<AccountPenaltiesEntity> penaltyEntities = assembleAccountPenalties(defaultAccountPenalties);
                LoanProductOverridenDetail memberOverridenDetail = new LoanProductOverridenDetail(loanAmount,
                        feeEntities, overridenDetail, penaltyEntities);

                LoanSchedule memberSchedule = assembleLoanSchedule(member, loanAccountDetail.getLoanProduct(),
                        memberOverridenDetail, configuration, repaymentDayMeeting, userOffice,
                        new ArrayList<DateTime>(), loanAccountInfo.getDisbursementDate(), new ArrayList<Number>());

                GroupMemberLoanDetail groupMemberLoanDetail = new GroupMemberLoanDetail(member,
                        memberOverridenDetail, memberSchedule, groupMemberAccount.getLoanPurposeId());
                individualMembersOfGroupLoan.add(groupMemberLoanDetail);
            }

            checkScheduleForMembers(loanSchedule, loan, individualMembersOfGroupLoan, radio);

            for (GroupMemberLoanDetail groupMemberAccount : individualMembersOfGroupLoan) {

                LoanBO memberLoan = LoanBO.openGroupMemberLoanAccount(loan, loanAccountDetail.getLoanProduct(),
                        groupMemberAccount.getMember(), repaymentDayMeeting, groupMemberAccount.getMemberSchedule(),
                        groupMemberAccount.getMemberOverridenDetail(), configuration, installmentRange, amountRange,
                        creationDetail, createdBy);
                if (groupMemberAccount.getLoanPurposeId() > 0) {
                    memberLoan.setBusinessActivityId(groupMemberAccount.getLoanPurposeId());
                }
                if (!backdatedLoanPayments.isEmpty()) {
                    memberLoan.markAsCreatedWithBackdatedPayments();
                }
                this.loanDao.save(memberLoan);
                transactionHelper.flushSession();
                try {
                    memberLoan.setGlobalAccountNum(memberLoan.generateId(userOffice.getGlobalOfficeNum()));
                } catch (AccountException e) {
                    throw new BusinessRuleException(e.getMessage());
                }
                this.loanDao.save(memberLoan);
                transactionHelper.flushSession();
            }

            // save question groups
            if (!questionGroups.isEmpty()) {
                Integer eventSourceId = questionnaireServiceFacade.getEventSourceId("Create", "Loan");
                QuestionGroupDetails questionGroupDetails = new QuestionGroupDetails(
                        Integer.valueOf(user.getUserId()).shortValue(), loan.getAccountId(), eventSourceId,
                        questionGroups);
                questionnaireServiceFacade.saveResponses(questionGroupDetails);
                transactionHelper.flushSession();
            }

            if (loanAccountCashFlow != null && !loanAccountCashFlow.getMonthlyCashFlow().isEmpty()) {

                List<MonthlyCashFlowDetail> monthlyCashFlowDetails = new ArrayList<MonthlyCashFlowDetail>();
                for (MonthlyCashFlowDto monthlyCashFlow : loanAccountCashFlow.getMonthlyCashFlow()) {
                    MonthlyCashFlowDetail monthlyCashFlowDetail = new MonthlyCashFlowDetail(
                            monthlyCashFlow.getMonthDate(), monthlyCashFlow.getRevenue(),
                            monthlyCashFlow.getExpenses(), monthlyCashFlow.getNotes());

                    monthlyCashFlowDetails.add(monthlyCashFlowDetail);
                }

                org.mifos.platform.cashflow.service.CashFlowDetail cashFlowDetail = new org.mifos.platform.cashflow.service.CashFlowDetail(
                        monthlyCashFlowDetails);
                cashFlowDetail.setTotalCapital(loanAccountCashFlow.getTotalCapital());
                cashFlowDetail.setTotalLiability(loanAccountCashFlow.getTotalLiability());
                cashFlowService.save(cashFlowDetail);
                transactionHelper.flushSession();
            }

            if (isBackdatedLoan) {
                // 3. auto approve loan
                String comment = "Automatic Status Update (Redo Loan)";
                LocalDate approvalDate = loanAccountInfo.getDisbursementDate();
                loan.approve(createdBy, comment, approvalDate);

                // 4. disburse loan
                String receiptNumber = null;
                Date receiptDate = null;
                PaymentTypeEntity paymentType = new PaymentTypeEntity(PaymentTypes.CASH.getValue());
                if (loanAccountInfo.getDisbursalPaymentTypeId() != null) {
                    paymentType = new PaymentTypeEntity(loanAccountInfo.getDisbursalPaymentTypeId());
                }
                Date paymentDate = loanAccountInfo.getDisbursementDate().toDateMidnight().toDate();
                AccountPaymentEntity disbursalPayment = new AccountPaymentEntity(loan, loan.getLoanAmount(),
                        receiptNumber, receiptDate, paymentType, paymentDate);
                disbursalPayment.setCreatedByUser(createdBy);

                // refactoring of loan disbursal
                if (customer
                        .isDisbursalPreventedDueToAnyExistingActiveLoansForTheSameProduct(loan.getLoanOffering())) {
                    throw new AccountException("errors.cannotDisburseLoan.because.otherLoansAreActive");
                }

                try {
                    loan.updateCustomer(customer);
                    new ProductMixValidator().checkIfProductsOfferingCanCoexist(loan);
                } catch (ServiceException e1) {
                    throw new AccountException(e1.getMessage());
                }

                loan.disburse(createdBy, disbursalPayment);

                customer.updatePerformanceHistoryOnDisbursement(loan, loan.getLoanAmount());
                // end of refactoring of loan disbural

                this.loanDao.save(loan);
                transactionHelper.flushSession();

                // 5. apply each payment
                for (LoanPaymentDto loanPayment : backdatedLoanPayments) {
                    Money amountPaidToDate = new Money(loan.getCurrency(), loanPayment.getAmount());
                    PaymentData paymentData = new PaymentData(amountPaidToDate, createdBy,
                            loanPayment.getPaymentTypeId(), loanPayment.getPaymentDate().toDateMidnight().toDate());
                    loan.applyPayment(paymentData);
                    this.loanDao.save(loan);
                }
            }
            transactionHelper.commitTransaction();

            return new LoanCreationResultDto(false, loan.getAccountId(), loan.getGlobalAccountNum());
        } catch (BusinessRuleException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getMessageKey(), e);
        } catch (Exception e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } finally {
            this.transactionHelper.closeSession();
        }
    }

    private void checkScheduleForMembers(LoanSchedule loanSchedule, LoanBO loan,
            List<GroupMemberLoanDetail> individualMembersOfGroupLoan, List<BigDecimal> radio) {

        for (int i = 0; i < loan.getNoOfInstallments(); ++i) {
            BigDecimal principal = loanSchedule.getRoundedLoanSchedules().get(i).getPrincipal().getAmount();
            BigDecimal interest = loanSchedule.getRoundedLoanSchedules().get(i).getInterest().getAmount();
            BigDecimal miscFee = loanSchedule.getRoundedLoanSchedules().get(i).getMiscFee().getAmount();
            BigDecimal miscPenalty = loanSchedule.getRoundedLoanSchedules().get(i).getMiscPenalty().getAmount();

            for (GroupMemberLoanDetail groupMemberLoanDetail : individualMembersOfGroupLoan) {
                LoanScheduleEntity loanScheduleEntity = groupMemberLoanDetail.getMemberSchedule()
                        .getRoundedLoanSchedules().get(i);

                principal = principal.subtract(loanScheduleEntity.getPrincipal().getAmount());
                interest = interest.subtract(loanScheduleEntity.getInterest().getAmount());
                miscFee = miscFee.subtract(loanScheduleEntity.getMiscFee().getAmount());
                miscPenalty = miscPenalty.subtract(loanScheduleEntity.getMiscPenalty().getAmount());
            }

            if (principal.compareTo(BigDecimal.ZERO) != 0) {
                for (int j = 0; j < individualMembersOfGroupLoan.size(); ++j) {
                    Money oldPrincipal = individualMembersOfGroupLoan.get(j).getMemberSchedule()
                            .getRoundedLoanSchedules().get(i).getPrincipal();
                    Money newPrincipal = oldPrincipal
                            .add(new Money(loan.getCurrency(), principal.multiply(radio.get(j))));

                    individualMembersOfGroupLoan.get(j).getMemberSchedule().getRoundedLoanSchedules().get(i)
                            .setPrincipal(newPrincipal);
                }
            }

            if (interest.compareTo(BigDecimal.ZERO) != 0) {
                for (int j = 0; j < individualMembersOfGroupLoan.size(); ++j) {
                    Money oldinterest = individualMembersOfGroupLoan.get(j).getMemberSchedule()
                            .getRoundedLoanSchedules().get(i).getInterest();
                    Money newInterest = oldinterest
                            .add(new Money(loan.getCurrency(), interest.multiply(radio.get(j))));

                    individualMembersOfGroupLoan.get(j).getMemberSchedule().getRoundedLoanSchedules().get(i)
                            .setInterest(newInterest);
                }
            }

            if (miscFee.compareTo(BigDecimal.ZERO) != 0) {
                for (int j = 0; j < individualMembersOfGroupLoan.size(); ++j) {
                    Money oldMiscFee = individualMembersOfGroupLoan.get(j).getMemberSchedule()
                            .getRoundedLoanSchedules().get(i).getMiscFee();
                    Money newMiscFee = oldMiscFee
                            .add(new Money(loan.getCurrency(), miscFee.multiply(radio.get(j))));

                    individualMembersOfGroupLoan.get(j).getMemberSchedule().getRoundedLoanSchedules().get(i)
                            .setMiscFee(newMiscFee);
                }
            }

            if (miscPenalty.compareTo(BigDecimal.ZERO) != 0) {
                for (int j = 0; j < individualMembersOfGroupLoan.size(); ++j) {
                    Money oldMiscPenalty = individualMembersOfGroupLoan.get(j).getMemberSchedule()
                            .getRoundedLoanSchedules().get(i).getMiscPenalty();
                    Money newMiscPenalty = oldMiscPenalty
                            .add(new Money(loan.getCurrency(), miscPenalty.multiply(radio.get(j))));

                    individualMembersOfGroupLoan.get(j).getMemberSchedule().getRoundedLoanSchedules().get(i)
                            .setMiscPenalty(newMiscPenalty);
                }
            }
        }
    }

    private List<AccountFeesEntity> assembleAccountFees(List<CreateAccountFeeDto> defaultAccountFees) {
        List<AccountFeesEntity> accountFeeEntities = new ArrayList<AccountFeesEntity>();
        for (CreateAccountFeeDto defaultFee : defaultAccountFees) {
            FeeBO fee = this.feeDao.findById(defaultFee.getFeeId().shortValue());
            AccountFeesEntity deafultAccountFeeEntity = new AccountFeesEntity(null, fee,
                    Double.valueOf(defaultFee.getAmount()));
            accountFeeEntities.add(deafultAccountFeeEntity);
        }
        return accountFeeEntities;
    }

    private List<AccountPenaltiesEntity> assembleAccountPenalties(
            List<CreateAccountPenaltyDto> defaultAccountPenalties) {
        List<AccountPenaltiesEntity> accountPenaltyEntities = new ArrayList<AccountPenaltiesEntity>();
        for (CreateAccountPenaltyDto defaultPenalty : defaultAccountPenalties) {
            PenaltyBO penalty = this.penaltyDao.findPenaltyById(defaultPenalty.getPenaltyId().shortValue());
            AccountPenaltiesEntity deafultAccountPenaltyEntity = new AccountPenaltiesEntity(null, penalty,
                    Double.valueOf(defaultPenalty.getAmount()));
            accountPenaltyEntities.add(deafultAccountPenaltyEntity);
        }
        return accountPenaltyEntities;
    }

    private MeetingBO createNewMeetingForRepaymentDay(LocalDate disbursementDate,
            RecurringSchedule recurringSchedule, CustomerBO customer) {
        MeetingBO newMeetingForRepaymentDay = null;

        final int minDaysInterval = new ConfigurationPersistence()
                .getConfigurationValueInteger(MIN_DAYS_BETWEEN_DISBURSAL_AND_FIRST_REPAYMENT_DAY);

        final Date repaymentStartDate = disbursementDate.plusDays(minDaysInterval).toDateMidnight().toDateTime()
                .toDate();
        try {
            if (recurringSchedule.isWeekly()) {
                WeekDay weekDay = WeekDay.getWeekDay(recurringSchedule.getDay().shortValue());
                Short recurEvery = recurringSchedule.getEvery().shortValue();
                newMeetingForRepaymentDay = new MeetingBO(weekDay, recurEvery, repaymentStartDate,
                        MeetingType.LOAN_INSTALLMENT, customer.getCustomerMeeting().getMeeting().getMeetingPlace());
            } else if (recurringSchedule.isMonthly()) {
                if (recurringSchedule.isMonthlyOnDayOfMonth()) {
                    Short dayOfMonth = recurringSchedule.getDay().shortValue();
                    Short dayRecurMonth = recurringSchedule.getEvery().shortValue();
                    newMeetingForRepaymentDay = new MeetingBO(dayOfMonth, dayRecurMonth, repaymentStartDate,
                            MeetingType.LOAN_INSTALLMENT,
                            customer.getCustomerMeeting().getMeeting().getMeetingPlace());
                } else {
                    Short weekOfMonth = recurringSchedule.getDay().shortValue();
                    Short monthRank = recurringSchedule.getWeek().shortValue();
                    Short everyMonth = recurringSchedule.getEvery().shortValue();

                    newMeetingForRepaymentDay = new MeetingBO(weekOfMonth, everyMonth, repaymentStartDate,
                            MeetingType.LOAN_INSTALLMENT,
                            customer.getCustomerMeeting().getMeeting().getMeetingPlace(), monthRank);
                }
            }
            return newMeetingForRepaymentDay;
        } catch (NumberFormatException nfe) {
            throw new MifosRuntimeException(nfe);
        } catch (MeetingException me) {
            throw new BusinessRuleException(me.getKey(), me);
        }

    }

    @Override
    public LoanDisbursalDto retrieveLoanDisbursalDetails(Integer loanAccountId) {

        try {
            LoanBO loan = this.loanDao.findById(loanAccountId);
            new ProductMixValidator().checkIfProductsOfferingCanCoexist(loan);

            Date proposedDate = new DateTimeService().getCurrentJavaDateTime();
            boolean backDatedTransactionsAllowed = AccountingRules.isBackDatedTxnAllowed();
            if (backDatedTransactionsAllowed) {
                proposedDate = loan.getDisbursementDate();
            }

            Short currencyId = Short.valueOf("0");
            boolean multiCurrencyEnabled = AccountingRules.isMultiCurrencyEnabled();
            if (multiCurrencyEnabled) {
                currencyId = loan.getCurrency().getCurrencyId();
            }

            boolean repaymentIndependentOfMeetingSchedule = new ConfigurationPersistence()
                    .isRepaymentIndepOfMeetingEnabled();

            return new LoanDisbursalDto(loan.getAccountId(), proposedDate, loan.getLoanAmount().toString(),
                    loan.getAmountTobePaidAtdisburtail().toString(), backDatedTransactionsAllowed,
                    repaymentIndependentOfMeetingSchedule, multiCurrencyEnabled, currencyId);
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e);
        } catch (AccountException e) {
            throw new BusinessRuleException(e.getKey(), e.getValues());
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public List<LoanActivityDto> retrieveAllLoanAccountActivities(String globalAccountNum) {

        LoanBO loan = this.loanDao.findByGlobalAccountNum(globalAccountNum);
        List<LoanActivityEntity> loanAccountActivityDetails = loan.getLoanActivityDetails();
        List<LoanActivityDto> loanActivityViewSet = new ArrayList<LoanActivityDto>();
        for (LoanActivityEntity loanActivity : loanAccountActivityDetails) {
            loanActivityViewSet.add(loanActivity.toDto());
        }

        return loanActivityViewSet;
    }

    @Override
    public LoanInstallmentDetailsDto retrieveInstallmentDetails(Integer accountId) {
        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        LoanBO loanBO = this.loanDao.findById(accountId);

        try {
            personnelDao.checkAccessPermission(userContext, loanBO.getOfficeId(),
                    loanBO.getCustomer().getLoanOfficerId());
        } catch (AccountException e) {
            throw new MifosRuntimeException(e.getMessage(), e);
        }

        InstallmentDetailsDto viewUpcomingInstallmentDetails = getUpcomingInstallmentDetails(
                loanBO.getDetailsOfNextInstallment(), loanBO.getCurrency());
        InstallmentDetailsDto viewOverDueInstallmentDetails = getOverDueInstallmentDetails(
                loanBO.getDetailsOfInstallmentsInArrears(), loanBO.getCurrency());

        Money upcomingInstallmentSubTotal = new Money(loanBO.getCurrency(),
                viewUpcomingInstallmentDetails.getSubTotal());
        Money overdueInstallmentSubTotal = new Money(loanBO.getCurrency(),
                viewOverDueInstallmentDetails.getSubTotal());
        Money totalAmountDue = upcomingInstallmentSubTotal.add(overdueInstallmentSubTotal);

        return new LoanInstallmentDetailsDto(viewUpcomingInstallmentDetails, viewOverDueInstallmentDetails,
                totalAmountDue.toString(), loanBO.getNextMeetingDate());
    }

    @Override
    public List<LoanRepaymentScheduleItemDto> retrieveLoanRepaymentSchedule(String globalAccountNum,
            Date viewDate) {
        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        LoanBO loanBO = this.loanDao.findByGlobalAccountNum(globalAccountNum);

        try {
            personnelDao.checkAccessPermission(userContext, loanBO.getOfficeId(),
                    loanBO.getCustomer().getLoanOfficerId());
        } catch (AccountException e) {
            throw new MifosRuntimeException(e.getMessage(), e);
        }

        Errors errors = loanBusinessService.computeExtraInterest(loanBO, viewDate);
        if (errors.hasErrors()) {
            throw new MifosRuntimeException(errors.getErrorEntries().get(0).getDefaultMessage());
        }

        List<LoanRepaymentScheduleItemDto> loanSchedule = new ArrayList<LoanRepaymentScheduleItemDto>();

        for (AccountActionDateEntity accountAction : loanBO.getAccountActionDates()) {
            LoanScheduleEntity loanAccountAction = (LoanScheduleEntity) accountAction;
            Set<AccountFeesActionDetailEntity> feeEntities = loanAccountAction.getAccountFeesActionDetails();

            List<AccountFeeScheduleDto> feeDtos = new ArrayList<AccountFeeScheduleDto>();
            for (AccountFeesActionDetailEntity feeEntity : feeEntities) {
                feeDtos.add(convertToDto(feeEntity));
            }

            loanSchedule.add(new LoanRepaymentScheduleItemDto(loanAccountAction.getInstallmentId(),
                    loanAccountAction.getActionDate(), loanAccountAction.getPaymentStatus(),
                    loanAccountAction.getPaymentDate(), loanAccountAction.getPrincipal().toString(),
                    loanAccountAction.getPrincipalPaid().toString(), loanAccountAction.getInterest().toString(),
                    loanAccountAction.getInterestPaid().toString(), loanAccountAction.getPenalty().toString(),
                    loanAccountAction.getPenaltyPaid().toString(), loanAccountAction.getExtraInterest().toString(),
                    loanAccountAction.getExtraInterestPaid().toString(), loanAccountAction.getMiscFee().toString(),
                    loanAccountAction.getMiscFeePaid().toString(), loanAccountAction.getMiscPenalty().toString(),
                    loanAccountAction.getMiscPenaltyPaid().toString(), feeDtos));
        }

        return loanSchedule;
    }

    private AccountFeeScheduleDto convertToDto(AccountFeesActionDetailEntity feeEntity) {
        return new AccountFeeScheduleDto(feeEntity.getFee().getFeeName(), feeEntity.getFeeAmount().toString(),
                feeEntity.getFeeAmountPaid().toString(), feeEntity.getFeeAllocated().toString());
    }

    private InstallmentDetailsDto getUpcomingInstallmentDetails(
            final AccountActionDateEntity upcomingAccountActionDate, final MifosCurrency currency) {
        if (upcomingAccountActionDate != null) {
            LoanScheduleEntity upcomingInstallment = (LoanScheduleEntity) upcomingAccountActionDate;
            Money subTotal = upcomingInstallment.getPrincipalDue().add(upcomingInstallment.getInterestDue())
                    .add(upcomingInstallment.getTotalFeesDueWithMiscFee()).add(upcomingInstallment.getPenaltyDue());
            return new InstallmentDetailsDto(upcomingInstallment.getPrincipalDue().toString(),
                    upcomingInstallment.getInterestDue().toString(),
                    upcomingInstallment.getTotalFeeDueWithMiscFeeDue().toString(),
                    upcomingInstallment.getPenaltyDue().toString(), subTotal.toString());
        }
        String zero = new Money(currency).toString();
        return new InstallmentDetailsDto(zero, zero, zero, zero, zero);
    }

    private InstallmentDetailsDto getOverDueInstallmentDetails(
            final List<AccountActionDateEntity> overDueInstallmentList, final MifosCurrency currency) {
        Money principalDue = new Money(currency);
        Money interestDue = new Money(currency);
        Money feesDue = new Money(currency);
        Money penaltyDue = new Money(currency);
        for (AccountActionDateEntity accountActionDate : overDueInstallmentList) {
            LoanScheduleEntity installment = (LoanScheduleEntity) accountActionDate;
            principalDue = principalDue.add(installment.getPrincipalDue());
            interestDue = interestDue.add(installment.getInterestDue());
            feesDue = feesDue.add(installment.getTotalFeeDueWithMiscFeeDue());
            penaltyDue = penaltyDue.add(installment.getPenaltyDue());
        }
        Money subTotal = principalDue.add(interestDue).add(feesDue).add(penaltyDue);
        return new InstallmentDetailsDto(principalDue.toString(), interestDue.toString(), feesDue.toString(),
                penaltyDue.toString(), subTotal.toString());
    }

    @Override
    public boolean isTrxnDateValid(Integer loanAccountId, Date trxnDate) {

        try {
            LoanBO loan = this.loanDao.findById(loanAccountId);

            Date meetingDate = new CustomerPersistence()
                    .getLastMeetingDateForCustomer(loan.getCustomer().getCustomerId());
            boolean repaymentIndependentOfMeetingEnabled = new ConfigurationPersistence()
                    .isRepaymentIndepOfMeetingEnabled();
            return loan.isTrxnDateValid(trxnDate, meetingDate, repaymentIndependentOfMeetingEnabled);
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public void makeEarlyRepayment(RepayLoanInfoDto repayLoanInfoDto) {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        LoanBO loan = this.loanDao.findByGlobalAccountNum(repayLoanInfoDto.getGlobalAccountNum());

        try {
            personnelDao.checkAccessPermission(userContext, loan.getOfficeId(),
                    loan.getCustomer().getLoanOfficerId());
        } catch (AccountException e) {
            throw new MifosRuntimeException(e.getMessage(), e);
        }
        monthClosingServiceFacade.validateTransactionDate(repayLoanInfoDto.getDateOfPayment());

        try {
            if (repayLoanInfoDto.isWaiveInterest() && !loan.isInterestWaived()) {
                throw new BusinessRuleException(LoanConstants.WAIVER_INTEREST_NOT_CONFIGURED);
            }
            Money earlyRepayAmount = new Money(loan.getCurrency(), repayLoanInfoDto.getEarlyRepayAmount());
            loanBusinessService.computeExtraInterest(loan, repayLoanInfoDto.getDateOfPayment());
            BigDecimal interestDueForCurrentInstallment = interestDueForNextInstallment(
                    repayLoanInfoDto.getTotalRepaymentAmount(), repayLoanInfoDto.getWaivedAmount(), loan,
                    repayLoanInfoDto.isWaiveInterest());
            loan.makeEarlyRepayment(earlyRepayAmount, repayLoanInfoDto.getDateOfPayment(),
                    repayLoanInfoDto.getReceiptNumber(), repayLoanInfoDto.getReceiptDate(),
                    repayLoanInfoDto.getPaymentTypeId(), repayLoanInfoDto.getId(),
                    repayLoanInfoDto.isWaiveInterest(),
                    new Money(loan.getCurrency(), interestDueForCurrentInstallment));
        } catch (AccountException e) {
            throw new BusinessRuleException(e.getKey(), e);
        }
    }

    @Override
    public void makeEarlyRepaymentWithCommit(RepayLoanInfoDto repayLoanInfoDto) {
        transactionHelper.startTransaction();
        makeEarlyRepayment(repayLoanInfoDto);
        transactionHelper.commitTransaction();
    }

    BigDecimal interestDueForNextInstallment(BigDecimal totalRepaymentAmount, BigDecimal waivedAmount, LoanBO loan,
            boolean waiveInterest) {
        BigDecimal result = BigDecimal.ZERO;
        if (!waiveInterest) {
            if (loan.isDecliningBalanceInterestRecalculation()) {
                result = totalRepaymentAmount.subtract(waivedAmount);
            } else {
                AccountActionDateEntity nextInstallment = loan.getDetailsOfNextInstallment();
                if (nextInstallment != null) {
                    LoanScheduleEntity loanScheduleEntity = (LoanScheduleEntity) nextInstallment;
                    result = loanScheduleEntity.getInterestDue().getAmount();
                }
            }
        }
        return result;
    }

    @Override
    public LoanInformationDto retrieveLoanInformation(String globalAccountNum) {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        LoanBO loan = this.loanDao.findByGlobalAccountNum(globalAccountNum);

        try {
            personnelDao.checkAccessPermission(userContext, loan.getOfficeId(),
                    loan.getCustomer().getLoanOfficerId());
        } catch (AccountException e) {
            throw new MifosRuntimeException("Access denied!", e);
        }

        String fundName = null;
        if (loan.getFund() != null) {
            fundName = loan.getFund().getFundName();
        }

        //        boolean activeSurveys = surveysPersistence.isActiveSurveysForSurveyType(SurveyType.LOAN);
        boolean activeSurveys = false;
        List<SurveyDto> accountSurveys = loanDao.getAccountSurveyDto(loan.getAccountId());

        LoanSummaryDto loanSummary = new LoanSummaryDto(loan.getLoanSummary().getOriginalPrincipal().toString(),
                loan.getLoanSummary().getPrincipalPaid().toString(),
                loan.getLoanSummary().getPrincipalDue().toString(),
                loan.getLoanSummary().getOriginalInterest().toString(),
                loan.getLoanSummary().getInterestPaid().toString(),
                loan.getLoanSummary().getInterestDue().toString(),
                loan.getLoanSummary().getOriginalFees().toString(), loan.getLoanSummary().getFeesPaid().toString(),
                loan.getLoanSummary().getFeesDue().toString(),
                loan.getLoanSummary().getOriginalPenalty().toString(),
                loan.getLoanSummary().getPenaltyPaid().toString(), loan.getLoanSummary().getPenaltyDue().toString(),
                loan.getLoanSummary().getTotalLoanAmnt().toString(),
                loan.getLoanSummary().getTotalAmntPaid().toString(),
                loan.getLoanSummary().getTotalAmntDue().toString());

        LoanPerformanceHistoryEntity performanceHistory = loan.getPerformanceHistory();
        LoanPerformanceHistoryDto loanPerformanceHistory = new LoanPerformanceHistoryDto(
                performanceHistory.getNoOfPayments(), performanceHistory.getTotalNoOfMissedPayments(),
                performanceHistory.getDaysInArrears(), performanceHistory.getLoanMaturityDate());

        Set<AccountFeesDto> accountFeesDtos = new HashSet<AccountFeesDto>();
        if (!loan.getAccountFees().isEmpty()) {
            for (AccountFeesEntity accountFeesEntity : loan.getAccountFees()) {
                AccountFeesDto accountFeesDto = new AccountFeesDto(
                        accountFeesEntity.getFees().getFeeFrequency().getFeeFrequencyType().getId(),
                        (accountFeesEntity.getFees().getFeeFrequency().getFeePayment() != null
                                ? accountFeesEntity.getFees().getFeeFrequency().getFeePayment().getId()
                                : null),
                        accountFeesEntity.getFeeStatus(), accountFeesEntity.getFees().getFeeName(),
                        accountFeesEntity.getAccountFeeAmount().toString(),
                        getMeetingRecurrence(accountFeesEntity.getFees().getFeeFrequency().getFeeMeetingFrequency(),
                                userContext),
                        accountFeesEntity.getFees().getFeeId());
                accountFeesDtos.add(accountFeesDto);
            }
        }

        Set<AccountPenaltiesDto> accountPenaltiesDtos = new HashSet<AccountPenaltiesDto>();
        if (!loan.getAccountPenalties().isEmpty()) {
            for (AccountPenaltiesEntity accountPenaltiesEntity : loan.getAccountPenalties()) {
                accountPenaltiesDtos.add(
                        new AccountPenaltiesDto(accountPenaltiesEntity.getPenalty().getPenaltyFrequency().getId(),
                                accountPenaltiesEntity.getPenaltyStatus(),
                                accountPenaltiesEntity.getPenalty().getPenaltyName(),
                                accountPenaltiesEntity.getAccountPenaltyAmount().toString(),
                                accountPenaltiesEntity.getPenalty().getPenaltyFrequency().getName(),
                                accountPenaltiesEntity.getPenalty().getPenaltyId()));
            }
        }

        Set<String> accountFlagNames = getAccountStateFlagEntityNames(loan.getAccountFlags());
        Short accountStateId = loan.getAccountState().getId();
        String accountStateName = getAccountStateName(accountStateId);
        boolean disbursed = AccountState.isDisbursed(accountStateId);
        String gracePeriodTypeName = getGracePeriodTypeName(loan.getGracePeriodType().getId());
        String interestTypeName = getInterestTypeName(loan.getInterestType().getId());
        List<CustomerNoteDto> recentNoteDtos = new ArrayList<CustomerNoteDto>();
        List<AccountNotesEntity> recentNotes = loan.getRecentAccountNotes();
        for (AccountNotesEntity accountNotesEntity : recentNotes) {
            recentNoteDtos.add(new CustomerNoteDto(accountNotesEntity.getCommentDate(),
                    accountNotesEntity.getComment(), accountNotesEntity.getPersonnelName()));
        }

        return new LoanInformationDto(loan.getLoanOffering().getPrdOfferingName(), globalAccountNum, accountStateId,
                accountStateName, disbursed, accountFlagNames, loan.getDisbursementDate(), loan.isRedone(),
                loan.getBusinessActivityId(), loan.getAccountId(), gracePeriodTypeName, interestTypeName,
                loan.getCustomer().getCustomerId(), loan.getAccountType().getAccountTypeId(),
                loan.getOffice().getOfficeId(), loan.getPersonnel().getPersonnelId(), loan.getNextMeetingDate(),
                loan.getTotalAmountDue().toString(), loan.getTotalAmountInArrears().toString(), loanSummary,
                loan.getLoanActivityDetails().isEmpty() ? false : true, loan.getInterestRate(),
                loan.isInterestDeductedAtDisbursement(),
                loan.getLoanOffering().getLoanOfferingMeeting().getMeeting().getMeetingDetails().getRecurAfter(),
                loan.getLoanOffering().getLoanOfferingMeeting().getMeeting().getMeetingDetails().getRecurrenceType()
                        .getRecurrenceId(),
                loan.getLoanOffering().isPrinDueLastInst(), loan.getNoOfInstallments(),
                loan.getMaxMinNoOfInstall().getMinNoOfInstall(), loan.getMaxMinNoOfInstall().getMaxNoOfInstall(),
                loan.getGracePeriodDuration(), fundName, loan.getCollateralTypeId(), loan.getCollateralNote(),
                loan.getExternalId(), accountFeesDtos, loan.getCreatedDate(), loanPerformanceHistory,
                loan.getCustomer().isGroup(), getRecentActivityView(globalAccountNum), activeSurveys,
                accountSurveys, loan.getCustomer().getDisplayName(), loan.getCustomer().getGlobalCustNum(),
                loan.getOffice().getOfficeName(), recentNoteDtos, accountPenaltiesDtos);
    }

    private String getMeetingRecurrence(MeetingBO meeting, UserContext userContext) {
        return meeting != null ? new MeetingHelper().getMessageWithFrequency(meeting, userContext) : null;
    }

    private String getAccountStateName(Short id) {
        AccountStateEntity accountStateEntity;
        try {
            accountStateEntity = legacyMasterDao.getPersistentObject(AccountStateEntity.class, id);
            return accountStateEntity.getLookUpValue().getLookUpName();
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e.toString());
        }
    }

    private String getGracePeriodTypeName(Short id) {
        GracePeriodTypeEntity gracePeriodType;
        try {
            gracePeriodType = legacyMasterDao.getPersistentObject(GracePeriodTypeEntity.class, id);
            return gracePeriodType.getLookUpValue().getLookUpName();
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e.toString());
        }
    }

    private String getInterestTypeName(Short id) {
        InterestTypesEntity interestType;
        try {
            interestType = legacyMasterDao.getPersistentObject(InterestTypesEntity.class, id);
            return interestType.getLookUpValue().getLookUpName();
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e.toString());
        }
    }

    private Set<String> getAccountStateFlagEntityNames(Set<AccountFlagMapping> accountFlagMappings) {
        Set<String> accountFlagNames = new HashSet<String>();
        if (!accountFlagMappings.isEmpty()) {
            for (AccountFlagMapping accountFlagMapping : accountFlagMappings) {
                String accountFlagName = getAccountStateFlagEntityName(accountFlagMapping.getFlag().getId());
                accountFlagNames.add(accountFlagName);
            }
        }
        return accountFlagNames;
    }

    private String getAccountStateFlagEntityName(Short id) {
        AccountStateFlagEntity accountStateFlagEntity;
        try {
            accountStateFlagEntity = legacyMasterDao.getPersistentObject(AccountStateFlagEntity.class, id);
            return accountStateFlagEntity.getLookUpValue().getLookUpName();
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e.toString());
        }
    }

    private List<LoanActivityDto> getRecentActivityView(final String globalAccountNumber) {
        LoanBO loanBO = loanDao.findByGlobalAccountNum(globalAccountNumber);
        List<LoanActivityEntity> loanAccountActivityDetails = loanBO.getLoanActivityDetails();
        List<LoanActivityDto> recentActivityView = new ArrayList<LoanActivityDto>();

        int count = 0;
        for (LoanActivityEntity loanActivity : loanAccountActivityDetails) {
            recentActivityView.add(getLoanActivityView(loanActivity));
            if (++count == 3) {
                break;
            }
        }
        return recentActivityView;
    }

    private LoanActivityDto getLoanActivityView(final LoanActivityEntity loanActivity) {
        LoanActivityDto loanActivityDto = new LoanActivityDto();
        loanActivityDto.setId(loanActivity.getAccount().getAccountId());
        loanActivityDto.setActionDate(loanActivity.getTrxnCreatedDate());
        loanActivityDto.setActivity(loanActivity.getComments());
        loanActivityDto.setPrincipal(removeSign(loanActivity.getPrincipal()).toString());
        loanActivityDto.setInterest(removeSign(loanActivity.getInterest()).toString());
        loanActivityDto.setPenalty(removeSign(loanActivity.getPenalty()).toString());
        loanActivityDto.setFees(removeSign(loanActivity.getFee()).toString());
        Money total = removeSign(loanActivity.getFee()).add(removeSign(loanActivity.getPenalty()))
                .add(removeSign(loanActivity.getPrincipal())).add(removeSign(loanActivity.getInterest()));
        loanActivityDto.setTotal(total.toString());
        loanActivityDto.setTotalValue(total.getAmount().doubleValue());
        loanActivityDto.setTimeStamp(loanActivity.getTrxnCreatedDate());
        loanActivityDto.setRunningBalanceInterest(loanActivity.getInterestOutstanding().toString());
        loanActivityDto.setRunningBalancePrinciple(loanActivity.getPrincipalOutstanding().toString());
        loanActivityDto.setRunningBalanceFees(loanActivity.getFeeOutstanding().toString());
        loanActivityDto.setRunningBalancePenalty(loanActivity.getPenaltyOutstanding().toString());

        loanActivityDto.setRunningBalancePrincipleWithInterestAndFees(loanActivity.getPrincipalOutstanding()
                .add(loanActivity.getInterestOutstanding()).add(loanActivity.getFeeOutstanding()).toString());

        return loanActivityDto;
    }

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

        return amount;
    }

    @Override
    public RepayLoanDto retrieveLoanRepaymentDetails(String globalAccountNumber) {
        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        LoanBO loan = this.loanDao.findByGlobalAccountNum(globalAccountNumber);

        try {
            personnelDao.checkAccessPermission(userContext, loan.getOfficeId(),
                    loan.getCustomer().getLoanOfficerId());
        } catch (AccountException e) {
            throw new MifosRuntimeException(e.getMessage(), e);
        }

        Money repaymentAmount;
        Money waiverAmount;
        if (loan.isDecliningBalanceInterestRecalculation()) {
            RepaymentResultsHolder repaymentResultsHolder = scheduleCalculatorAdaptor.computeRepaymentAmount(loan,
                    DateUtils.getCurrentDateWithoutTimeStamp());
            repaymentAmount = new Money(loan.getCurrency(), repaymentResultsHolder.getTotalRepaymentAmount());
            waiverAmount = new Money(loan.getCurrency(), repaymentResultsHolder.getWaiverAmount());
        } else {
            repaymentAmount = loan.getEarlyRepayAmount();
            waiverAmount = loan.waiverAmount();
        }
        Money waivedRepaymentAmount = repaymentAmount.subtract(waiverAmount);
        return new RepayLoanDto(repaymentAmount.toString(), waivedRepaymentAmount.toString(),
                loan.isInterestWaived());
    }

    @Override
    public ExpectedPaymentDto retrieveExpectedPayment(String globalAccountNumber, LocalDate paymentDueAsOf) {
        LoanBO loan = loanDao.findByGlobalAccountNum(globalAccountNumber);

        Money amountDue = loan.getTotalAmountDueOn(paymentDueAsOf);

        return new ExpectedPaymentDto(globalAccountNumber, amountDue.getAmount());
    }

    @Override
    public void applyLoanRepayment(String globalAccountNumber, LocalDate paymentDate, BigDecimal repaymentAmount) {

        try {
            this.transactionHelper.startTransaction();
            LoanBO loan = loanDao.findByGlobalAccountNum(globalAccountNumber);

            Money outstandingOverpayment = loan.applyNewPaymentMechanism(paymentDate, repaymentAmount);

            // 3. pay off principal of next installment and recalculate interest if 'over paid'
            if (outstandingOverpayment.isGreaterThanZero()) {

                Money totalPrincipalDueNow = loan.getTotalPrincipalDue().subtract(outstandingOverpayment);

                MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                UserContext userContext = toUserContext(user);

                // assemble into domain entities
                LoanOfferingBO loanProduct = this.loanProductDao
                        .findById(loan.getLoanOffering().getPrdOfferingId().intValue());
                CustomerBO customer = this.customerDao.findCustomerById(loan.getCustomer().getCustomerId());

                List<AccountFeesEntity> accountFeeEntities = new ArrayList<AccountFeesEntity>();

                Integer unpaidInstallments = loan.getDetailsOfUnpaidInstallmentsOn(paymentDate).size();

                Integer gracePeriodDiff = loan.getNoOfInstallments().intValue()
                        - loan.getGracePeriodDuration().intValue();

                Integer gracePeriodsRemaining = unpaidInstallments - gracePeriodDiff;

                LocalDate disbursementDate = new LocalDate(loan.getDetailsOfUpcomigInstallment().getActionDate());

                LoanProductOverridenDetail overridenDetail = new LoanProductOverridenDetail(totalPrincipalDueNow,
                        disbursementDate, loan.getInterestRate(), unpaidInstallments, gracePeriodsRemaining,
                        accountFeeEntities, new ArrayList<AccountPenaltiesEntity>());

                Integer interestDays = Integer.valueOf(AccountingRules.getNumberOfInterestDays().intValue());
                boolean loanScheduleIndependentOfCustomerMeetingEnabled = false;

                MeetingBO loanMeeting = customer.getCustomerMeetingValue();
                if (loanScheduleIndependentOfCustomerMeetingEnabled) {
                    RecurringSchedule createLoanSchedule = new MonthlyOnDayOfMonthSchedule(Integer.valueOf(1),
                            Integer.valueOf(5));
                    loanMeeting = this.createNewMeetingForRepaymentDay(disbursementDate, createLoanSchedule,
                            customer);

                    if (loanProduct.isVariableInstallmentsAllowed()) {
                        loanMeeting.setMeetingStartDate(disbursementDate.toDateMidnight().toDate());
                    }
                }

                LoanScheduleConfiguration configuration = new LoanScheduleConfiguration(
                        loanScheduleIndependentOfCustomerMeetingEnabled, interestDays);

                Short userBranchOfficeId = userContext.getBranchId();

                LoanSchedule loanSchedule = this.loanScheduleService.generate(loanProduct, customer, loanMeeting,
                        overridenDetail, configuration, userBranchOfficeId, accountFeeEntities, disbursementDate);

                loan.rescheduleRemainingUnpaidInstallments(loanSchedule, paymentDate);

                loan.recordOverpayment(outstandingOverpayment, paymentDate);
            }

            this.loanDao.save(loan);
            this.transactionHelper.commitTransaction();
        } catch (BusinessRuleException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getMessageKey(), e);
        } catch (AccountException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getKey(), e);
        } finally {
            this.transactionHelper.closeSession();
        }
    }

    @Override
    public List<LoanAccountDetailsDto> retrieveLoanAccountDetails(LoanInformationDto loanInformationDto) {

        List<LoanBO> individualLoans = this.loanDao.findIndividualLoans(loanInformationDto.getAccountId());
        List<ValueListElement> allLoanPurposes = this.loanProductDao.findAllLoanPurposes();

        List<LoanAccountDetailsDto> loanAccountDetailsViewList = new ArrayList<LoanAccountDetailsDto>();

        for (LoanBO individualLoan : individualLoans) {
            LoanAccountDetailsDto loandetails = new LoanAccountDetailsDto();
            loandetails.setClientId(individualLoan.getCustomer().getGlobalCustNum());
            loandetails.setClientName(individualLoan.getCustomer().getDisplayName());
            loandetails.setLoanAmount(null != individualLoan.getLoanAmount()
                    && !EMPTY.equals(individualLoan.getLoanAmount().toString())
                            ? individualLoan.getLoanAmount().toString()
                            : "0.0");
            loandetails.setLoanAccountId(individualLoan.getAccountId().toString());

            if (null != individualLoan.getBusinessActivityId()) {
                loandetails.setBusinessActivity(individualLoan.getBusinessActivityId().toString());
                for (ValueListElement busact : allLoanPurposes) {
                    if (busact.getId().toString().equals(individualLoan.getBusinessActivityId().toString())) {
                        loandetails.setBusinessActivityName(busact.getName());
                    }
                }
            }
            ClientBO client = this.customerDao
                    .findClientBySystemId(individualLoan.getCustomer().getGlobalCustNum());
            String governmentId = client.getGovernmentId();
            loandetails.setGovermentId(StringUtils.isNotBlank(governmentId) ? governmentId : "-");
            loanAccountDetailsViewList.add(loandetails);
        }
        return loanAccountDetailsViewList;
    }

    @Override
    public void disburseLoan(AccountPaymentParametersDto loanDisbursement, Short paymentTypeId) {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        LoanBO loan = this.loanDao.findById(loanDisbursement.getAccountId());

        try {
            personnelDao.checkAccessPermission(userContext, loan.getOfficeId(),
                    loan.getCustomer().getLoanOfficerId());
        } catch (AccountException e) {
            throw new MifosRuntimeException(e.getMessage(), e);
        }

        PaymentTypeDto paymentType = null;
        try {
            for (org.mifos.dto.domain.PaymentTypeDto paymentTypeDto : accountService.getLoanDisbursementTypes()) {
                if (paymentTypeDto.getValue() == paymentTypeId) {
                    paymentType = paymentTypeDto;
                }
            }
        } catch (Exception e) {
            throw new MifosRuntimeException(e.getMessage(), e);
        }

        if (paymentType == null) {
            throw new MifosRuntimeException("Expected loan PaymentTypeDto not found for id: " + paymentTypeId);
        }

        loanDisbursement.setPaymentType(paymentType);

        Date trxnDate = DateUtils
                .getDateWithoutTimeStamp(loanDisbursement.getPaymentDate().toDateMidnight().toDate());

        monthClosingServiceFacade.validateTransactionDate(trxnDate);

        if (!isTrxnDateValid(Integer.valueOf(loanDisbursement.getAccountId()), trxnDate)) {
            throw new BusinessRuleException("errors.invalidTxndate");
        }

        DateTime loanDisbursementDate = new DateTime(trxnDate);
        holidayServiceFacade.validateDisbursementDateForNewLoan(loan.getOfficeId(), loanDisbursementDate);

        if (loan.isFixedRepaymentSchedule()) {
            for (AccountActionDateEntity installment : loan.getAccountActionDates()) {
                if (installment.compareDate(trxnDate) <= 0) {
                    throw new BusinessRuleException("errors.invalidTxndateWhenDisbursalAfterFirstRepayment");
                }
            }
        }

        List<AccountPaymentParametersDto> loanDisbursements = new ArrayList<AccountPaymentParametersDto>();
        loanDisbursements.add(loanDisbursement);

        try {
            accountService.disburseLoans(loanDisbursements, userContext.getPreferredLocale());
        } catch (Exception e) {
            throw new MifosRuntimeException(e.getMessage(), e);
        }
    }

    @Override
    public ChangeAccountStatusDto retrieveAllActiveBranchesAndLoanOfficerDetails() {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        List<PersonnelDto> loanOfficers = new ArrayList<PersonnelDto>();
        List<OfficeDetailsDto> activeBranches = this.officeDao.findActiveBranches(userContext.getBranchId());
        if (onlyOneActiveBranchExists(activeBranches)) {
            OfficeDetailsDto singleOffice = activeBranches.get(0);
            CenterCreation officeDetails = new CenterCreation(singleOffice.getOfficeId(), userContext.getId(),
                    userContext.getLevelId(), userContext.getPreferredLocale());
            loanOfficers = this.personnelDao.findActiveLoanOfficersForOffice(officeDetails);
        }

        boolean loanPendingApprovalStateEnabled = ProcessFlowRules.isLoanPendingApprovalStateEnabled();
        Short accountState = AccountState.LOAN_PARTIAL_APPLICATION.getValue();
        if (loanPendingApprovalStateEnabled) {
            accountState = AccountState.LOAN_PENDING_APPROVAL.getValue();
        }

        boolean centerHierarchyExists = ClientRules.getCenterHierarchyExists();

        return new ChangeAccountStatusDto(activeBranches, loanOfficers, loanPendingApprovalStateEnabled,
                accountState, centerHierarchyExists);
    }

    private boolean onlyOneActiveBranchExists(List<OfficeDetailsDto> activeBranches) {
        return activeBranches.size() == 1;
    }

    @Override
    public ChangeAccountStatusDto retrieveLoanOfficerDetailsForBranch(Short officeId) {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        CenterCreation officeDetails = new CenterCreation(officeId, userContext.getId(), userContext.getLevelId(),
                userContext.getPreferredLocale());
        List<PersonnelDto> loanOfficers = this.personnelDao.findActiveLoanOfficersForOffice(officeDetails);

        boolean loanPendingApprovalStateEnabled = ProcessFlowRules.isLoanPendingApprovalStateEnabled();
        Short accountState = AccountState.LOAN_PARTIAL_APPLICATION.getValue();
        if (loanPendingApprovalStateEnabled) {
            accountState = AccountState.LOAN_PENDING_APPROVAL.getValue();
        }
        boolean centerHierarchyExists = ClientRules.getCenterHierarchyExists();

        return new ChangeAccountStatusDto(new ArrayList<OfficeDetailsDto>(), loanOfficers,
                loanPendingApprovalStateEnabled, accountState, centerHierarchyExists);
    }

    @Override
    public List<String> updateSeveralLoanAccountStatuses(List<AccountUpdateStatus> accountsForUpdate,
            Date transactionDate) {

        List<String> updatedAccountNumbers = new ArrayList<String>();
        for (AccountUpdateStatus accountUpdate : accountsForUpdate) {
            String accountNumber = updateLoanAccountStatus(accountUpdate, transactionDate);
            updatedAccountNumbers.add(accountNumber);
        }

        return updatedAccountNumbers;
    }

    @Override
    public List<LoanActivityDto> retrieveLoanPaymentsForReversal(String globalAccountNum) {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        OfficeBO userOffice = this.officeDao.findOfficeById(userContext.getBranchId());
        userContext.setOfficeLevelId(userOffice.getOfficeLevel().getValue());

        LoanBO loan = null;
        LoanBO searchedLoan = this.loanDao.findByGlobalAccountNum(globalAccountNum);
        if (searchedLoan != null && isAccountUnderUserScope(searchedLoan, userContext)) {
            loan = searchedLoan;
        }

        if (loan == null) {
            throw new BusinessRuleException(LoanConstants.NOSEARCHRESULTS);
        }

        if (!loan.isAccountActive()) {
            throw new BusinessRuleException(LoanConstants.NOSEARCHRESULTS);
        }

        return getApplicablePayments(loan);
    }

    private boolean isAccountUnderUserScope(LoanBO loan, UserContext userContext) {
        if (userContext.getLevelId().equals(PersonnelLevel.LOAN_OFFICER.getValue())) {
            return (loan.getPersonnel().getPersonnelId().equals(userContext.getId()));
        }

        if (userContext.getOfficeLevelId().equals(OfficeLevel.BRANCHOFFICE.getValue())) {
            return (loan.getOffice().getOfficeId().equals(userContext.getBranchId()));
        }

        OfficeBO userOffice = this.officeDao.findOfficeById(userContext.getBranchId());
        return (userOffice.isParent(loan.getOffice()));
    }

    private List<LoanActivityDto> getApplicablePayments(LoanBO loan) {

        List<LoanActivityDto> payments = new ArrayList<LoanActivityDto>();
        List<AccountPaymentEntity> accountPayments = loan.getAccountPayments();
        int i = accountPayments.size() - 1;
        if (accountPayments.size() > 0) {
            for (AccountPaymentEntity accountPayment : accountPayments) {
                if (accountPayment.getAmount().isGreaterThanZero()) {
                    Money amount = new Money(accountPayment.getAmount().getCurrency());
                    if (i == 0) {
                        for (AccountTrxnEntity accountTrxn : accountPayment.getAccountTrxns()) {
                            short accountActionTypeId = accountTrxn.getAccountActionEntity().getId().shortValue();
                            boolean isLoanRepayment = accountActionTypeId == AccountActionTypes.LOAN_REPAYMENT
                                    .getValue();
                            boolean isFeeRepayment = accountActionTypeId == AccountActionTypes.FEE_REPAYMENT
                                    .getValue();
                            if (isLoanRepayment || isFeeRepayment) {
                                amount = amount.add(accountTrxn.getAmount());
                            }
                        }
                    } else {
                        amount = accountPayment.getAmount();
                    }
                    if (amount.isGreaterThanZero()) {
                        LoanActivityDto loanActivityDto = new LoanActivityDto();
                        loanActivityDto.setActionDate(accountPayment.getPaymentDate());
                        loanActivityDto.setTotal(amount.toString());
                        loanActivityDto.setTotalValue(amount.getAmount().doubleValue());
                        payments.add(0, loanActivityDto);
                    }
                }
                i--;
            }
        }
        return payments;
    }

    @Override
    public void reverseLoanDisbursal(String globalAccountNum, String note) {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        LoanBO loan = this.loanDao.findByGlobalAccountNum(globalAccountNum);
        PersonnelBO personnel = this.personnelDao.findPersonnelById(userContext.getId());
        loan.updateDetails(userContext);

        try {
            this.transactionHelper.startTransaction();
            loan.reverseLoanDisbursal(personnel, note);
            this.loanDao.save(loan);
            this.transactionHelper.commitTransaction();
        } catch (BusinessRuleException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getMessageKey(), e);
        } catch (AccountException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getKey(), e);
        } finally {
            this.transactionHelper.closeSession();
        }
    }

    @Override
    public List<CustomerDto> retrieveActiveGroupingAtTopOfCustomerHierarchyForLoanOfficer(Short loanOfficerId,
            Short officeId) {

        CustomerLevel customerLevel = CustomerLevel.CENTER;
        if (!ClientRules.getCenterHierarchyExists()) {
            customerLevel = CustomerLevel.GROUP;
        }

        return this.customerDao.findTopOfHierarchyCustomersUnderLoanOfficer(customerLevel, loanOfficerId, officeId);
    }

    @Override
    public MultipleLoanAccountDetailsDto retrieveMultipleLoanAccountDetails(String searchId, Short branchId,
            Integer productId) {

        List<ClientBO> clients = this.customerDao.findActiveClientsUnderParent(searchId, branchId);
        if (clients.isEmpty()) {
            throw new BusinessRuleException(LoanConstants.NOSEARCHRESULTS);
        }

        LoanOfferingBO loanOffering = this.loanProductDao.findById(productId);

        // FIXME - Refactor MultipleLoanCreationDto into proper Dto
        List<MultipleLoanCreationDto> multipleLoanDetails = buildClientViewHelper(loanOffering, clients);

        List<ValueListElement> allLoanPruposes = this.loanProductDao.findAllLoanPurposes();
        boolean loanPendingApprovalStateEnabled = ProcessFlowRules.isLoanPendingApprovalStateEnabled();

        return new MultipleLoanAccountDetailsDto(allLoanPruposes, loanPendingApprovalStateEnabled);
    }

    private List<MultipleLoanCreationDto> buildClientViewHelper(final LoanOfferingBO loanOffering,
            List<ClientBO> clients) {
        return collect(clients, new Transformer<ClientBO, MultipleLoanCreationDto>() {
            @Override
            public MultipleLoanCreationDto transform(ClientBO client) {
                return new MultipleLoanCreationDto(client,
                        loanOffering.eligibleLoanAmount(client.getMaxLoanAmount(loanOffering),
                                client.getMaxLoanCycleForProduct(loanOffering)),
                        loanOffering.eligibleNoOfInstall(client.getMaxLoanAmount(loanOffering),
                                client.getMaxLoanCycleForProduct(loanOffering)),
                        loanOffering.getCurrency());
            }
        });
    }

    @Override
    public List<String> createMultipleLoans(List<CreateLoanRequest> multipleLoans) {

        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        OfficeBO userOffice = this.officeDao.findOfficeById(userContext.getBranchId());
        userContext.setBranchGlobalNum(userOffice.getGlobalOfficeNum());

        List<String> createdLoanAccountNumbers = new ArrayList<String>();
        for (CreateLoanRequest loanDetail : multipleLoans) {

            CustomerBO center = this.customerDao.findCustomerById(loanDetail.getCenterId());
            Short loanProductId = loanDetail.getLoanProductId();
            LoanOfferingBO loanProduct = this.loanProductDao.findById(loanProductId.intValue());

            List<QuestionGroupDetail> questionGroups = new ArrayList<QuestionGroupDetail>();
            LoanAccountCashFlow loanAccountCashFlow = null;

            BigDecimal loanAmount = BigDecimal.valueOf(Double.valueOf(loanDetail.getLoanAmount()));
            BigDecimal minAllowedLoanAmount = BigDecimal.valueOf(Double.valueOf(loanDetail.getMinLoanAmount()));
            BigDecimal maxAllowedLoanAmount = BigDecimal.valueOf(Double.valueOf(loanDetail.getMaxLoanAmount()));
            Double interestRate = loanProduct.getDefInterestRate();
            LocalDate disbursementDate = new LocalDate(center.getCustomerAccount().getNextMeetingDate());
            int numberOfInstallments = loanDetail.getDefaultNoOfInstall();
            int minAllowedNumberOfInstallments = loanDetail.getMinNoOfInstall();
            int maxAllowedNumberOfInstallments = loanDetail.getMaxNoOfInstall();

            int graceDuration = loanProduct.getGracePeriodDuration();
            boolean isRepaymentIndepOfMeetingEnabled = new ConfigurationBusinessService()
                    .isRepaymentIndepOfMeetingEnabled();

            Integer sourceOfFundId = null;
            Integer loanPurposeId = loanDetail.getLoanPurpose();
            Integer collateralTypeId = null;
            String collateralNotes = null;
            String externalId = null;
            RecurringSchedule recurringSchedule = null;
            List<CreateAccountFeeDto> accountFees = new ArrayList<CreateAccountFeeDto>();
            CreateLoanAccount loanAccountInfo = new CreateLoanAccount(loanDetail.getClientId(),
                    loanDetail.getLoanProductId().intValue(), loanDetail.getAccountStateId().intValue(), loanAmount,
                    minAllowedLoanAmount, maxAllowedLoanAmount, interestRate, disbursementDate, null,
                    numberOfInstallments, minAllowedNumberOfInstallments, maxAllowedNumberOfInstallments,
                    graceDuration, sourceOfFundId, loanPurposeId, collateralTypeId, collateralNotes, externalId,
                    isRepaymentIndepOfMeetingEnabled, recurringSchedule, accountFees,
                    new ArrayList<CreateAccountPenaltyDto>());

            LoanCreationResultDto result = this.createLoan(loanAccountInfo, questionGroups, loanAccountCashFlow);
            createdLoanAccountNumbers.add(result.getGlobalAccountNum());

            //            try {
            //                CustomerBO center = this.customerDao.findCustomerById(loanDetail.getCenterId());
            //
            //                Short loanProductId = loanDetail.getLoanProductId();
            //                LoanOfferingBO loanProduct = this.loanProductDao.findById(loanProductId.intValue());
            //                CustomerBO client = this.customerDao.findCustomerById(loanDetail.getClientId());
            //
            //                AccountState accountState = AccountState.fromShort(loanDetail.getAccountStateId());
            //                Money loanAmount = new Money(loanProduct.getCurrency(), loanDetail.getLoanAmount());
            //                Short defaultNumOfInstallments = loanDetail.getDefaultNoOfInstall();
            //                Date disbursementDate = center.getCustomerAccount().getNextMeetingDate();
            //                boolean interestDeductedAtDisbursement = loanProduct.isIntDedDisbursement();
            //                boolean isRepaymentIndepOfMeetingEnabled = new ConfigurationBusinessService().isRepaymentIndepOfMeetingEnabled();
            //
            //                MeetingBO newMeetingForRepaymentDay = null;
            //                if (isRepaymentIndepOfMeetingEnabled) {
            //                    MeetingBO meeting = center.getCustomerAccount().getMeetingForAccount();
            //                    LoanAccountMeetingDto loanAccountMeetingDto = null;
            //
            //                    if (meeting.isWeekly()) {
            //                        loanAccountMeetingDto = new LoanAccountMeetingDto(RecurrenceType.WEEKLY.getValue().toString(),
            //                                meeting.getMeetingDetails().getWeekDay().getValue().toString(),
            //                                meeting.getMeetingDetails().getRecurAfter().toString(),
            //                                null, null, null, null, null, null);
            //                    } else if (meeting.isMonthly()) {
            //                        if (meeting.isMonthlyOnDate()) {
            //                            loanAccountMeetingDto = new LoanAccountMeetingDto(RecurrenceType.MONTHLY.getValue().toString(),
            //                                    null, null, "1",
            //                                    meeting.getMeetingDetails().getDayNumber().toString(),
            //                                    meeting.getMeetingDetails().getRecurAfter().toString(),
            //                                    null, null, null);
            //                        } else {
            //                            loanAccountMeetingDto = new LoanAccountMeetingDto(RecurrenceType.MONTHLY.getValue().toString(),
            //                                    null, null, "2", null, null,
            //                                    meeting.getMeetingDetails().getWeekDay().getValue().toString(),
            //                                    meeting.getMeetingDetails().getRecurAfter().toString(),
            //                                    meeting.getMeetingDetails().getWeekRank().getValue().toString());
            //                        }
            //                    }
            //
            //                    newMeetingForRepaymentDay = this.createNewMeetingForRepaymentDay(new LocalDate(disbursementDate), loanAccountMeetingDto, client);
            //                }
            //
            //                checkPermissionForCreate(accountState.getValue(), userContext, userContext.getBranchId(), userContext.getId());
            //
            //                List<FeeDto> additionalFees = new ArrayList<FeeDto>();
            //                List<FeeDto> defaultFees = new ArrayList<FeeDto>();
            //
            //                new LoanProductService().getDefaultAndAdditionalFees(loanProductId, userContext, defaultFees, additionalFees);
            //
            //                FundBO fund = null;
            //                List<CustomFieldDto> customFields = new ArrayList<CustomFieldDto>();
            //                boolean isRedone = false;
            //
            //                // FIXME - keithw - tidy up constructor and use domain concepts rather than primitives, e.g. money v double, loanpurpose v integer.
            //
            //                LoanBO loan = new LoanBO(userContext, loanProduct, client, accountState, loanAmount, defaultNumOfInstallments,
            //                        disbursementDate, interestDeductedAtDisbursement, interestRate, gracePeriodDuration, fund, defaultFees,
            //                        customFields, isRedone, maxLoanAmount, minLoanAmount,
            //                        loanProduct.getMaxInterestRate(), loanProduct.getMinInterestRate(),
            //                        maxNoOfInstall, minNoOfInstall, isRepaymentIndepOfMeetingEnabled, newMeetingForRepaymentDay);
            //                loan.setBusinessActivityId(loanDetail.getLoanPurpose());
            //
            //                PersonnelBO loggedInUser = this.personnelDao.findPersonnelById(userContext.getId());
            //                AccountStateEntity newAccountState = new AccountStateEntity(accountState);
            //                AccountStatusChangeHistoryEntity statusChange = new AccountStatusChangeHistoryEntity(null, newAccountState, loggedInUser, loan);
            //
            //                this.transactionHelper.startTransaction();
            //                loan.addAccountStatusChangeHistory(statusChange);
            //                this.loanDao.save(loan);
            //                this.transactionHelper.flushSession();
            //                String globalAccountNum = loan.generateId(userContext.getBranchGlobalNum());
            //                loan.setGlobalAccountNum(globalAccountNum);
            //                this.loanDao.save(loan);
            //                this.transactionHelper.commitTransaction();
            //
            //                createdLoanAccountNumbers.add(loan.getGlobalAccountNum());
            //            } catch (ServiceException e) {
            //                this.transactionHelper.rollbackTransaction();
            //                throw new MifosRuntimeException(e);
            //            } catch (PersistenceException e) {
            //                this.transactionHelper.rollbackTransaction();
            //                throw new MifosRuntimeException(e);
            //            } catch (AccountException e) {
            //                this.transactionHelper.rollbackTransaction();
            //                throw new BusinessRuleException(e.getKey(), e);
            //            }
        }

        return createdLoanAccountNumbers;
    }

    private void checkPermissionForCreate(Short newState, UserContext userContext, Short officeId,
            Short loanOfficerId) {
        if (!isPermissionAllowed(newState, userContext, officeId, loanOfficerId)) {
            throw new BusinessRuleException(SecurityConstants.KEY_ACTIVITY_NOT_ALLOWED);
        }
    }

    private void checkPermission(UserContext userContext, Short officeId, Short loanOfficerId) {

    }

    private boolean isPermissionAllowed(final Short newSate, final UserContext userContext, final Short officeId,
            final Short loanOfficerId) {
        return legacyRolesPermissionsDao.isActivityAllowed(userContext, new ActivityContext(
                ActivityMapper.getInstance().getActivityIdForState(newSate), officeId, loanOfficerId));
    }

    @Override
    public List<CustomerSearchResultDto> retrieveCustomersThatQualifyForLoans(CustomerSearchDto customerSearchDto) {

        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);

        try {
            List<CustomerSearchResultDto> pagedDetails = new ArrayList<CustomerSearchResultDto>();

            QueryResult customerForSavings = new CustomerPersistence()
                    .searchGroupClient(customerSearchDto.getSearchTerm(), userContext.getId());

            int position = (customerSearchDto.getPage() - 1) * customerSearchDto.getPageSize();
            List<AccountSearchResultsDto> pagedResults = customerForSavings.get(position,
                    customerSearchDto.getPageSize());
            int i = 1;
            for (AccountSearchResultsDto customerBO : pagedResults) {
                CustomerSearchResultDto customer = new CustomerSearchResultDto();
                customer.setCustomerId(customerBO.getClientId());
                customer.setBranchName(customerBO.getOfficeName());
                customer.setGlobalId(customerBO.getGlobelNo());
                customer.setSearchIndex(i);

                customer.setCenterName(StringUtils.defaultIfEmpty(customerBO.getCenterName(), "--"));
                customer.setGroupName(StringUtils.defaultIfEmpty(customerBO.getGroupName(), "--"));
                customer.setClientName(customerBO.getClientName());

                pagedDetails.add(customer);
                i++;
            }
            return pagedDetails;
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e);
        } catch (HibernateSearchException e) {
            throw new MifosRuntimeException(e);
        } catch (ConfigurationException e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public Errors validateLoanDisbursementDate(LocalDate loanDisbursementDate, Integer customerId,
            Integer productId) {

        Errors errors = new Errors();

        if (loanDisbursementDate.isBefore(new LocalDate())) {
            String[] args = { "" };
            errors.addError("dibursementdate.cannot.be.before.todays.date", args);
        }

        CustomerBO customer = this.customerDao.findCustomerById(customerId);
        LocalDate customerActivationDate = new LocalDate(customer.getCustomerActivationDate());
        if (loanDisbursementDate.isBefore(customerActivationDate)) {
            String[] args = { customerActivationDate.toString("dd-MMM-yyyy") };
            errors.addError("dibursementdate.before.customer.activation.date", args);
        }

        LoanOfferingBO loanProduct = this.loanProductDao.findById(productId);
        LocalDate productStartDate = new LocalDate(loanProduct.getStartDate());
        if (loanDisbursementDate.isBefore(productStartDate)) {
            String[] args = { productStartDate.toString("dd-MMM-yyyy") };
            errors.addError("dibursementdate.before.product.startDate", args);
        }

        try {
            this.holidayServiceFacade.validateDisbursementDateForNewLoan(customer.getOfficeId(),
                    loanDisbursementDate.toDateMidnight().toDateTime());
        } catch (BusinessRuleException e) {
            String[] args = { "" };
            errors.addError("dibursementdate.falls.on.holiday", args);
        }

        boolean isRepaymentIndependentOfMeetingEnabled = new ConfigurationBusinessService()
                .isRepaymentIndepOfMeetingEnabled();

        LoanDisbursementDateFactory loanDisbursementDateFactory = new LoanDisbursmentDateFactoryImpl();
        LoanDisbursementDateValidator loanDisbursementDateFinder = loanDisbursementDateFactory.create(customer,
                loanProduct, isRepaymentIndependentOfMeetingEnabled, false);

        boolean isValid = loanDisbursementDateFinder
                .isDisbursementDateValidInRelationToSchedule(loanDisbursementDate);
        if (!isValid) {
            String[] args = { "" };
            errors.addError("dibursementdate.invalid.in.relation.to.meeting.schedule", args);
        }

        return errors;
    }

    @Override
    public Errors validateLoanWithBackdatedPaymentsDisbursementDate(LocalDate loanDisbursementDate,
            Integer customerId, Integer productId) {
        Errors errors = new Errors();

        if (!loanDisbursementDate.isBefore(new LocalDate())) {
            String[] args = { "" };
            errors.addError("dibursementdate.before.todays.date", args);
        }

        CustomerBO customer = this.customerDao.findCustomerById(customerId);
        LocalDate customerActivationDate = new LocalDate(customer.getCustomerActivationDate());
        if (loanDisbursementDate.isBefore(customerActivationDate)) {
            String[] args = { customerActivationDate.toString("dd-MMM-yyyy") };
            errors.addError("dibursementdate.before.customer.activation.date", args);
        }

        LoanOfferingBO loanProduct = this.loanProductDao.findById(productId);
        LocalDate productStartDate = new LocalDate(loanProduct.getStartDate());
        if (loanDisbursementDate.isBefore(productStartDate)) {
            String[] args = { productStartDate.toString("dd-MMM-yyyy") };
            errors.addError("dibursementdate.before.product.startDate", args);
        }

        try {
            this.holidayServiceFacade.validateDisbursementDateForNewLoan(customer.getOfficeId(),
                    loanDisbursementDate.toDateMidnight().toDateTime());
        } catch (BusinessRuleException e) {
            String[] args = { "" };
            errors.addError("dibursementdate.falls.on.holiday", args);
        }

        return errors;
    }

    @Override
    public LoanApplicationStateDto retrieveLoanApplicationState() {

        AccountState applicationState = AccountState.LOAN_APPROVED;
        boolean loanPendingApprovalEnabled = ProcessFlowRules.isLoanPendingApprovalStateEnabled();
        if (loanPendingApprovalEnabled) {
            applicationState = AccountState.LOAN_PENDING_APPROVAL;
        }

        return new LoanApplicationStateDto(AccountState.LOAN_PARTIAL_APPLICATION.getValue().intValue(),
                applicationState.getValue().intValue());
    }

    @Override
    public List<QuestionGroupDetail> retrieveApplicableQuestionGroups(Integer productId) {

        List<QuestionGroupDetail> questionGroupDetails = new ArrayList<QuestionGroupDetail>();
        LoanOfferingBO loanProduct = this.loanProductDao.findById(productId);
        if (!loanProduct.getQuestionGroups().isEmpty()) {
            List<QuestionGroupDetail> allQuestionGroupDetails = questionnaireServiceFacade
                    .getQuestionGroups("Create", "Loan");
            for (QuestionGroupDetail questionGroupDetail : allQuestionGroupDetails) {
                if (questionGroupDetail.isActive()) {
                    questionGroupDetails.add(questionGroupDetail);
                }
            }
        }

        return questionGroupDetails;
    }

    @Override
    public CashFlowDto retrieveCashFlowSettings(DateTime firstInstallment, DateTime lastInstallment,
            Integer productId, BigDecimal loanAmount) {
        LoanOfferingBO loanProduct = this.loanProductDao.findById(productId);
        return new CashFlowDto(firstInstallment, lastInstallment,
                loanProduct.shouldCaptureCapitalAndLiabilityInformation(), loanAmount,
                loanProduct.getIndebtednessRatio(), loanProduct.getRepaymentCapacity());
    }

    @Override
    public List<CashFlowDataDto> retrieveCashFlowSummary(List<MonthlyCashFlowDto> monthlyCashFlow,
            LoanScheduleDto loanScheduleDto) {

        List<CashFlowDataDto> cashflowdetails = new ArrayList<CashFlowDataDto>();

        for (MonthlyCashFlowDto monthlyCashflowform : monthlyCashFlow) {

            CashFlowDataDto cashflowDataDto = new CashFlowDataDto();
            cashflowDataDto.setMonth(monthlyCashflowform.getMonthDate().monthOfYear().getAsText());
            cashflowDataDto.setYear(String.valueOf(monthlyCashflowform.getMonthDate().getYear()));

            cashflowDataDto.setCumulativeCashFlow(String.valueOf(monthlyCashflowform.getCumulativeCashFlow()
                    .setScale(AccountingRules.getDigitsAfterDecimal(), BigDecimal.ROUND_HALF_UP)));
            cashflowDataDto.setMonthYear(monthlyCashflowform.getMonthDate().toDate());
            cashflowDataDto.setNotes(monthlyCashflowform.getNotes());

            String cumulativeCashflowAndInstallment = computeDiffBetweenCumulativeAndInstallment(
                    monthlyCashflowform.getMonthDate(), monthlyCashflowform.getCumulativeCashFlow(),
                    loanScheduleDto);
            cashflowDataDto.setDiffCumulativeCashflowAndInstallment(cumulativeCashflowAndInstallment);
            String cashflowAndInstallmentPercent = computeDiffBetweenCumulativeAndInstallmentPercent(
                    monthlyCashflowform.getMonthDate(), monthlyCashflowform.getCumulativeCashFlow(),
                    loanScheduleDto);
            cashflowDataDto.setDiffCumulativeCashflowAndInstallmentPercent(cashflowAndInstallmentPercent);

            cashflowdetails.add(cashflowDataDto);
        }

        return cashflowdetails;
    }

    private String computeDiffBetweenCumulativeAndInstallment(DateTime dateOfCashFlow, BigDecimal cashflow,
            LoanScheduleDto loanScheduleDto) {
        BigDecimal totalInstallmentForMonth = cumulativeTotalForMonth(dateOfCashFlow, loanScheduleDto);
        return String.valueOf(cashflow.subtract(totalInstallmentForMonth)
                .setScale(AccountingRules.getDigitsAfterDecimal(), RoundingMode.HALF_UP));
    }

    private BigDecimal cumulativeTotalForMonth(DateTime dateOfCashFlow, LoanScheduleDto loanScheduleDto) {
        BigDecimal value = new BigDecimal(0).setScale(2, BigDecimal.ROUND_HALF_UP);
        for (LoanCreationInstallmentDto repaymentScheduleInstallment : loanScheduleDto.getInstallments()) {

            DateTime dueDate = new DateTime(repaymentScheduleInstallment.getDueDate());

            if (dueDate.getMonthOfYear() == dateOfCashFlow.getMonthOfYear()
                    && (dueDate.getYear() == dateOfCashFlow.getYear())) {
                value = value.add(BigDecimal.valueOf(repaymentScheduleInstallment.getTotal()));
            }
        }
        return value;
    }

    private String computeDiffBetweenCumulativeAndInstallmentPercent(DateTime dateOfCashFlow, BigDecimal cashflow,
            LoanScheduleDto loanScheduleDto) {
        BigDecimal totalInstallmentForMonth = cumulativeTotalForMonth(dateOfCashFlow, loanScheduleDto);
        String value;
        if (cashflow.doubleValue() != 0) {
            value = String.valueOf(totalInstallmentForMonth.multiply(BigDecimal.valueOf(100)).divide(cashflow,
                    AccountingRules.getDigitsAfterDecimal(), RoundingMode.HALF_UP));
        } else {
            value = "Infinity";
        }
        return value;
    }

    @Override
    public Errors validateCashFlowForInstallmentsForWarnings(List<CashFlowDataDto> cashFlowDataDtos,
            Integer productId) {
        Errors errors = new Errors();
        LoanOfferingBO loanOfferingBO = this.loanProductDao.findById(productId);
        if (loanOfferingBO.shouldValidateCashFlowForInstallments()) {
            CashFlowDetail cashFlowDetail = loanOfferingBO.getCashFlowDetail();
            if (CollectionUtils.isNotEmpty(cashFlowDataDtos)) {
                for (CashFlowDataDto cashflowDataDto : cashFlowDataDtos) {
                    validateCashFlow(errors, cashFlowDetail.getCashFlowThreshold(), cashflowDataDto);
                }
            }
        }
        return errors;
    }

    private void validateCashFlow(Errors errors, Double cashFlowThreshold, CashFlowDataDto cashflowDataDto) {
        String cashFlowAndInstallmentDiffPercent = cashflowDataDto.getDiffCumulativeCashflowAndInstallmentPercent();
        String monthYearAsString = cashflowDataDto.getMonthYearAsString();
        String cumulativeCashFlow = cashflowDataDto.getCumulativeCashFlow();
        if (StringUtils.isNotEmpty(cashFlowAndInstallmentDiffPercent)
                && Double.valueOf(cashFlowAndInstallmentDiffPercent) > cashFlowThreshold) {
            errors.addError(AccountConstants.BEYOND_CASHFLOW_THRESHOLD,
                    new String[] { monthYearAsString, cashFlowThreshold.toString() });
        }
        if (StringUtils.isNotEmpty(cumulativeCashFlow)) {
            Double cumulativeCashFlowValue = Double.valueOf(cumulativeCashFlow);
            if (cumulativeCashFlowValue < 0) {
                errors.addError(AccountConstants.CUMULATIVE_CASHFLOW_NEGATIVE, new String[] { monthYearAsString });
            } else if (cumulativeCashFlowValue == 0) {
                errors.addError(AccountConstants.CUMULATIVE_CASHFLOW_ZERO, new String[] { monthYearAsString });
            }
        }
    }

    @Override
    public Errors validateCashFlowForInstallments(LoanInstallmentsDto loanInstallmentsDto,
            List<MonthlyCashFlowDto> monthlyCashFlows, Double repaymentCapacity, BigDecimal cashFlowTotalBalance) {

        Errors errors = new Errors();
        if (CollectionUtils.isNotEmpty(monthlyCashFlows)) {

            boolean lowerBound = DateUtils.firstLessOrEqualSecond(monthlyCashFlows.get(0).getMonthDate().toDate(),
                    loanInstallmentsDto.getFirstInstallmentDueDate());
            boolean upperBound = DateUtils.firstLessOrEqualSecond(loanInstallmentsDto.getLastInstallmentDueDate(),
                    monthlyCashFlows.get(monthlyCashFlows.size() - 1).getMonthDate().toDate());

            SimpleDateFormat df = new SimpleDateFormat("MMMM yyyy", Locale.UK);

            if (!lowerBound) {
                errors.addError(AccountConstants.INSTALLMENT_BEYOND_CASHFLOW_DATE,
                        new String[] { df.format(loanInstallmentsDto.getFirstInstallmentDueDate()) });
            }

            if (!upperBound) {
                errors.addError(AccountConstants.INSTALLMENT_BEYOND_CASHFLOW_DATE,
                        new String[] { df.format(loanInstallmentsDto.getLastInstallmentDueDate()) });
            }
        }
        validateForRepaymentCapacity(loanInstallmentsDto.getTotalInstallmentAmount(),
                loanInstallmentsDto.getLoanAmount(), repaymentCapacity, errors, cashFlowTotalBalance);
        return errors;
    }

    private void validateForRepaymentCapacity(BigDecimal totalInstallmentAmount, BigDecimal loanAmount,
            Double repaymentCapacity, Errors errors, BigDecimal totalBalance) {
        if (repaymentCapacity == null || repaymentCapacity == 0) {
            return;
        }
        Double calculatedRepaymentCapacity = totalBalance.multiply(CashFlowConstants.HUNDRED)
                .divide(totalInstallmentAmount, 2, BigDecimal.ROUND_HALF_UP).doubleValue();
        if (calculatedRepaymentCapacity < repaymentCapacity) {
            errors.addError(AccountConstants.REPAYMENT_CAPACITY_LESS_THAN_ALLOWED,
                    new String[] { calculatedRepaymentCapacity.toString(), repaymentCapacity.toString() });
        }
    }

    @Override
    public boolean isCompareWithCashFlowEnabledOnProduct(Integer productId) {
        LoanOfferingBO loanProduct = this.loanProductDao.findById(productId);
        return loanProduct.isCashFlowCheckEnabled();
    }

    @Override
    public Errors validateInputInstallments(Date disbursementDate, Integer minGapInDays, Integer maxGapInDays,
            BigDecimal minInstallmentAmount, List<LoanCreationInstallmentDto> dtoInstallments, Integer customerId) {
        Short officeId = customerDao.findCustomerById(customerId).getOfficeId();
        VariableInstallmentDetailsBO variableInstallmentDetails = new VariableInstallmentDetailsBO();
        variableInstallmentDetails.setMinGapInDays(minGapInDays);
        variableInstallmentDetails.setMaxGapInDays(maxGapInDays);
        InstallmentValidationContext context = new InstallmentValidationContext(disbursementDate,
                variableInstallmentDetails, minInstallmentAmount, holidayServiceFacade, officeId);

        MifosCurrency currency = Money.getDefaultCurrency();
        List<RepaymentScheduleInstallment> installments = new ArrayList<RepaymentScheduleInstallment>();

        for (LoanCreationInstallmentDto dto : dtoInstallments) {
            Money principal = new Money(currency, dto.getPrincipal());
            Money interest = new Money(currency, dto.getInterest());
            Money fees = new Money(currency, dto.getFees());
            Money miscFees = new Money(currency);
            Money miscPenalty = new Money(currency);
            RepaymentScheduleInstallment installment = new RepaymentScheduleInstallment(dto.getInstallmentNumber(),
                    dto.getDueDate(), principal, interest, fees, miscFees, miscPenalty);
            installment.setTotalAndTotalValue(new Money(currency, dto.getTotal()));

            installments.add(installment);
        }

        return installmentsValidator.validateInputInstallments(installments, context);
    }

    @Override
    public Errors validateInstallmentSchedule(List<LoanCreationInstallmentDto> dtoInstallments,
            BigDecimal minInstallmentAmount) {

        MifosCurrency currency = Money.getDefaultCurrency();
        List<RepaymentScheduleInstallment> installments = new ArrayList<RepaymentScheduleInstallment>();

        for (LoanCreationInstallmentDto dto : dtoInstallments) {
            Money principal = new Money(currency, dto.getPrincipal());
            Money interest = new Money(currency, dto.getInterest());
            Money fees = new Money(currency, dto.getFees());
            Money miscFees = new Money(currency);
            Money miscPenalty = new Money(currency);
            RepaymentScheduleInstallment installment = new RepaymentScheduleInstallment(dto.getInstallmentNumber(),
                    dto.getDueDate(), principal, interest, fees, miscFees, miscPenalty);
            installment.setTotalAndTotalValue(new Money(currency, dto.getTotal()));

            installments.add(installment);
        }

        return installmentsValidator.validateInstallmentSchedule(installments, minInstallmentAmount);
    }

    @Override
    public VariableInstallmentWithFeeValidationResult validateFeeCanBeAppliedToVariableInstallmentLoan(Long feeId) {

        try {
            org.mifos.dto.domain.FeeDto fee = this.feeDao.findDtoById(feeId.shortValue());
            boolean feeCanBeAppliedToVariableInstallmentLoan = true;
            String feeName = fee.getName();
            if (fee.isPeriodic()) {
                feeCanBeAppliedToVariableInstallmentLoan = false;
            } else if (fee.isRateBasedFee()) {
                FeeFormula formula = FeeFormula.getFeeFormula(fee.getFeeFormula().getId());
                switch (formula) {
                case AMOUNT_AND_INTEREST:
                    feeCanBeAppliedToVariableInstallmentLoan = false;
                    break;
                case INTEREST:
                    feeCanBeAppliedToVariableInstallmentLoan = false;
                    break;
                default:
                    break;
                }
            }
            return new VariableInstallmentWithFeeValidationResult(feeCanBeAppliedToVariableInstallmentLoan,
                    feeName);
        } catch (PropertyNotFoundException e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public OverpaymentDto retrieveOverpayment(String overpaymentId) {
        try {
            return accountService.getOverpayment(overpaymentId);
        } catch (Exception e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public void applyOverpaymentClear(String overpaymentId, BigDecimal overpaymentAmount) {
        try {
            this.transactionHelper.startTransaction();
            AccountOverpaymentEntity overpaymentEntity = legacyAccountDao
                    .findOverpaymentById(Integer.valueOf(overpaymentId));

            overpaymentEntity.clearOverpayment(overpaymentAmount);

            legacyAccountDao.save(overpaymentEntity);
            this.transactionHelper.commitTransaction();
        } catch (BusinessRuleException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getMessageKey(), e);
        } catch (PersistenceException e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } finally {
            this.transactionHelper.closeSession();
        }
    }

    @Override
    public void putLoanBusinessKeyInSession(String globalAccountNum, HttpServletRequest request) {
        LoanBO loanBO = this.loanDao.findByGlobalAccountNum(globalAccountNum);
        try {
            SessionUtils.removeThenSetAttribute(Constants.BUSINESS_KEY, loanBO, request);
        } catch (PageExpiredException e) {
            throw new MifosRuntimeException(e);
        }
    }

}