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.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
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.AccountBO;
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.AccountPenaltiesEntity;
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.service.AccountBusinessService;
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.fees.util.helpers.RateAmountFlag;
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.OriginalLoanScheduleEntity;
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.LoanOfferingFeesEntity;
import org.mifos.accounts.productdefinition.business.LoanOfferingFundEntity;
import org.mifos.accounts.productdefinition.business.LoanOfferingInstallmentRange;
import org.mifos.accounts.productdefinition.business.QuestionGroupReference;
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.business.MeetingDetailsEntity;
import org.mifos.application.meeting.business.MeetingFactory;
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.application.util.helpers.AccountPaymentDtoComperator;
import org.mifos.clientportfolio.loan.service.CreateLoanSchedule;
import org.mifos.clientportfolio.loan.service.MonthlyOnDayOfMonthSchedule;
import org.mifos.clientportfolio.loan.service.MonthlyOnWeekOfMonthSchedule;
import org.mifos.clientportfolio.loan.service.RecurringSchedule;
import org.mifos.clientportfolio.loan.service.WeeklySchedule;
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.AccountPaymentDto.AmountWithInterest;
import org.mifos.dto.domain.AccountPaymentParametersDto;
import org.mifos.dto.domain.AccountStatusDto;
import org.mifos.dto.domain.AccountUpdateStatus;
import org.mifos.dto.domain.ApplicableCharge;
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.FeeDto;
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.OriginalScheduleInfoDto;
import org.mifos.dto.domain.OverpaymentDto;
import org.mifos.dto.domain.PaymentDto;
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.RepaymentScheduleInstallmentDto;
import org.mifos.dto.domain.SavingsAccountDetailDto;
import org.mifos.dto.domain.SavingsDetailDto;
import org.mifos.dto.domain.SavingsWithdrawalDto;
import org.mifos.dto.domain.SurveyDto;
import org.mifos.dto.domain.ValueListElement;
import org.mifos.dto.screen.AccountFeesDto;
import org.mifos.dto.screen.AccountPaymentDto;
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.dto.screen.UploadedFileDto;
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.fileupload.service.LoanFileService;
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 static final Integer PARENT_LOAN = 1;
    private static final Integer MEMBER_LOAN = 0;
    private static final Integer OTHER_LOAN = -1;

    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 HibernateTransactionHelper transactionHelper;
    private final MonthClosingServiceFacade monthClosingServiceFacade;
    private final CustomerPersistence customerPersistence;
    private final ConfigurationPersistence configurationPersistence;
    private final ClientServiceFacade clientServiceFacade;
    private final SavingsServiceFacade savingsServiceFacade;

    @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
    private LoanFileService loanFileService;

    @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, CustomerPersistence customerPersistence,
            ConfigurationPersistence configurationPersistence, ClientServiceFacade clientServiceFacade,
            SavingsServiceFacade savingsServiceFacade) {
        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;
        this.customerPersistence = customerPersistence;
        this.configurationPersistence = configurationPersistence;
        this.clientServiceFacade = clientServiceFacade;
        this.savingsServiceFacade = savingsServiceFacade;
    }

    public void setTransactionHelper(HibernateTransactionHelper transactionHelper) {
        this.transactionHelper = transactionHelper;
    }

    @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 = configurationPersistence.isGlimEnabled();
        final boolean isLsimEnabled = configurationPersistence.isRepaymentIndepOfMeetingEnabled();
        final boolean isGroupLoanWithMembersEnabled = AccountingRules.isGroupLoanWithMembers();

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

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

        Errors errors = new Errors();
        List<ErrorEntry> errorEntry = new ArrayList<ErrorEntry>();
        if (isGroup && isGlimEnabled && isGroupLoanWithMembersEnabled) {
            String defaultMessage = "New Group Loan option is enabled in configuration file and also GLIM option is enabled in the database. Loan for Group will be created using new Group Loan approach. Please turn off GLIM option in database. If you want to create Loan for Group using old approach, then please disable AccountingRules.GroupLoanWithMembers option in configuration file and restart Mifos.";
            errorEntry.add(new ErrorEntry("createLoanAccount.glim.and.new.glim.are.enabled", "activeClients2",
                    defaultMessage));
            errors.addErrors(errorEntry);
        }

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

            if (activeClientsOfGroup == null || activeClientsOfGroup.size() < 2) {
                errorEntry.clear();
                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
                        .add(new ErrorEntry("createLoanAccount.glim.invalid.less.than.two.active.clients.in.group",
                                "activeClients", defaultMessage));
                errors.addErrors(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 = null;
            LocalDate nextPossibleDisbursementDate = null;
            MeetingBO customerMeeting = customer.getCustomerMeetingValue();
            LocalDate dateToStart = new LocalDate();

            if (customerMeeting.isWeekly()) {

                LocalDate meetingStartDate = new LocalDate(customerMeeting.getMeetingStartDate());

                if (dateToStart.isBefore(meetingStartDate)) {
                    dateToStart = meetingStartDate;
                    loanDisbursementDateFinder = loanDisbursementDateFactory.create(customer, loanProduct, false,
                            isLoanWithBackdatedPayments);
                } else {
                    loanDisbursementDateFinder = loanDisbursementDateFactory.create(customer, loanProduct,
                            isRepaymentIndependentOfMeetingEnabled, isLoanWithBackdatedPayments);
                }

                nextPossibleDisbursementDate = loanDisbursementDateFinder
                        .findClosestMatchingDateFromAndInclusiveOf(dateToStart);
            } else {
                loanDisbursementDateFinder = loanDisbursementDateFactory.create(customer, loanProduct,
                        isRepaymentIndependentOfMeetingEnabled, isLoanWithBackdatedPayments);
                nextPossibleDisbursementDate = loanDisbursementDateFinder
                        .findClosestMatchingDateFromAndInclusiveOf(dateToStart);
            }

            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 = configurationPersistence.isGlimEnabled();

            //Group Loan Account with members specific
            final boolean isGroupLoanWithMembersEnabled = AccountingRules.isGroupLoanWithMembers();

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

            if (isGroup && (isGlimEnabled || isGroupLoanWithMembersEnabled)) {
                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, isGroupLoanWithMembersEnabled);

        } 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 = 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 = null;
        if (loanScheduleIndependentOfCustomerMeetingEnabled) {
            loanMeeting = this.createNewMeetingForRepaymentDay(createLoanSchedule.getDisbursementDate(),
                    createLoanSchedule, customer);
            if (loanProduct.isVariableInstallmentsAllowed()) {
                loanMeeting.setMeetingStartDate(createLoanSchedule.getDisbursementDate().toDateMidnight().toDate());
            }
        } else {
            MeetingDto customerMeetingDto = customer.getCustomerMeetingValue().toDto();
            loanMeeting = new MeetingFactory().create(customerMeetingDto);

            Short recurAfter = loanProduct.getLoanOfferingMeeting().getMeeting().getRecurAfter();
            loanMeeting.getMeetingDetails().setRecurAfter(recurAfter);
        }
        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 = null;
        if (loanScheduleIndependentOfCustomerMeetingEnabled) {
            repaymentDayMeeting = this.createNewMeetingForRepaymentDay(loanAccountInfo.getDisbursementDate(),
                    loanAccountInfo, loanAccountDetail.getCustomer());
        } else {
            MeetingDto customerMeetingDto = customer.getCustomerMeetingValue().toDto();
            repaymentDayMeeting = new MeetingFactory().create(customerMeetingDto);

            Short recurAfter = loanAccountDetail.getLoanProduct().getLoanOfferingMeeting().getMeeting()
                    .getRecurAfter();
            repaymentDayMeeting.getMeetingDetails().setRecurAfter(recurAfter);
        }

        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();
        }
        //set up predefined loan account for importing loans
        if (loanAccountInfo.getPredefinedAccountNumber() != null) {
            loan.setGlobalAccountNum(loanAccountInfo.getPredefinedAccountNumber());
        }

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

        try {
            transactionHelper.startTransaction();
            this.loanDao.save(loan);
            transactionHelper.flushSession();
            //no predefined account number, generate one instead
            if (loanAccountInfo.getPredefinedAccountNumber() == null) {
                try {
                    loan.setGlobalAccountNum(loan.generateId(userOffice.getGlobalOfficeNum()));
                } catch (AccountException e) {
                    throw new BusinessRuleException(e.getMessage());
                }
                this.loanDao.save(loan);
                transactionHelper.flushSession();
            }
            //set up status flag
            AccountStateFlagEntity flagEntity = null;
            if (loanAccountInfo.getFlagId() != null) {
                try {
                    flagEntity = legacyMasterDao.getPersistentObject(AccountStateFlagEntity.class,
                            loanAccountInfo.getFlagId());
                    loan.setUserContext(userContext);
                    loan.setFlag(flagEntity);
                    loan.setClosedDate(new DateTimeService().getCurrentJavaDateTime());
                    loan.setUserContext(userContext);
                } catch (PersistenceException 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));
                }

                int memberCount = memberDetails.size();
                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()) / memberCount);
                    }

                    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);

            List<LoanBO> memberLoans = new ArrayList<LoanBO>(); //for original schedule persisting
            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();

                memberLoans.add(memberLoan);
            }

            // 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);

                this.loanBusinessService.persistOriginalSchedule(loan);
                for (LoanBO memberLoan : memberLoans) {
                    this.loanBusinessService.persistOriginalSchedule(memberLoan);
                }

                // refactoring of loan disbursal
                if (customer
                        .isDisbursalPreventedDueToAnyExistingActiveLoansForTheSameProduct(loan.getLoanOffering())) {
                    throw new BusinessRuleException("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 = 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);
                }
            } else {
                Short recurEvery = recurringSchedule.getEvery().shortValue();
                newMeetingForRepaymentDay = new MeetingBO(recurEvery, repaymentStartDate,
                        MeetingType.LOAN_INSTALLMENT, customer.getCustomerMeeting().getMeeting().getMeetingPlace());
            }
            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 = 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;
        InstallmentDetailsDto viewOverDueInstallmentDetails;

        if (loanBO.isGroupLoanAccount() && null == loanBO.getParentAccount()) {
            List<AccountActionDateEntity> memberDetailsOfNextInstallment = new ArrayList<AccountActionDateEntity>();
            List<List<AccountActionDateEntity>> memberDetailsOfInstallmentsInArrears = new ArrayList<List<AccountActionDateEntity>>();
            for (LoanBO member : loanBO.getMemberAccounts()) {
                memberDetailsOfNextInstallment.add(member.getDetailsOfNextInstallment());
                memberDetailsOfInstallmentsInArrears.add(member.getDetailsOfInstallmentsInArrears());
            }
            viewUpcomingInstallmentDetails = getUpcomingInstallmentDetailsForGroupLoan(
                    memberDetailsOfNextInstallment, loanBO.getCurrency());
            viewOverDueInstallmentDetails = getOverDueInstallmentDetailsForGroupLoan(
                    memberDetailsOfInstallmentsInArrears, loanBO.getCurrency());
        } else {
            viewUpcomingInstallmentDetails = getUpcomingInstallmentDetails(loanBO.getDetailsOfNextInstallment(),
                    loanBO.getCurrency());
            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;
    }

    @Override
    public OriginalScheduleInfoDto retrieveOriginalLoanSchedule(String globalAccountNum) {
        LoanBO loanBO = this.loanDao.findByGlobalAccountNum(globalAccountNum);
        Integer accountId = loanBO.getAccountId();
        try {
            List<OriginalLoanScheduleEntity> loanScheduleEntities = loanBusinessService
                    .retrieveOriginalLoanSchedule(accountId);
            ArrayList<RepaymentScheduleInstallmentDto> repaymentScheduleInstallments = new ArrayList<RepaymentScheduleInstallmentDto>();
            for (OriginalLoanScheduleEntity loanScheduleEntity : loanScheduleEntities) {
                RepaymentScheduleInstallment repaymentScheduleInstallment = loanScheduleEntity.toDto();
                RepaymentScheduleInstallmentDto installmentDto = new RepaymentScheduleInstallmentDto(
                        repaymentScheduleInstallment.getInstallment(),
                        repaymentScheduleInstallment.getPrincipal().toString(),
                        repaymentScheduleInstallment.getInterest().toString(),
                        repaymentScheduleInstallment.getFees().toString(),
                        repaymentScheduleInstallment.getMiscFees().toString(),
                        repaymentScheduleInstallment.getFeesWithMiscFee().toString(),
                        repaymentScheduleInstallment.getMiscPenalty().toString(),
                        repaymentScheduleInstallment.getTotal(), repaymentScheduleInstallment.getDueDate());
                repaymentScheduleInstallments.add(installmentDto);
            }

            return new OriginalScheduleInfoDto(loanBO.getLoanAmount().toString(), loanBO.getDisbursementDate(),
                    repaymentScheduleInstallments);
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    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 getUpcomingInstallmentDetailsForGroupLoan(
            final List<AccountActionDateEntity> upcomingAccountActionDate, final MifosCurrency currency) {
        if (upcomingAccountActionDate != null && !upcomingAccountActionDate.isEmpty()) {
            Money subTotal = new Money(Money.getDefaultCurrency());
            Money principalDue = new Money(Money.getDefaultCurrency());
            Money interestDue = new Money(Money.getDefaultCurrency());
            Money totalFeesDueWithMiscFee = new Money(Money.getDefaultCurrency());
            Money penaltyDue = new Money(Money.getDefaultCurrency());

            for (AccountActionDateEntity accAction : upcomingAccountActionDate) {
                LoanScheduleEntity upcomingInstallment = (LoanScheduleEntity) accAction;
                principalDue = principalDue.add(upcomingInstallment.getPenaltyDue());
                interestDue = interestDue.add(upcomingInstallment.getInterestDue());
                totalFeesDueWithMiscFee = totalFeesDueWithMiscFee
                        .add(upcomingInstallment.getTotalFeeDueWithMiscFeeDue());
                penaltyDue = penaltyDue.add(upcomingInstallment.getPenaltyDue());

                subTotal = upcomingInstallment.getPrincipalDue().add(upcomingInstallment.getInterestDue())
                        .add(upcomingInstallment.getTotalFeesDueWithMiscFee())
                        .add(upcomingInstallment.getPenaltyDue());
            }
            return new InstallmentDetailsDto(principalDue.toString(), interestDue.toString(),
                    totalFeesDueWithMiscFee.toString(), penaltyDue.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());
    }

    //TODO
    private InstallmentDetailsDto getOverDueInstallmentDetailsForGroupLoan(
            final List<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 (List<AccountActionDateEntity> member : overDueInstallmentList) {
            for (AccountActionDateEntity accountActionDate : member) {
                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) {
        LoanBO loan = this.loanDao.findById(loanAccountId);

        Date firstMeetingDate = customerDao.getFirstMeetingDateForCustomer(loan.getCustomer().getCustomerId());
        boolean repaymentIndependentOfMeetingEnabled = configurationPersistence.isRepaymentIndepOfMeetingEnabled();
        return loan.isTrxnDateValid(trxnDate, firstMeetingDate, repaymentIndependentOfMeetingEnabled);
    }

    @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());

        if (!isTrxnDateValid(loan.getAccountId(), repayLoanInfoDto.getDateOfPayment())) {
            throw new BusinessRuleException("errors.invalidTxndate");
        }

        try {
            if (repayLoanInfoDto.isWaiveInterest() && !loan.isInterestWaived()) {
                throw new BusinessRuleException(LoanConstants.WAIVER_INTEREST_NOT_CONFIGURED);
            }
            BigDecimal interestDueForCurrentInstallment = calculateInterestDueForCurrentInstalmanet(
                    repayLoanInfoDto);

            org.mifos.dto.domain.AccountPaymentDto paymentDto = new org.mifos.dto.domain.AccountPaymentDto(
                    Double.valueOf(repayLoanInfoDto.getEarlyRepayAmount()), repayLoanInfoDto.getDateOfPayment(),
                    repayLoanInfoDto.getReceiptNumber(), repayLoanInfoDto.getReceiptDate(),
                    repayLoanInfoDto.getId());

            paymentDto.setPaymentTypeId(Short.valueOf(repayLoanInfoDto.getPaymentTypeId()));

            if (repayLoanInfoDto.getSavingsPaymentId() != null) {
                paymentDto.setMemberNumWithAmount(generateAmountWithInterest(
                        null == repayLoanInfoDto.getMembersValue() ? new HashMap<String, Double>()
                                : repayLoanInfoDto.getMembersValue(),
                        repayLoanInfoDto));
                loan.makeEarlyRepayment(paymentDto, repayLoanInfoDto.getId(), repayLoanInfoDto.isWaiveInterest(),
                        new Money(loan.getCurrency(), interestDueForCurrentInstallment),
                        repayLoanInfoDto.getSavingsPaymentId(), null);
            } else {
                loan.makeEarlyRepayment(paymentDto, 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();
    }

    public void makeEarlyRepaymentFromSavings(RepayLoanInfoDto repayLoanInfoDto, String savingsAccGlobalNum) {
        MifosUser mifosUser = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = new UserContextFactory().create(mifosUser);

        SavingsAccountDetailDto savingsAcc = savingsServiceFacade
                .retrieveSavingsAccountDetails(savingsAccGlobalNum);

        Long savingsId = savingsAcc.getAccountId().longValue();
        Long customerId = savingsAcc.getCustomerId().longValue();
        LocalDate trxnDate = new LocalDate(repayLoanInfoDto.getDateOfPayment());
        Double amount = Double.parseDouble(repayLoanInfoDto.getEarlyRepayAmount());
        Integer paymentTypeId = Integer.parseInt(repayLoanInfoDto.getPaymentTypeId());
        String receiptId = repayLoanInfoDto.getReceiptNumber();
        LocalDate receiptDate = new LocalDate(repayLoanInfoDto.getReceiptDate());
        Locale preferredLocale = userContext.getPreferredLocale();

        SavingsWithdrawalDto savingsWithdrawalDto = new SavingsWithdrawalDto(savingsId, customerId, trxnDate,
                amount, paymentTypeId, receiptId, receiptDate, preferredLocale);

        try {
            transactionHelper.startTransaction();
            PaymentDto withdrawal = savingsServiceFacade.withdraw(savingsWithdrawalDto, true);
            repayLoanInfoDto.setSavingsPaymentId(withdrawal.getPaymentId());
            makeEarlyRepayment(repayLoanInfoDto);
            transactionHelper.commitTransaction();
        } catch (BusinessRuleException e) {
            transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getMessageKey(), e);
        } catch (Exception e) {
            transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        }
    }

    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);

        if (loan.isDecliningBalanceInterestRecalculation()) {
            loanBusinessService.computeExtraInterest(loan, DateUtils.getCurrentDateWithoutTimeStamp());
        }

        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());
        Short interestType = loan.getInterestType().getId();
        String interestTypeName = getInterestTypeName(interestType);
        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()));
        }

        CustomValueDto customValueDto = legacyMasterDao.getLookUpEntity(MasterConstants.COLLATERAL_TYPES);
        List<CustomValueListElementDto> collateralTypes = customValueDto.getCustomValueListElements();
        String collateralTypeName = null;
        for (CustomValueListElementDto collateralType : collateralTypes) {
            if (collateralType.getId() == loan.getCollateralTypeId()) {
                collateralTypeName = collateralType.getName();
                break;
            }
        }

        return new LoanInformationDto(loan.getLoanOffering().getPrdOfferingName(), globalAccountNum, accountStateId,
                accountStateName, disbursed, accountFlagNames, loan.getDisbursementDate(), loan.isRedone(),
                loan.getBusinessActivityId(), loan.getAccountId(), gracePeriodTypeName, interestType,
                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.getLoanMeeting().getMeetingDetails().getRecurAfter(),
                loan.getLoanMeeting().getMeetingDetails().getRecurrenceType().getRecurrenceId(),
                loan.getLoanOffering().isPrinDueLastInst(), loan.getNoOfInstallments(),
                loan.getMaxMinNoOfInstall().getMinNoOfInstall(), loan.getMaxMinNoOfInstall().getMaxNoOfInstall(),
                loan.getGracePeriodDuration(), fundName, loan.getCollateralTypeId(), collateralTypeName,
                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, AccountingRules.isGroupLoanWithMembers());
    }

    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);

        List<SavingsDetailDto> savingsInUse = clientServiceFacade
                .retrieveSavingsInUseForClient(loan.getCustomer().getCustomerId());
        List<SavingsDetailDto> accountsForTransfer = new ArrayList<SavingsDetailDto>();
        if (savingsInUse != null) {
            for (SavingsDetailDto savingsAccount : savingsInUse) {
                if (savingsAccount.getAccountStateId().equals(AccountState.SAVINGS_ACTIVE.getValue())) {
                    accountsForTransfer.add(savingsAccount);
                }
            }
        }

        return new RepayLoanDto(repaymentAmount.toString(), waivedRepaymentAmount.toString(),
                loan.isInterestWaived(), accountsForTransfer);
    }

    @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,
            String receiptId, LocalDate receiptDate, Short modeOfPayment) {
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);
        try {
            this.transactionHelper.startTransaction();
            LoanBO loan = loanDao.findByGlobalAccountNum(globalAccountNumber);
            PersonnelBO personnel = personnelDao.findPersonnelById((short) user.getUserId());

            Money outstandingOverpayment = loan.applyNewPaymentMechanism(paymentDate, repaymentAmount, personnel,
                    receiptId, receiptDate, modeOfPayment);

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

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

                // 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, personnel, receiptId, receiptDate,
                        modeOfPayment);
            }

            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());
            loandetails.setLoanGlobalAccountNum(individualLoan.getGlobalAccountNum());
            loandetails.setParentLoanGlobalAccountNum(individualLoan.getParentAccount().getGlobalAccountNum());
            loandetails.setParentLoanAccountId(individualLoan.getParentAccount().getAccountId());

            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 void updateMemberLoansFeeAmounts(Integer accountId) {
        LoanBO loan = this.loanDao.findById(accountId);
        List<LoanBO> individualLoans = this.loanDao.findIndividualLoans(accountId);

        try {
            transactionHelper.startTransaction();
            Map<Integer, LoanScheduleEntity> parentScheduleEntities = loan.getLoanScheduleEntityMap();

            for (Integer installmentId : parentScheduleEntities.keySet()) {

                LoanScheduleEntity parentEntity = parentScheduleEntities.get(installmentId);
                Map<Short, BigDecimal> feeAmountsForInstallment = new HashMap<Short, BigDecimal>();

                for (AccountFeesActionDetailEntity feesActionDetailEntity : parentEntity
                        .getAccountFeesActionDetails()) {
                    feeAmountsForInstallment.put(feesActionDetailEntity.getFee().getFeeId(),
                            feesActionDetailEntity.getFeeAmount().getAmount());
                }

                for (int i = 0; i < individualLoans.size(); i++) {
                    LoanScheduleEntity memberEntity = individualLoans.get(i).getLoanScheduleEntityMap()
                            .get(installmentId);

                    for (AccountFeesActionDetailEntity feesActionDetailEntity : memberEntity
                            .getAccountFeesActionDetails()) {

                        if (feesActionDetailEntity.getFee().getFeeType().equals(RateAmountFlag.RATE)) {
                            continue;
                        }

                        BigDecimal currentAmount = feeAmountsForInstallment
                                .get(feesActionDetailEntity.getFee().getFeeId());
                        currentAmount = currentAmount.subtract(feesActionDetailEntity.getFeeAmount().getAmount());

                        if (currentAmount.compareTo(BigDecimal.ZERO) != 0 && i == individualLoans.size() - 1) {
                            BigDecimal toUpdate = feesActionDetailEntity.getFeeAmount().getAmount()
                                    .add(currentAmount);
                            feesActionDetailEntity.updateFeeAmount(toUpdate);
                            currentAmount = BigDecimal.ZERO;
                        }

                        feeAmountsForInstallment.put(feesActionDetailEntity.getFee().getFeeId(), currentAmount);
                    }
                }
            }

            for (LoanBO memberLoan : individualLoans) {
                memberLoan.updateLoanSummary();
                loanDao.save(memberLoan);
            }
            loanDao.save(loan);
            transactionHelper.flushSession();

            this.loanBusinessService.clearAndPersistOriginalSchedule(loan);
            for (LoanBO memberLoan : individualLoans) {
                this.loanBusinessService.clearAndPersistOriginalSchedule(memberLoan);
            }

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

    @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 String updateSingleLoanAccountStatus(AccountUpdateStatus accountForUpdate, Date transactionDate) {
        return updateLoanAccountStatus(accountForUpdate, transactionDate);
    }

    @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() || loan.isGroupLoanAccountMember()) {
            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;
            List<CreateAccountFeeDto> accountFees = new ArrayList<CreateAccountFeeDto>();

            for (LoanOfferingFeesEntity loanOfferingFeesEntity : loanProduct.getLoanOfferingFees()) {
                FeeBO feeBO = loanOfferingFeesEntity.getFees();
                FeeDto feeDto = feeBO.toDto();
                String amountOrRate = feeDto.getAmountOrRate().toString();

                accountFees.add(new CreateAccountFeeDto(feeBO.getFeeId().intValue(), amountOrRate));
            }

            RecurringSchedule recurringSchedule = null;
            if (isRepaymentIndepOfMeetingEnabled) {
                MeetingBO meetingBO = loanProduct.getLoanOfferingMeetingValue();
                MeetingDetailsEntity meetingDetailsEntity = meetingBO.getMeetingDetails();

                Integer recursEvery = meetingDetailsEntity.getRecurAfter().intValue();
                Short dayOfMonth = meetingDetailsEntity.getDayNumber();
                RankOfDay weekOfMonth = meetingDetailsEntity.getWeekRank();
                WeekDay dayOfWeek = meetingDetailsEntity.getWeekDay();

                MeetingDetailsEntity customerMeetingDetailsEntity = center.getCustomerAccount()
                        .getMeetingForAccount().getMeetingDetails();
                Short customerRecurrenceType = customerMeetingDetailsEntity.getRecurrenceType().getRecurrenceId();
                if (meetingDetailsEntity.getRecurrenceType().getRecurrenceId().equals(customerRecurrenceType)) {
                    dayOfMonth = customerMeetingDetailsEntity.getDayNumber();
                    weekOfMonth = customerMeetingDetailsEntity.getWeekRank();
                    dayOfWeek = customerMeetingDetailsEntity.getWeekDay();
                }

                if (meetingBO.isMonthly()) {
                    if (meetingBO.isMonthlyOnDate()) {
                        recurringSchedule = new MonthlyOnDayOfMonthSchedule(recursEvery, dayOfMonth.intValue());
                    } else {
                        recurringSchedule = new MonthlyOnWeekOfMonthSchedule(recursEvery,
                                weekOfMonth.getValue().intValue(), dayOfWeek.getValue().intValue());
                    }
                } else if (meetingBO.isWeekly()) {
                    recurringSchedule = new WeeklySchedule(recursEvery, dayOfWeek.getValue().intValue());
                }
            }

            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,
            boolean isNewGLIMCreation) {

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

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

            QueryResult customerForSavings = customerPersistence
                    .searchGroupClient(customerSearchDto.getSearchTerm(), userContext.getId(), isNewGLIMCreation);

            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);
        Set<QuestionGroupReference> questionGroupReferences = loanProduct.getQuestionGroups();
        if (!questionGroupReferences.isEmpty()) {
            List<QuestionGroupDetail> allQuestionGroupDetails = questionnaireServiceFacade
                    .getQuestionGroups("Create", "Loan");
            for (QuestionGroupDetail questionGroupDetail : allQuestionGroupDetails) {
                for (QuestionGroupReference questionGroupReference : questionGroupReferences) {
                    if (questionGroupDetail.isActive()
                            && questionGroupReference.getQuestionGroupId().equals(questionGroupDetail.getId())) {
                        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);
        }
    }

    @Override
    public void removeLoanPenalty(Integer loanId, Short penaltyId) {
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);

        try {
            AccountBO account = new AccountBusinessService().getAccount(loanId);

            if (account instanceof LoanBO) {
                LoanBO loanAccount = (LoanBO) account;
                List<LoanBO> individualLoans = this.loanDao.findIndividualLoans(account.getAccountId());

                if (individualLoans != null && individualLoans.size() > 0) {
                    for (LoanBO individual : individualLoans) {
                        individual.updateDetails(userContext);
                        individual.removePenalty(penaltyId, userContext.getId());
                        this.customerDao.save(individual);
                    }
                }

                account.updateDetails(userContext);

                if (account.getPersonnel() != null) {
                    new AccountBusinessService().checkPermissionForRemovePenalties(account.getType(),
                            account.getCustomer().getLevel(), userContext, account.getOffice().getOfficeId(),
                            account.getPersonnel().getPersonnelId());
                } else {
                    new AccountBusinessService().checkPermissionForRemovePenalties(account.getType(),
                            account.getCustomer().getLevel(), userContext, account.getOffice().getOfficeId(),
                            userContext.getId());
                }

                this.transactionHelper.startTransaction();
                loanAccount.removePenalty(penaltyId, userContext.getId());
                this.loanDao.save(loanAccount);
                this.transactionHelper.commitTransaction();
            }
        } catch (ServiceException e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } catch (AccountException e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } finally {
            this.transactionHelper.closeSession();
        }
    }

    @Override
    public List<ApplicableCharge> getApplicablePenalties(Integer accountId) {
        try {
            MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            UserContext userContext = toUserContext(user);

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

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

            return loanBusinessService.getAppllicablePenalties(accountId, userContext);
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public List<AccountPaymentDto> getLoanAccountPayments(String globalAccountNum) {
        List<AccountPaymentDto> loanAccountPayments = new ArrayList<AccountPaymentDto>();
        LoanBO loanAccount = loanDao.findByGlobalAccountNum(globalAccountNum);
        List<AccountPaymentEntity> loanAccountPaymentsEntities = loanAccount.getAccountPayments();
        for (AccountPaymentEntity accountPaymentEntity : loanAccountPaymentsEntities) {
            loanAccountPayments.add(accountPaymentEntity.toScreenDto());
        }

        //for new group loan accounts
        if (loanAccount.isGroupLoanAccount() && null == loanAccount.getParentAccount()) {
            for (LoanBO member : loanAccount.getMemberAccounts()) {
                for (AccountPaymentEntity accPayment : member.getAccountPayments()) {
                    if (!accPayment.isLoanDisbursment()) {
                        loanAccountPayments.add(accPayment.toScreenDto());
                    }
                }
            }
            Collections.sort(loanAccountPayments, new AccountPaymentDtoComperator());
            Collections.reverse(loanAccountPayments);
        }

        return loanAccountPayments;
    }

    @Override
    public Integer getGroupLoanType(String globalAccountNum) {
        Integer loanType;
        LoanBO loan = loanDao.findByGlobalAccountNum(globalAccountNum);
        if (loan.isGroupLoanAccount() && null == loan.getParentAccount()) {
            loanType = PARENT_LOAN;
        } else if (loan.isGroupLoanAccount() && null != loan.getParentAccount()) {
            loanType = MEMBER_LOAN;
        } else {
            loanType = OTHER_LOAN;
        }
        return loanType;
    }

    @Override
    public void makeEarlyGroupRepayment(RepayLoanInfoDto repayLoanInfoDto,
            Map<String, Double> memberNumWithAmount) {

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

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

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

        if (!isTrxnDateValid(parentLoan.getAccountId(), repayLoanInfoDto.getDateOfPayment())) {
            throw new BusinessRuleException("errors.invalidTxndate");
        }

        try {
            if (repayLoanInfoDto.isWaiveInterest() && !parentLoan.isInterestWaived()) {
                throw new BusinessRuleException(LoanConstants.WAIVER_INTEREST_NOT_CONFIGURED);
            }
            BigDecimal interestDueForCurrentInstallment = calculateInterestDueForCurrentInstalmanet(
                    repayLoanInfoDto);

            org.mifos.dto.domain.AccountPaymentDto paymentDto = new org.mifos.dto.domain.AccountPaymentDto(
                    repayLoanInfoDto.getTotalRepaymentAmount().doubleValue(), repayLoanInfoDto.getDateOfPayment(),
                    repayLoanInfoDto.getReceiptNumber(), repayLoanInfoDto.getReceiptDate(),
                    repayLoanInfoDto.getId(), generateAmountWithInterest(memberNumWithAmount, repayLoanInfoDto));

            paymentDto.setPaymentTypeId(Short.valueOf(repayLoanInfoDto.getPaymentTypeId()));

            if (repayLoanInfoDto.getSavingsPaymentId() != null) {
                parentLoan.makeEarlyRepayment(paymentDto, repayLoanInfoDto.getId(),
                        repayLoanInfoDto.isWaiveInterest(),
                        new Money(parentLoan.getCurrency(), interestDueForCurrentInstallment),
                        repayLoanInfoDto.getSavingsPaymentId(), null);
            } else {
                parentLoan.makeEarlyRepayment(paymentDto, repayLoanInfoDto.getId(),
                        repayLoanInfoDto.isWaiveInterest(),
                        new Money(parentLoan.getCurrency(), interestDueForCurrentInstallment));
            }
        } catch (AccountException e) {
            throw new BusinessRuleException(e.getKey(), e);
        }

    }

    private Map<String, AmountWithInterest> generateAmountWithInterest(Map<String, Double> memberNumWithAmount,
            RepayLoanInfoDto parentRLIDto) {
        Map<String, AmountWithInterest> awi = new HashMap<String, AmountWithInterest>();

        for (Map.Entry<String, Double> entry : memberNumWithAmount.entrySet()) {
            LoanBO loan = this.loanDao.findById(Integer.valueOf(entry.getKey()));
            String globalAccountNum = loan.getGlobalAccountNum();
            RepayLoanDto memberRLDto = retrieveLoanRepaymentDetails(globalAccountNum);
            RepayLoanInfoDto memberRLIDto = new RepayLoanInfoDto(globalAccountNum,
                    memberRLDto.getEarlyRepaymentMoney(), parentRLIDto.getReceiptNumber(),
                    parentRLIDto.getReceiptDate(), parentRLIDto.getPaymentTypeId(), parentRLIDto.getId(),
                    parentRLIDto.isWaiveInterest(), parentRLIDto.getDateOfPayment(),
                    new BigDecimal(entry.getValue()), new BigDecimal(memberRLDto.getWaivedRepaymentMoney()));
            awi.put(entry.getKey(), new org.mifos.dto.domain.AccountPaymentDto.AmountWithInterest(entry.getValue(),
                    calculateInterestDueForCurrentInstalmanet(memberRLIDto)));
        }
        return awi;
    }

    @Override
    public BigDecimal calculateInterestDueForCurrentInstalmanet(RepayLoanInfoDto repayLoanInfoDto) {
        LoanBO loan = this.loanDao.findByGlobalAccountNum(repayLoanInfoDto.getGlobalAccountNum());
        loanBusinessService.computeExtraInterest(loan, repayLoanInfoDto.getDateOfPayment());
        BigDecimal interestDueForCurrentInstallment = interestDueForNextInstallment(
                repayLoanInfoDto.getTotalRepaymentAmount(), repayLoanInfoDto.getWaivedAmount(), loan,
                repayLoanInfoDto.isWaiveInterest());
        return interestDueForCurrentInstallment;
    }

    @Override
    public void uploadFile(Integer accountId, InputStream in, UploadedFileDto uploadedFileDto) {
        loanFileService.create(accountId, in, uploadedFileDto);
    }

}