org.mifos.accounts.servicefacade.WebTierAccountServiceFacade.java Source code

Java tutorial

Introduction

Here is the source code for org.mifos.accounts.servicefacade.WebTierAccountServiceFacade.java

Source

/*
 * Copyright (c) 2005-2011 Grameen Foundation USA
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * See also http://www.apache.org/licenses/LICENSE-2.0.html for an
 * explanation of the license and how it is applied.
 */

package org.mifos.accounts.servicefacade;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;

import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifos.accounts.acceptedpaymenttype.persistence.LegacyAcceptedPaymentTypeDao;
import org.mifos.accounts.api.AccountService;
import org.mifos.accounts.business.AccountBO;
import org.mifos.accounts.business.AccountFeesEntity;
import org.mifos.accounts.business.AccountPaymentEntity;
import org.mifos.accounts.business.AccountPenaltiesEntity;
import org.mifos.accounts.business.service.AccountBusinessService;
import org.mifos.accounts.exceptions.AccountException;
import org.mifos.accounts.fees.business.FeeBO;
import org.mifos.accounts.fees.business.RateFeeBO;
import org.mifos.accounts.fees.persistence.FeeDao;
import org.mifos.accounts.fees.util.helpers.RateAmountFlag;
import org.mifos.accounts.loan.business.LoanBO;
import org.mifos.accounts.loan.business.ScheduleCalculatorAdaptor;
import org.mifos.accounts.loan.persistance.LoanDao;
import org.mifos.accounts.loan.util.helpers.LoanExceptionConstants;
import org.mifos.accounts.penalties.business.PenaltyBO;
import org.mifos.accounts.penalties.business.RatePenaltyBO;
import org.mifos.accounts.penalties.persistence.PenaltyDao;
import org.mifos.accounts.persistence.LegacyAccountDao;
import org.mifos.accounts.util.helpers.AccountConstants;
import org.mifos.accounts.util.helpers.AccountExceptionConstants;
import org.mifos.accounts.util.helpers.AccountState;
import org.mifos.accounts.util.helpers.AccountTypes;
import org.mifos.accounts.util.helpers.PaymentData;
import org.mifos.application.admin.servicefacade.MonthClosingServiceFacade;
import org.mifos.application.master.business.PaymentTypeEntity;
import org.mifos.application.servicefacade.ClientServiceFacade;
import org.mifos.application.servicefacade.ListItem;
import org.mifos.application.servicefacade.SavingsServiceFacade;
import org.mifos.application.util.helpers.TrxnTypes;
import org.mifos.core.MifosRuntimeException;
import org.mifos.customers.api.CustomerLevel;
import org.mifos.customers.exceptions.CustomerException;
import org.mifos.customers.personnel.business.PersonnelBO;
import org.mifos.customers.personnel.persistence.LegacyPersonnelDao;
import org.mifos.customers.personnel.persistence.PersonnelDao;
import org.mifos.dto.domain.AccountPaymentParametersDto;
import org.mifos.dto.domain.AdjustedPaymentDto;
import org.mifos.dto.domain.ApplicableCharge;
import org.mifos.dto.domain.CustomerDto;
import org.mifos.dto.domain.PaymentDto;
import org.mifos.dto.domain.SavingsAdjustmentDto;
import org.mifos.dto.domain.SavingsDetailDto;
import org.mifos.dto.domain.UserReferenceDto;
import org.mifos.dto.screen.AccountTypeCustomerLevelDto;
import org.mifos.framework.exceptions.ApplicationException;
import org.mifos.framework.exceptions.PersistenceException;
import org.mifos.framework.exceptions.ServiceException;
import org.mifos.framework.hibernate.helper.HibernateTransactionHelper;
import org.mifos.framework.hibernate.helper.StaticHibernateUtil;
import org.mifos.framework.util.helpers.Constants;
import org.mifos.framework.util.helpers.Money;
import org.mifos.security.MifosUser;
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.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * Concrete implementation of service to manipulate accounts from the presentation layer.
 *
 */
public class WebTierAccountServiceFacade implements AccountServiceFacade {
    private AccountService accountService;
    private HibernateTransactionHelper transactionHelper;
    private AccountBusinessService accountBusinessService;
    private ScheduleCalculatorAdaptor scheduleCalculatorAdaptor;
    private LegacyAcceptedPaymentTypeDao acceptedPaymentTypePersistence;
    private LegacyPersonnelDao personnelPersistence;
    private LegacyAccountDao legacyAccountDao;
    private MonthClosingServiceFacade monthClosingServiceFacade;
    private ClientServiceFacade clientServiceFacade;
    private SavingsServiceFacade savingsServiceFacade;

    @Autowired
    private PersonnelDao personnelDao;

    @Autowired
    private LoanDao loanDao;

    @Autowired
    private FeeDao feeDao;

    @Autowired
    private PenaltyDao penaltyDao;

    @Autowired
    public WebTierAccountServiceFacade(AccountService accountService, HibernateTransactionHelper transactionHelper,
            AccountBusinessService accountBusinessService, ScheduleCalculatorAdaptor scheduleCalculatorAdaptor,
            LegacyAcceptedPaymentTypeDao acceptedPaymentTypePersistence, LegacyPersonnelDao personnelPersistence,
            LegacyAccountDao legacyAccountDao, MonthClosingServiceFacade monthClosingServiceFacade,
            ClientServiceFacade clientServiceFacade, SavingsServiceFacade savingsServiceFacade) {
        this.accountService = accountService;
        this.transactionHelper = transactionHelper;
        this.accountBusinessService = accountBusinessService;
        this.scheduleCalculatorAdaptor = scheduleCalculatorAdaptor;
        this.acceptedPaymentTypePersistence = acceptedPaymentTypePersistence;
        this.personnelPersistence = personnelPersistence;
        this.legacyAccountDao = legacyAccountDao;
        this.monthClosingServiceFacade = monthClosingServiceFacade;
        this.clientServiceFacade = clientServiceFacade;
        this.savingsServiceFacade = savingsServiceFacade;
    }

    @Override
    public AccountPaymentDto getAccountPaymentInformation(Integer accountId, String paymentType, Short localeId,
            UserReferenceDto userReferenceDto, Date paymentDate) {
        try {
            AccountBO account = accountBusinessService.getAccount(accountId);
            CustomerDto customer = account.getCustomer().toCustomerDto();

            List<SavingsDetailDto> savingsInUse = clientServiceFacade
                    .retrieveSavingsInUseForClient(customer.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);
                    }
                }
            }

            if (isLoanPayment(paymentType)) {
                scheduleCalculatorAdaptor.computeExtraInterest((LoanBO) account, paymentDate);
            }

            UserReferenceDto accountUser = userReferenceDto;
            if (account.getPersonnel() != null) {
                accountUser = new UserReferenceDto(account.getPersonnel().getPersonnelId());
            }

            List<ListItem<Short>> paymentTypeList = constructPaymentTypeList(paymentType, localeId);
            AccountTypeDto accountType = AccountTypeDto.getAccountType(account.getAccountType().getAccountTypeId());
            String totalPaymentDue = account.getTotalPaymentDue().toString();
            clearSessionAndRollback();

            return new AccountPaymentDto(accountType, account.getVersionNo(), paymentTypeList, totalPaymentDue,
                    accountUser, getLastPaymentDate(account), accountsForTransfer, customer);
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    public Date retrieveLatPaymentDate(String globalAccountNum) {
        try {
            AccountBO account = accountBusinessService.findBySystemId(globalAccountNum);
            return getLastPaymentDate(account);
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    private Date getLastPaymentDate(AccountBO account) {
        Date lastPaymentDate = new Date(0);
        AccountPaymentEntity lastPayment = account.findMostRecentNonzeroPaymentByPaymentDate();
        if (lastPayment != null) {
            lastPaymentDate = lastPayment.getPaymentDate();
        }
        return lastPaymentDate;
    }

    // Exposed for testing
    void clearSessionAndRollback() {
        StaticHibernateUtil.getSessionTL().clear();
        transactionHelper.rollbackTransaction();
    }

    private boolean isLoanPayment(String paymentType) {
        return paymentType.equals(Constants.LOAN);
    }

    @Override
    public List<ListItem<Short>> constructPaymentTypeListForLoanRepayment(Short localeId) {
        try {
            List<PaymentTypeEntity> paymentTypeList = acceptedPaymentTypePersistence
                    .getAcceptedPaymentTypesForATransaction(localeId, TrxnTypes.loan_repayment.getValue());
            List<ListItem<Short>> listItems = new ArrayList<ListItem<Short>>();
            for (PaymentTypeEntity paymentTypeEntity : paymentTypeList) {
                listItems.add(new ListItem<Short>(paymentTypeEntity.getId(), paymentTypeEntity.getName()));
            }
            return listItems;
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    private List<ListItem<Short>> constructPaymentTypeList(String paymentType, Short localeId) {

        try {
            List<PaymentTypeEntity> paymentTypeList = null;
            if (paymentType != null && !Constants.EMPTY_STRING.equals(paymentType.trim())) {
                if (isLoanPayment(paymentType)) {
                    paymentTypeList = acceptedPaymentTypePersistence
                            .getAcceptedPaymentTypesForATransaction(localeId, TrxnTypes.loan_repayment.getValue());
                } else {
                    paymentTypeList = acceptedPaymentTypePersistence
                            .getAcceptedPaymentTypesForATransaction(localeId, TrxnTypes.fee.getValue());
                }
            }

            List<ListItem<Short>> listItems = new ArrayList<ListItem<Short>>();
            for (PaymentTypeEntity paymentTypeEntity : paymentTypeList) {
                listItems.add(new ListItem<Short>(paymentTypeEntity.getId(), paymentTypeEntity.getName()));
            }
            return listItems;
        } catch (PersistenceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public boolean isPaymentPermitted(Integer accountId) {

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

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

            CustomerLevel customerLevel = null;
            if (account.getType().equals(AccountTypes.CUSTOMER_ACCOUNT)) {
                customerLevel = account.getCustomer().getLevel();
            }

            Short personnelId = userContext.getId();
            if (account.getPersonnel() != null) {
                personnelId = account.getPersonnel().getPersonnelId();
            }

            return ActivityMapper.getInstance().isPaymentPermittedForAccounts(account.getType(), customerLevel,
                    userContext, account.getOffice().getOfficeId(), personnelId);
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        }
    }

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

            AccountBO account = this.accountBusinessService.getAccount(accountId);

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

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

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

    @Override
    public void applyCharge(Integer accountId, Short chargeId, Double chargeAmount, boolean isPenaltyType) {

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

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

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

                if (individualLoans != null && individualLoans.size() > 0) {
                    for (LoanBO individual : individualLoans) {
                        individual.updateDetails(userContext);

                        if (isPenaltyType && !chargeId.equals(Short.valueOf(AccountConstants.MISC_PENALTY))) {
                            PenaltyBO penalty = this.penaltyDao.findPenaltyById(chargeId.intValue());
                            individual.addAccountPenalty(
                                    new AccountPenaltiesEntity(individual, penalty, chargeAmount));
                        } else {
                            FeeBO fee = this.feeDao.findById(chargeId);

                            if (fee instanceof RateFeeBO) {
                                individual.applyCharge(chargeId, chargeAmount);
                            } else {
                                Double radio = individual.getLoanAmount().getAmount().doubleValue()
                                        / ((LoanBO) account).getLoanAmount().getAmount().doubleValue();

                                individual.applyCharge(chargeId, chargeAmount * radio);
                            }
                        }
                    }
                }
            }

            account.updateDetails(userContext);

            CustomerLevel customerLevel = null;
            if (account.isCustomerAccount()) {
                customerLevel = account.getCustomer().getLevel();
            }
            if (account.getPersonnel() != null) {
                checkPermissionForApplyCharges(account.getType(), customerLevel, userContext,
                        account.getOffice().getOfficeId(), account.getPersonnel().getPersonnelId());
            } else {
                checkPermissionForApplyCharges(account.getType(), customerLevel, userContext,
                        account.getOffice().getOfficeId(), userContext.getId());
            }

            this.transactionHelper.startTransaction();

            if (isPenaltyType && account instanceof LoanBO) {
                PenaltyBO penalty = this.penaltyDao.findPenaltyById(chargeId.intValue());
                ((LoanBO) account).addAccountPenalty(new AccountPenaltiesEntity(account, penalty, chargeAmount));
            } else {
                account.applyCharge(chargeId, chargeAmount);
            }

            this.transactionHelper.commitTransaction();
        } catch (ServiceException e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } catch (ApplicationException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getKey(), e);
        }
    }

    private void checkPermissionForApplyCharges(AccountTypes accountTypes, CustomerLevel customerLevel,
            UserContext userContext, Short recordOfficeId, Short recordLoanOfficerId) throws ApplicationException {
        if (!isPermissionAllowed(accountTypes, customerLevel, userContext, recordOfficeId, recordLoanOfficerId)) {
            throw new CustomerException(SecurityConstants.KEY_ACTIVITY_NOT_ALLOWED);
        }
    }

    private boolean isPermissionAllowed(AccountTypes accountTypes, CustomerLevel customerLevel,
            UserContext userContext, Short recordOfficeId, Short recordLoanOfficerId) {
        return ActivityMapper.getInstance().isApplyChargesPermittedForAccounts(accountTypes, customerLevel,
                userContext, recordOfficeId, recordLoanOfficerId);
    }

    @Override
    public AccountTypeCustomerLevelDto getAccountTypeCustomerLevelDto(Integer accountId) {

        try {
            AccountBO account = new AccountBusinessService().getAccount(accountId);
            return new AccountTypeCustomerLevelDto(account.getType().getValue(),
                    account.getCustomer().getCustomerLevel().getId());
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    @Override
    public void makePayment(AccountPaymentParametersDto accountPaymentParametersDto) {
        this.accountService.makePayment(accountPaymentParametersDto);
    }

    @Override
    public void applyAdjustment(String globalAccountNum, String adjustmentNote, Short loggedInUser) {
        try {
            AccountBO account = accountBusinessService.findBySystemId(globalAccountNum);
            AccountPaymentEntity lastPayment = account.getLastPmntToBeAdjusted();
            applyHistoricalAdjustment(globalAccountNum, (lastPayment == null) ? null : lastPayment.getPaymentId(),
                    adjustmentNote, loggedInUser, null);
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        }
    }

    private void checkPermissionForAdjustment(AccountBO accountBO) throws ServiceException {
        AccountPaymentEntity lastPmntToBeAdjusted = accountBO.getLastPmntToBeAdjusted();
        if (lastPmntToBeAdjusted == null)
            return;
        UserContext userContext = accountBO.getUserContext();
        Date lastPaymentDate = lastPmntToBeAdjusted.getPaymentDate();
        PersonnelBO personnel = accountBO.getPersonnel();
        Short personnelId = personnel != null ? personnel.getPersonnelId() : userContext.getId();
        Short officeId = accountBO.getOfficeId();
        accountBusinessService.checkPermissionForAdjustment(AccountTypes.LOAN_ACCOUNT, null, userContext, officeId,
                personnelId);
        accountBusinessService.checkPermissionForAdjustmentOnBackDatedPayments(lastPaymentDate, userContext,
                officeId, personnelId);
    }

    UserContext getUserContext() {
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return new UserContextFactory().create(user);
    }

    /**
     * adjustment for member account payment has to be handled along with parent account payments and other members payments.
     * This method prepares data and run applyHistoricalAdjustment so adjustment will be made for parent payment context.
     */
    @Override
    @PreAuthorize("isFullyAuthenticated()")
    public void applyMemberAccountHistoricalAdjustment(String memberGlobalAccountNum, Integer memberPaymentId,
            String adjustmentNote, Short personnelId, AdjustedPaymentDto adjustedPaymentDto) {

        try {
            LoanBO memberAccount = (LoanBO) accountBusinessService.findBySystemId(memberGlobalAccountNum);
            LoanBO parentAccount = memberAccount.getParentAccount();

            if (parentAccount == null) {
                throw new AccountException(LoanExceptionConstants.NO_PARENT_ACCOUNT_EXCEPTION);
            }

            AccountPaymentEntity parentPaymentEntity = memberAccount
                    .findParentPaymentByMemberPaymentId(memberPaymentId);
            List<AdjustedPaymentDto> membersAdjustedPaymentDtoList = new ArrayList<AdjustedPaymentDto>();

            for (AccountPaymentEntity memberPayment : parentPaymentEntity.getMemberPayments()) {
                if (memberPayment.getAccount().getAccountId().equals(memberAccount.getAccountId())) {
                    membersAdjustedPaymentDtoList.add(new AdjustedPaymentDto(adjustedPaymentDto.getAmount(),
                            adjustedPaymentDto.getPaymentDate(), adjustedPaymentDto.getPaymentType(),
                            memberAccount.getAccountId()));
                } else {
                    membersAdjustedPaymentDtoList.add(new AdjustedPaymentDto(
                            memberPayment.getAmount().getAmount().toString(), adjustedPaymentDto.getPaymentDate(),
                            adjustedPaymentDto.getPaymentType(), memberPayment.getAccount().getAccountId()));
                }

            }

            BigDecimal parentAmountSubstraction = memberAccount.findPaymentById(memberPaymentId).getAmount()
                    .getAmount().subtract((new BigDecimal(adjustedPaymentDto.getAmount())));
            String newParentAmount = parentPaymentEntity.getAmount().getAmount().subtract(parentAmountSubstraction)
                    .toString();
            AdjustedPaymentDto parentAdjustedPaymentDto = new AdjustedPaymentDto(newParentAmount,
                    adjustedPaymentDto.getPaymentDate(), adjustedPaymentDto.getPaymentType(),
                    parentAccount.getAccountId(), membersAdjustedPaymentDtoList);

            this.applyHistoricalAdjustment(parentAccount.getGlobalAccountNum(), parentPaymentEntity.getPaymentId(),
                    adjustmentNote, personnelId, parentAdjustedPaymentDto);
        } catch (ServiceException e) {
            throw new MifosRuntimeException(e);
        } catch (AccountException e) {
            throw new MifosRuntimeException(e);
        }

    }

    @Override
    public void applyHistoricalAdjustment(String globalAccountNum, Integer paymentId, String adjustmentNote,
            Short personnelId, AdjustedPaymentDto adjustedPaymentDto) {
        try {
            AccountBO accountBO = accountBusinessService.findBySystemId(globalAccountNum);
            accountBO.setUserContext(getUserContext());
            checkPermissionForAdjustment(accountBO);
            PersonnelBO personnelBO = personnelPersistence.findPersonnelById(personnelId);

            AccountPaymentEntity accountPaymentEntity = accountBO.findPaymentById(paymentId);
            if (accountPaymentEntity == null) {
                throw new AccountException(AccountExceptionConstants.CANNOTADJUST);
            }
            monthClosingServiceFacade.validateTransactionDate(accountPaymentEntity.getPaymentDate());

            PaymentDto otherTransferPayment = accountPaymentEntity.getOtherTransferPaymentDto();

            transactionHelper.flushAndClearSession(); //flush to avoid proxy casting problems
            transactionHelper.startTransaction();

            Integer newSavingsPaymentId = null;
            if (otherTransferPayment != null) {
                SavingsAdjustmentDto savingsAdjustment = new SavingsAdjustmentDto(
                        otherTransferPayment.getAccountId().longValue(),
                        (adjustedPaymentDto == null) ? 0 : Double.valueOf(adjustedPaymentDto.getAmount()),
                        adjustmentNote, otherTransferPayment.getPaymentId(),
                        (adjustedPaymentDto == null) ? otherTransferPayment.getPaymentDate()
                                : new LocalDate(adjustedPaymentDto.getPaymentDate()));
                PaymentDto newSavingsPayment = this.savingsServiceFacade.adjustTransaction(savingsAdjustment, true);
                newSavingsPaymentId = (newSavingsPayment == null) ? null : newSavingsPayment.getPaymentId();
            }

            //reload after flush & clear
            accountBO = accountBusinessService.findBySystemId(globalAccountNum);
            accountBO.setUserContext(getUserContext());

            AccountPaymentEntity adjustedPayment = null;
            Integer adjustedId;
            Stack<PaymentData> paymentsToBeReapplied = new Stack<PaymentData>();

            Map<Integer, Stack<PaymentData>> memberPaymentsToBeReappliedMap = new HashMap<Integer, Stack<PaymentData>>();
            if (accountBO.isGroupLoanAccount()) {
                for (LoanBO memberAccount : ((LoanBO) accountBO).getMemberAccounts()) {
                    Stack<PaymentData> memberPaymentsToBeReapplied = new Stack<PaymentData>();
                    memberPaymentsToBeReappliedMap.put(memberAccount.getAccountId(), memberPaymentsToBeReapplied);
                }
            }

            do {
                adjustedPayment = accountBO.getLastPmntToBeAdjusted();

                if (adjustedPayment == null) {
                    break;
                }

                adjustedId = adjustedPayment.getPaymentId();
                if (!accountPaymentEntity.getPaymentId().equals(adjustedId)) {
                    PersonnelBO paymentCreator = (adjustedPayment.getCreatedByUser() == null) ? personnelBO
                            : adjustedPayment.getCreatedByUser();

                    PaymentData paymentData = accountBO.createPaymentData(adjustedPayment.getAmount(),
                            adjustedPayment.getPaymentDate(), adjustedPayment.getReceiptNumber(),
                            adjustedPayment.getReceiptDate(), adjustedPayment.getPaymentType().getId(),
                            paymentCreator);
                    paymentData.setOtherTransferPayment(adjustedPayment.getOtherTransferPayment());

                    paymentsToBeReapplied.push(paymentData);

                    // handling new Group Loan Members payments
                    for (AccountPaymentEntity memberAdjustedPayment : adjustedPayment.getMemberPayments()) {
                        PaymentData memberPaymentData = memberAdjustedPayment.getAccount().createPaymentData(
                                memberAdjustedPayment.getAmount(), adjustedPayment.getPaymentDate(),
                                adjustedPayment.getReceiptNumber(), adjustedPayment.getReceiptDate(),
                                adjustedPayment.getPaymentType().getId(), paymentCreator);
                        memberPaymentsToBeReappliedMap.get(memberAdjustedPayment.getAccount().getAccountId())
                                .push(memberPaymentData);
                    }

                }

                transactionHelper.flushAndClearSession();
                //reload after flush & clear
                accountBO = accountBusinessService.findBySystemId(globalAccountNum);
                accountBO.setUserContext(getUserContext());
                accountBO.adjustLastPayment(adjustmentNote, personnelBO);

                legacyAccountDao.createOrUpdate(accountBO);

                //adjust New Group Loan member payments
                if (accountBO.isGroupLoanAccount()) {
                    for (LoanBO memberAccount : ((LoanBO) accountBO).getMemberAccounts()) {

                        AccountPaymentEntity memberPayment = memberAccount.getLastPmntToBeAdjusted();
                        if (memberPayment.getParentPaymentId() == null || !memberPayment.getParentPaymentId()
                                .getPaymentId().equals(accountPaymentEntity.getPaymentId())) {
                            continue;
                        }

                        memberAccount.setUserContext(getUserContext());
                        memberAccount.adjustLastPayment(adjustmentNote, personnelBO);
                        legacyAccountDao.createOrUpdate(memberAccount);
                    }
                }
                transactionHelper.flushSession();
            } while (!accountPaymentEntity.getPaymentId().equals(adjustedId));

            if (adjustedPaymentDto != null) {
                //reapply adjusted payment
                PersonnelBO paymentCreator = (accountPaymentEntity.getCreatedByUser() == null) ? personnelBO
                        : accountPaymentEntity.getCreatedByUser();
                Money amount = new Money(accountBO.getCurrency(), adjustedPaymentDto.getAmount());

                PaymentData paymentData = accountBO.createPaymentData(amount, adjustedPaymentDto.getPaymentDate(),
                        accountPaymentEntity.getReceiptNumber(), accountPaymentEntity.getReceiptDate(),
                        adjustedPaymentDto.getPaymentType(), paymentCreator);

                //new adjusted savings payment must be tied to this payment
                if (newSavingsPaymentId != null) {
                    AccountPaymentEntity newSvngPayment = legacyAccountDao.findPaymentById(newSavingsPaymentId);
                    paymentData.setOtherTransferPayment(newSvngPayment);
                }

                accountBO.applyPayment(paymentData);
                legacyAccountDao.createOrUpdate(accountBO);
                transactionHelper.flushSession();
                // handling new Group Loan Members payments
                if (accountBO.isGroupLoanAccount()) {
                    for (AdjustedPaymentDto adjustedMemberPayment : adjustedPaymentDto.getMemberPayments()) {
                        AccountBO memberAccount = ((LoanBO) accountBO)
                                .findMemberById(adjustedMemberPayment.getAccountId());
                        BigDecimal adjustedMemberPaymentAmount = BigDecimal.ZERO;
                        if (!StringUtils.isBlank(adjustedMemberPayment.getAmount())) {
                            adjustedMemberPaymentAmount = new BigDecimal(adjustedMemberPayment.getAmount());
                        }
                        Money memberAmount = new Money(memberAccount.getCurrency(),
                                adjustedMemberPaymentAmount.toString());

                        PaymentData memberPaymentData = memberAccount.createPaymentData(memberAmount,
                                adjustedPaymentDto.getPaymentDate(), accountPaymentEntity.getReceiptNumber(),
                                accountPaymentEntity.getReceiptDate(), adjustedPaymentDto.getPaymentType(),
                                paymentCreator);
                        memberPaymentData.setParentPayment(accountBO.getLastPmnt());
                        memberAccount.applyPayment(memberPaymentData);
                        legacyAccountDao.createOrUpdate(memberAccount);
                    }
                }
            }

            while (!paymentsToBeReapplied.isEmpty()) {
                PaymentData paymentData = paymentsToBeReapplied.pop();
                //avoid lazy loading exception
                if (paymentData.getOtherTransferPayment() != null) {
                    legacyAccountDao.updatePayment(paymentData.getOtherTransferPayment());
                }
                accountBO.applyPayment(paymentData);
                legacyAccountDao.createOrUpdate(accountBO);
                transactionHelper.flushSession();

                if (accountBO.isGroupLoanAccount()) {
                    for (LoanBO memberAccount : ((LoanBO) accountBO).getMemberAccounts()) {
                        PaymentData memberPaymentData = memberPaymentsToBeReappliedMap
                                .get(memberAccount.getAccountId()).pop();
                        memberPaymentData.setParentPayment(accountBO.getLastPmnt());
                        memberAccount.applyPayment(memberPaymentData);
                        legacyAccountDao.createOrUpdate(memberAccount);
                    }
                }
            }

            transactionHelper.commitTransaction();
        } catch (ServiceException e) {
            transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } catch (AccountException e) {
            transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } catch (PersistenceException e) {
            transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } catch (RuntimeException e) {
            transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } finally {
            transactionHelper.closeSession();
        }
    }

    @Override
    public void makePaymentFromSavingsAcc(AccountPaymentParametersDto accountPaymentParametersDto,
            String savingsAccGlobalNumber) {
        this.accountService.makePaymentFromSavings(accountPaymentParametersDto, savingsAccGlobalNumber);
    }

    @Override
    /**
     * do not use this method
     * used and implemented in Audi Plugin
     * @return null
     */
    public Integer getAccountTrxnById(Integer id) {
        return null;
    }

    @Override
    public void applyGroupCharge(Map<Integer, String> idsAndValues, Short chargeId, boolean isPenaltyType) {
        MifosUser user = (MifosUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        UserContext userContext = toUserContext(user);
        TreeMap<Integer, String> idsAndValueAsTreeMap = new TreeMap<Integer, String>(idsAndValues);

        try {
            AccountBO parentAccount = ((LoanBO) legacyAccountDao.getAccount(
                    new AccountBusinessService().getAccount(idsAndValueAsTreeMap.firstKey()).getAccountId()))
                            .getParentAccount();
            BigDecimal parentAmount = ((LoanBO) parentAccount).getLoanAmount().getAmount();
            BigDecimal membersAmount = BigDecimal.ZERO;

            for (Map.Entry<Integer, String> entry : idsAndValues.entrySet()) {
                LoanBO individual = loanDao.findById(entry.getKey());
                Double chargeAmount = Double.valueOf(entry.getValue());
                if (chargeAmount.equals(0.0)) {
                    continue;
                }
                membersAmount = membersAmount.add(individual.getLoanAmount().getAmount());
                individual.updateDetails(userContext);

                if (isPenaltyType && !chargeId.equals(Short.valueOf(AccountConstants.MISC_PENALTY))) {
                    PenaltyBO penalty = this.penaltyDao.findPenaltyById(chargeId.intValue());
                    individual.addAccountPenalty(new AccountPenaltiesEntity(individual, penalty, chargeAmount));
                } else {
                    individual.applyCharge(chargeId, chargeAmount);
                }
            }

            boolean isRateCharge = false;

            if (!chargeId.equals(Short.valueOf(AccountConstants.MISC_FEES))
                    && !chargeId.equals(Short.valueOf(AccountConstants.MISC_PENALTY))) {

                if (isPenaltyType) {
                    PenaltyBO penalty = this.penaltyDao.findPenaltyById(chargeId.intValue());
                    if (penalty instanceof RatePenaltyBO) {
                        isRateCharge = true;
                    }
                } else {
                    FeeBO fee = feeDao.findById(chargeId);
                    if (fee.getFeeType().equals(RateAmountFlag.RATE)) {
                        isRateCharge = true;
                    }
                }
            }

            Double chargeAmount = null;

            if (!isRateCharge) {
                chargeAmount = sumCharge(idsAndValues);
            } else {
                chargeAmount = Double.valueOf(idsAndValueAsTreeMap.firstEntry().getValue());
                BigDecimal chargeAmountBig = new BigDecimal(chargeAmount);
                membersAmount = membersAmount.multiply(chargeAmountBig);
                int scale = Money.getInternalPrecision();
                chargeAmountBig = membersAmount.divide(parentAmount, scale, RoundingMode.HALF_EVEN);
                chargeAmount = chargeAmountBig.doubleValue();
            }

            parentAccount.updateDetails(userContext);

            CustomerLevel customerLevel = null;
            if (parentAccount.isCustomerAccount()) {
                customerLevel = parentAccount.getCustomer().getLevel();
            }
            if (parentAccount.getPersonnel() != null) {
                checkPermissionForApplyCharges(parentAccount.getType(), customerLevel, userContext,
                        parentAccount.getOffice().getOfficeId(), parentAccount.getPersonnel().getPersonnelId());
            } else {
                checkPermissionForApplyCharges(parentAccount.getType(), customerLevel, userContext,
                        parentAccount.getOffice().getOfficeId(), userContext.getId());
            }

            this.transactionHelper.startTransaction();

            if (isPenaltyType && parentAccount instanceof LoanBO) {
                PenaltyBO penalty = this.penaltyDao.findPenaltyById(chargeId.intValue());
                ((LoanBO) parentAccount)
                        .addAccountPenalty(new AccountPenaltiesEntity(parentAccount, penalty, chargeAmount));
            } else {
                parentAccount.applyCharge(chargeId, chargeAmount);
            }

            this.transactionHelper.commitTransaction();
        } catch (ServiceException e) {
            this.transactionHelper.rollbackTransaction();
            throw new MifosRuntimeException(e);
        } catch (ApplicationException e) {
            this.transactionHelper.rollbackTransaction();
            throw new BusinessRuleException(e.getKey(), e);
        }

    }

    private Double sumCharge(Map<Integer, String> idsAndValues) {
        Double sum = 0.0;
        for (Map.Entry<Integer, String> entry : idsAndValues.entrySet()) {
            sum += Double.valueOf(entry.getValue());
        }
        return sum;
    }

}