edu.cornell.kfs.fp.service.impl.CUPaymentMethodGeneralLedgerPendingEntryServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for edu.cornell.kfs.fp.service.impl.CUPaymentMethodGeneralLedgerPendingEntryServiceImpl.java

Source

/*
 * Copyright 2010 The Kuali Foundation.
 * 
 * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
 * 
 * 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.
 */
package edu.cornell.kfs.fp.service.impl;

import edu.cornell.kfs.fp.businessobject.PaymentMethod;
import edu.cornell.kfs.fp.businessobject.PaymentMethodChart;
import edu.cornell.kfs.fp.service.CUPaymentMethodGeneralLedgerPendingEntryService;
import edu.cornell.kfs.module.purap.document.CuPaymentRequestDocument;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.coa.businessobject.OffsetDefinition;
import org.kuali.kfs.coa.service.ObjectCodeService;
import org.kuali.kfs.coa.service.OffsetDefinitionService;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.gl.businessobject.Entry;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.util.KRADConstants;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
import org.kuali.kfs.module.purap.service.PurapGeneralLedgerService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
import org.kuali.kfs.sys.businessobject.SystemOptions;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.GeneralLedgerPostingDocument;
import org.kuali.kfs.sys.document.service.AccountingDocumentRuleHelperService;
import org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBaseConstants.GENERAL_LEDGER_PENDING_ENTRY_CODE;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.springframework.cache.annotation.Cacheable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.kuali.kfs.module.purap.PurapConstants.PURAP_ORIGIN_CODE;
import static org.kuali.kfs.sys.KFSConstants.GL_CREDIT_CODE;
import static org.kuali.kfs.sys.KFSConstants.GL_DEBIT_CODE;

@NonTransactional
public class CUPaymentMethodGeneralLedgerPendingEntryServiceImpl
        implements CUPaymentMethodGeneralLedgerPendingEntryService {
    private static final Logger LOG = LogManager
            .getLogger(CUPaymentMethodGeneralLedgerPendingEntryServiceImpl.class);
    protected static final String DEFAULT_PAYMENT_METHOD_IF_MISSING = "A"; // check/ACH

    // not sure why these are not injected ?
    private GeneralLedgerPendingEntryService generalLedgerPendingEntryService;
    private ObjectCodeService objectCodeService;
    private ParameterService parameterService;
    private BusinessObjectService businessObjectService;
    private BankService bankService;
    protected PurapGeneralLedgerService purapGeneralLedgerService;

    @Cacheable(value = SystemOptions.CACHE_NAME, key = "'{isPaymentMethodProcessedUsingPdp}'+#p0")
    public boolean isPaymentMethodProcessedUsingPdp(String paymentMethodCode) {
        if (StringUtils.isBlank(paymentMethodCode)) {
            paymentMethodCode = DEFAULT_PAYMENT_METHOD_IF_MISSING;
        }
        PaymentMethod pm = getBusinessObjectService().findBySinglePrimaryKey(PaymentMethod.class,
                paymentMethodCode);
        if (pm != null) {
            return pm.isProcessedUsingPdp();
        }
        return false;
    }

    /**
     * This implementation will also return null if the bank code on the payment method record does not exist in the bank table.
     * 
     */
    public Bank getBankForPaymentMethod(String paymentMethodCode) {
        if (StringUtils.isBlank(paymentMethodCode)) {
            paymentMethodCode = DEFAULT_PAYMENT_METHOD_IF_MISSING;
        }
        PaymentMethod pm = getBusinessObjectService().findBySinglePrimaryKey(PaymentMethod.class,
                paymentMethodCode);
        if (pm != null) {
            // if no bank code, short circuit and return null
            if (pm.getBankCode() != null) {
                return pm.getBank();
            }
        }
        return null;
    }

    /**
     * Generates additional document-level GL entries for the DV, depending on the payment method code. 
     * 
     * Return true if GLPE's are generated successfully (i.e. there are either 0 GLPE's or 1 GLPE in disbursement voucher document)
     * 
     * @param document submitted financial document
     * @param paymentMethodCode
     * @param bankCode
     * @param bankCodePropertyName
     * @param templatePendingEntry
     * @param feesWaived
     * @param reverseCharge
     * @param sequenceHelper helper class to keep track of GLPE sequence
     * @return true if GLPE's are generated successfully
     */
    public boolean generatePaymentMethodSpecificDocumentGeneralLedgerPendingEntries(AccountingDocument document,
            String paymentMethodCode, String bankCode, String bankCodePropertyName,
            GeneralLedgerPendingEntry templatePendingEntry, boolean feesWaived, boolean reverseCharge,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        return generatePaymentMethodSpecificDocumentGeneralLedgerPendingEntries(document, paymentMethodCode,
                bankCode, bankCodePropertyName, templatePendingEntry, feesWaived, reverseCharge, sequenceHelper,
                null, null);
    }

    public boolean generatePaymentMethodSpecificDocumentGeneralLedgerPendingEntries(AccountingDocument document,
            String paymentMethodCode, String bankCode, String bankCodePropertyName,
            GeneralLedgerPendingEntry templatePendingEntry, boolean feesWaived, boolean reverseCharge,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper, KualiDecimal bankOffsetAmount,
            Map<String, KualiDecimal> actualTotalsByChart) {

        if (StringUtils.isBlank(paymentMethodCode)) {
            paymentMethodCode = DEFAULT_PAYMENT_METHOD_IF_MISSING;
        }
        PaymentMethod pm = getBusinessObjectService().findBySinglePrimaryKey(PaymentMethod.class,
                paymentMethodCode);
        // no payment method? abort.
        if (pm == null) {
            return false;
        }

        if (pm.isAssessedFees()) {
            if (!feesWaived) {
                generateFeeAssessmentEntries(pm, document, templatePendingEntry, sequenceHelper, reverseCharge);
            }
        }

        if (pm.isOffsetUsingClearingAccount()) {
            generateClearingAccountOffsetEntries(pm, document, sequenceHelper, actualTotalsByChart);
        }

        if (!pm.isProcessedUsingPdp() && StringUtils.isNotBlank(bankCode)) {
            if (PaymentMethod.PM_CODE_WIRE.equalsIgnoreCase(paymentMethodCode)
                    || PaymentMethod.PM_CODE_FOREIGN_DRAFT.equalsIgnoreCase(paymentMethodCode)) {
                //do not create bank offsets unless DM approval
            } else {
                generateDocumentBankOffsetEntries(document, bankCode, bankCodePropertyName,
                        templatePendingEntry.getFinancialDocumentTypeCode(), sequenceHelper, bankOffsetAmount);
            }
        }

        return true;
    }

    /**
     * Generates the GL entries to charge the department for the foreign draft and credit the Wire Charge
     * Fee Account as specified by system parameters.
     * 
     * @param document Document into which to add the generated GL Entries.
     * 
     */
    protected boolean generateFeeAssessmentEntries(PaymentMethod pm, AccountingDocument document,
            GeneralLedgerPendingEntry templatePendingEntry, GeneralLedgerPendingEntrySequenceHelper sequenceHelper,
            boolean reverseEntries) {
        LOG.debug("generateForeignDraftChargeEntries started");

        PaymentMethodChart pmc = pm.getPaymentMethodChartInfo(templatePendingEntry.getChartOfAccountsCode(),
                new java.sql.Date(document.getDocumentHeader().getWorkflowDocument().getDateCreated().getMillis()));
        if (pmc == null) {
            LOG.warn("No Applicable PaymentMethodChart found for chart: "
                    + templatePendingEntry.getChartOfAccountsCode() + " and date: "
                    + document.getDocumentHeader().getWorkflowDocument().getDateCreated());
            return false;
        }
        // Get all the parameters which control these entries
        String feeIncomeChartCode = pmc.getFeeIncomeChartOfAccountsCode();
        String feeIncomeAccountNumber = pmc.getFeeIncomeAccountNumber();
        String feeExpenseObjectCode = pmc.getFeeExpenseFinancialObjectCode();
        String feeIncomeObjectCode = pmc.getFeeIncomeFinancialObjectCode();
        KualiDecimal feeAmount = pmc.getFeeAmount();

        // skip creation if the fee has been set to zero
        if (!KualiDecimal.ZERO.equals(feeAmount)) {
            // grab the explicit entry for the first accounting line and adjust for the foreign draft fee
            GeneralLedgerPendingEntry chargeEntry = new GeneralLedgerPendingEntry(
                    document.getGeneralLedgerPendingEntry(0));
            chargeEntry.setTransactionLedgerEntrySequenceNumber(sequenceHelper.getSequenceCounter());

            // change the object code (expense to the department)
            chargeEntry.setFinancialObjectCode(feeExpenseObjectCode);
            chargeEntry
                    .setFinancialSubObjectCode(GENERAL_LEDGER_PENDING_ENTRY_CODE.getBlankFinancialSubObjectCode());
            chargeEntry.setTransactionLedgerEntryDescription(
                    StringUtils.left("Automatic debit for " + pm.getPaymentMethodName() + " fee", 40));
            chargeEntry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_ACTUAL);

            // retrieve object type
            ObjectCode objectCode = getObjectCodeService().getByPrimaryIdForCurrentYear(
                    chargeEntry.getChartOfAccountsCode(), chargeEntry.getFinancialObjectCode());
            if (objectCode == null) {
                LOG.fatal(
                        "Specified offset object code: " + chargeEntry.getChartOfAccountsCode() + "-"
                                + chargeEntry.getFinancialObjectCode()
                                + " does not exist - failed to generate foreign draft fee entries",
                        new RuntimeException());
                return false;
            }
            chargeEntry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());

            // Set the amount from the parameter
            chargeEntry.setTransactionLedgerEntryAmount(feeAmount);
            chargeEntry.setTransactionDebitCreditCode(reverseEntries ? GL_CREDIT_CODE : GL_DEBIT_CODE);

            document.addPendingEntry(chargeEntry);
            sequenceHelper.increment();

            // handle the offset entry
            GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(chargeEntry);
            getGeneralLedgerPendingEntryService().populateOffsetGeneralLedgerPendingEntry(document.getPostingYear(),
                    chargeEntry, sequenceHelper, offsetEntry);

            document.addPendingEntry(offsetEntry);
            sequenceHelper.increment();

            // Now, create the income entry in the AP Foreign draft fee account

            GeneralLedgerPendingEntry feeIncomeEntry = new GeneralLedgerPendingEntry(
                    document.getGeneralLedgerPendingEntry(0));
            feeIncomeEntry.setTransactionLedgerEntrySequenceNumber(sequenceHelper.getSequenceCounter());

            feeIncomeEntry.setChartOfAccountsCode(feeIncomeChartCode);
            feeIncomeEntry.setAccountNumber(feeIncomeAccountNumber);
            feeIncomeEntry.setFinancialObjectCode(feeIncomeObjectCode);
            feeIncomeEntry
                    .setFinancialSubObjectCode(GENERAL_LEDGER_PENDING_ENTRY_CODE.getBlankFinancialSubObjectCode());
            feeIncomeEntry.setSubAccountNumber(GENERAL_LEDGER_PENDING_ENTRY_CODE.getBlankSubAccountNumber());
            feeIncomeEntry.setProjectCode(GENERAL_LEDGER_PENDING_ENTRY_CODE.getBlankProjectCode());

            // retrieve object type
            objectCode = getObjectCodeService().getByPrimaryIdForCurrentYear(feeIncomeChartCode,
                    feeIncomeObjectCode);
            if (objectCode == null) {
                LOG.fatal(
                        "Specified income object code: " + feeIncomeChartCode + "-" + feeIncomeObjectCode
                                + " does not exist - failed to generate foreign draft income entries",
                        new RuntimeException());
                return false;
            }
            feeIncomeEntry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
            feeIncomeEntry.setTransactionLedgerEntryAmount(feeAmount);
            feeIncomeEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
            feeIncomeEntry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_ACTUAL);

            document.addPendingEntry(feeIncomeEntry);
            sequenceHelper.increment();

            // create the offset entry
            offsetEntry = new GeneralLedgerPendingEntry(feeIncomeEntry);
            getGeneralLedgerPendingEntryService().populateOffsetGeneralLedgerPendingEntry(document.getPostingYear(),
                    feeIncomeEntry, sequenceHelper, offsetEntry);

            document.addPendingEntry(offsetEntry);
            sequenceHelper.increment();
        }
        return true;
    }

    /**
     * Adds up the amounts of all cash to offset GeneralLedgerPendingEntry records on the given AccountingDocument
     * 
     * MOD-PA2000-01 : Copied from the GL Pending entry service since that one does not make any distinction between
     * expense and encumbrance balance types
     * 
     * @author jonathan
     * 
     * @param glPostingDocument the accounting document total the offset to cash amount for
     * @return the offset to cash amount, where debited values have been subtracted and credited values have been added
     */
    protected Map<String, KualiDecimal> getNonOffsetActualTotalsByChart(
            GeneralLedgerPostingDocument glPostingDocument) {
        Map<String, KualiDecimal> totals = new HashMap<String, KualiDecimal>();
        for (GeneralLedgerPendingEntry glpe : glPostingDocument.getGeneralLedgerPendingEntries()) {
            if (KFSConstants.BALANCE_TYPE_ACTUAL.equals(glpe.getFinancialBalanceTypeCode())) {
                if (!glpe.isTransactionEntryOffsetIndicator()) {
                    if (!totals.containsKey(glpe.getChartOfAccountsCode())) {
                        totals.put(glpe.getChartOfAccountsCode(), KualiDecimal.ZERO);
                    }
                    if (glpe.getTransactionDebitCreditCode().equals(KFSConstants.GL_DEBIT_CODE)) {
                        totals.put(glpe.getChartOfAccountsCode(), totals.get(glpe.getChartOfAccountsCode())
                                .add(glpe.getTransactionLedgerEntryAmount()));
                    } else if (glpe.getTransactionDebitCreditCode().equals(KFSConstants.GL_CREDIT_CODE)) {
                        totals.put(glpe.getChartOfAccountsCode(), totals.get(glpe.getChartOfAccountsCode())
                                .subtract(glpe.getTransactionLedgerEntryAmount()));
                    }
                }
            }
        }
        return totals;
    }

    /**
     * When the "A" payment method is used for AP Credit Cards - generate the needed entries in the clearing account.
     * 
     * @param document Document into which to add the generated GL Entries.
     * @param sequenceHelper helper class to keep track of GLPE sequence
     * 
     */
    public boolean generateClearingAccountOffsetEntries(PaymentMethod pm, AccountingDocument document,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper, Map<String, KualiDecimal> actualTotalsByChart) {
        if (actualTotalsByChart == null) {
            actualTotalsByChart = getNonOffsetActualTotalsByChart(document);
        }

        for (String chart : actualTotalsByChart.keySet()) {
            KualiDecimal offsetAmount = actualTotalsByChart.get(chart);
            if (!KualiDecimal.ZERO.equals(offsetAmount)) {
                PaymentMethodChart pmc = pm.getPaymentMethodChartInfo(chart, new java.sql.Date(
                        document.getDocumentHeader().getWorkflowDocument().getDateCreated().getMillis()));
                if (pmc == null) {
                    LOG.warn("No Applicable PaymentMethodChart found for chart: " + chart + " and date: "
                            + document.getDocumentHeader().getWorkflowDocument().getDateCreated());
                    // skip this line - still attempt for other charts
                    continue;
                }
                String clearingChartCode = pmc.getClearingChartOfAccountsCode();
                String clearingAccountNumber = pmc.getClearingAccountNumber();
                String clearingObjectCode = pmc.getClearingFinancialObjectCode(); // liability object code

                GeneralLedgerPendingEntry apOffsetEntry = new GeneralLedgerPendingEntry(
                        document.getGeneralLedgerPendingEntry(0));
                apOffsetEntry
                        .setTransactionLedgerEntrySequenceNumber(new Integer(sequenceHelper.getSequenceCounter()));

                apOffsetEntry.setChartOfAccountsCode(clearingChartCode);
                apOffsetEntry.setAccountNumber(clearingAccountNumber);
                apOffsetEntry.setFinancialObjectCode(clearingObjectCode);
                // if internal billing
                if (StringUtils.equals(PaymentMethod.PM_CODE_INTERNAL_BILLING, pm.getPaymentMethodCode())) {
                    apOffsetEntry.setFinancialSubObjectCode(pmc.getClearingFinancialSubObjectCode());
                    apOffsetEntry.setSubAccountNumber(pmc.getClearingSubAccountNumber());
                } else {
                    apOffsetEntry.setFinancialSubObjectCode(
                            GENERAL_LEDGER_PENDING_ENTRY_CODE.getBlankFinancialSubObjectCode());
                    apOffsetEntry.setSubAccountNumber(GENERAL_LEDGER_PENDING_ENTRY_CODE.getBlankSubAccountNumber());
                }
                apOffsetEntry.setProjectCode(GENERAL_LEDGER_PENDING_ENTRY_CODE.getBlankProjectCode());

                // retrieve object type
                ObjectCode objectCode = getObjectCodeService().getByPrimaryIdForCurrentYear(clearingChartCode,
                        clearingObjectCode);
                if (objectCode == null) {
                    LOG.fatal(
                            "Specified offset object code: " + clearingChartCode + "-" + clearingObjectCode
                                    + " does not exist - failed to generate CC offset entries",
                            new RuntimeException());
                    return false;
                }
                apOffsetEntry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
                apOffsetEntry.setTransactionLedgerEntryAmount(offsetAmount.abs());
                apOffsetEntry.setTransactionDebitCreditCode(
                        offsetAmount.isNegative() ? KFSConstants.GL_DEBIT_CODE : KFSConstants.GL_CREDIT_CODE);
                apOffsetEntry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_ACTUAL);

                document.addPendingEntry(apOffsetEntry);
                sequenceHelper.increment();

                // handle the offset entry
                GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(apOffsetEntry);
                getGeneralLedgerPendingEntryService().populateOffsetGeneralLedgerPendingEntry(
                        document.getPostingYear(), apOffsetEntry, sequenceHelper, offsetEntry);

                document.addPendingEntry(offsetEntry);
                sequenceHelper.increment();
            }
        }

        return true;
    }

    /**
     * If bank specification is enabled generates bank offsetting entries for the document amount
     * 
     */
    public boolean generateDocumentBankOffsetEntries(AccountingDocument document, String bankCode,
            String bankCodePropertyName, String documentTypeCode,
            GeneralLedgerPendingEntrySequenceHelper sequenceHelper, KualiDecimal bankOffsetAmount) {
        boolean success = true;

        if (!getBankService().isBankSpecificationEnabled()) {
            return success;
        }
        Bank bank = getBankService().getByPrimaryId(bankCode);

        if (bankOffsetAmount == null) {
            bankOffsetAmount = getGeneralLedgerPendingEntryService().getOffsetToCashAmount(document).negated();
        }
        if (!KualiDecimal.ZERO.equals(bankOffsetAmount)) {
            GeneralLedgerPendingEntry bankOffsetEntry = new GeneralLedgerPendingEntry();
            success &= getGeneralLedgerPendingEntryService().populateBankOffsetGeneralLedgerPendingEntry(bank,
                    bankOffsetAmount, document, document.getPostingYear(), sequenceHelper, bankOffsetEntry,
                    bankCodePropertyName);

            if (success) {
                AccountingDocumentRuleHelperService accountingDocumentRuleUtil = SpringContext
                        .getBean(AccountingDocumentRuleHelperService.class);
                bankOffsetEntry.setTransactionLedgerEntryDescription(accountingDocumentRuleUtil
                        .formatProperty(KFSKeyConstants.Bank.DESCRIPTION_GLPE_BANK_OFFSET));
                bankOffsetEntry.setFinancialDocumentTypeCode(documentTypeCode);
                document.addPendingEntry(bankOffsetEntry);
                sequenceHelper.increment();

                GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(bankOffsetEntry);
                success &= getGeneralLedgerPendingEntryService().populateOffsetGeneralLedgerPendingEntry(
                        document.getPostingYear(), bankOffsetEntry, sequenceHelper, offsetEntry);
                bankOffsetEntry.setFinancialDocumentTypeCode(documentTypeCode);

                document.addPendingEntry(offsetEntry);
                sequenceHelper.increment();
            }
        }

        return success;
    }

    protected GeneralLedgerPendingEntryService getGeneralLedgerPendingEntryService() {
        if (generalLedgerPendingEntryService == null) {
            generalLedgerPendingEntryService = SpringContext.getBean(GeneralLedgerPendingEntryService.class);
        }
        return generalLedgerPendingEntryService;
    }

    protected ObjectCodeService getObjectCodeService() {
        if (objectCodeService == null) {
            objectCodeService = SpringContext.getBean(ObjectCodeService.class);
        }
        return objectCodeService;
    }

    protected ParameterService getParameterService() {
        if (parameterService == null) {
            parameterService = SpringContext.getBean(ParameterService.class);
        }
        return parameterService;
    }

    protected BusinessObjectService getBusinessObjectService() {
        if (businessObjectService == null) {
            businessObjectService = SpringContext.getBean(BusinessObjectService.class);
        }
        return businessObjectService;
    }

    protected BankService getBankService() {
        if (bankService == null) {
            bankService = SpringContext.getBean(BankService.class);
        }
        return bankService;
    }

    /**
     * Creates final entries for PRNC doc: Reverse all usage 2900 object codes Replaces with 1000 offset object code Generate
     * Bank Offsets for total amounts
     * 
     * @see edu.cornell.kfs.fp.service.CUPaymentMethodGeneralLedgerPendingEntryService#generateFinalEntriesForPRNC(org.kuali.kfs.module.purap.document.PaymentRequestDocument)
     */
    public void generateFinalEntriesForPRNC(PaymentRequestDocument document) {

        GeneralLedgerPendingEntrySequenceHelper sequenceHelper = new GeneralLedgerPendingEntrySequenceHelper(
                getNextAvailableSequence(document.getDocumentNumber()));
        String documentType = CuPaymentRequestDocument.DOCUMENT_TYPE_NON_CHECK;
        if (PaymentMethod.PM_CODE_INTERNAL_BILLING
                .equalsIgnoreCase(((CuPaymentRequestDocument) document).getPaymentMethodCode())) {
            documentType = CuPaymentRequestDocument.DOCUMENT_TYPE_INTERNAL_BILLING;
        }

        // generate bank offset
        if (PaymentMethod.PM_CODE_FOREIGN_DRAFT
                .equalsIgnoreCase(((CuPaymentRequestDocument) document).getPaymentMethodCode())
                || PaymentMethod.PM_CODE_WIRE
                        .equalsIgnoreCase(((CuPaymentRequestDocument) document).getPaymentMethodCode())) {
            generateDocumentBankOffsetEntries((AccountingDocument) document, document.getBankCode(),
                    KRADConstants.DOCUMENT_PROPERTY_NAME + "." + "bankCode", documentType, sequenceHelper,
                    document.getTotalDollarAmount().negated());
        }

        // check for balance type Actual offset pending entries and replace the object code with chart cash object code (currently replacing object code 2900 with 1000)
        List<GeneralLedgerPendingEntry> glpes = document.getGeneralLedgerPendingEntries();

        for (GeneralLedgerPendingEntry glpe : glpes) {
            OffsetDefinition offsetDefinition = SpringContext.getBean(OffsetDefinitionService.class).getByPrimaryId(
                    glpe.getUniversityFiscalYear(), glpe.getChartOfAccountsCode(), documentType,
                    KFSConstants.BALANCE_TYPE_ACTUAL);
            if (glpe.getFinancialObjectCode().equalsIgnoreCase(offsetDefinition.getFinancialObjectCode())) {
                if (ObjectUtils.isNull(glpe.getChart())) {
                    glpe.refreshReferenceObject(KFSPropertyConstants.CHART);
                }
                glpe.setFinancialObjectCode(glpe.getChart().getFinancialCashObjectCode());
                glpe.refreshReferenceObject(KFSPropertyConstants.FINANCIAL_OBJECT);
                glpe.setFinancialObjectTypeCode(glpe.getFinancialObject().getFinancialObjectTypeCode());
            }
        }

        // reverse 2900
        // check for posted entries and create reverse pending entries
        // get all charts on document
        List<AccountingLine> accountingLines = new ArrayList<AccountingLine>();
        if (ObjectUtils.isNotNull(document.getSourceAccountingLines())) {
            accountingLines.addAll(document.getSourceAccountingLines());
        }
        if (ObjectUtils.isNotNull(document.getTargetAccountingLines())) {
            accountingLines.addAll(document.getTargetAccountingLines());
        }
        Map<String, String> chartOffsets = new HashMap<String, String>();
        if (accountingLines.size() > 0) {
            for (AccountingLine accountingLine : accountingLines) {
                if (!chartOffsets.containsKey(accountingLine.getChartOfAccountsCode())) {
                    OffsetDefinition offsetDefinition = SpringContext.getBean(OffsetDefinitionService.class)
                            .getByPrimaryId(accountingLine.getPostingYear(),
                                    accountingLine.getChartOfAccountsCode(), documentType,
                                    KFSConstants.BALANCE_TYPE_ACTUAL);
                    chartOffsets.put(accountingLine.getChartOfAccountsCode(),
                            offsetDefinition.getFinancialObjectCode());

                }
            }
        }

        for (String offsetObjectCode : chartOffsets.values()) {
            Collection<Entry> glEntries = findMatchingGLEntries(document, offsetObjectCode);
            if (glEntries != null && glEntries.size() > 0) {
                for (Entry entry : glEntries) {
                    // create reversal
                    GeneralLedgerPendingEntry glpe = new GeneralLedgerPendingEntry();
                    boolean debit = KFSConstants.GL_CREDIT_CODE
                            .equalsIgnoreCase(entry.getTransactionDebitCreditCode());
                    glpe = getGeneralLedgerPendingEntryService().buildGeneralLedgerPendingEntry(
                            (GeneralLedgerPostingDocument) document, entry.getAccount(), entry.getFinancialObject(),
                            entry.getSubAccountNumber(), entry.getFinancialSubObjectCode(),
                            entry.getOrganizationReferenceId(), entry.getProjectCode(),
                            entry.getReferenceFinancialDocumentNumber(),
                            entry.getReferenceFinancialDocumentTypeCode(),
                            entry.getReferenceFinancialSystemOriginationCode(),
                            entry.getTransactionLedgerEntryDescription(), debit,
                            entry.getTransactionLedgerEntryAmount(), sequenceHelper);
                    glpe.setFinancialDocumentTypeCode(documentType);
                    document.addPendingEntry(glpe);
                    sequenceHelper.increment();
                    // create cash entry
                    GeneralLedgerPendingEntry cashGlpe = new GeneralLedgerPendingEntry();
                    cashGlpe = getGeneralLedgerPendingEntryService().buildGeneralLedgerPendingEntry(
                            (GeneralLedgerPostingDocument) document, entry.getAccount(),
                            entry.getChart().getFinancialCashObject(), entry.getSubAccountNumber(),
                            entry.getFinancialSubObjectCode(), entry.getOrganizationReferenceId(),
                            entry.getProjectCode(), entry.getReferenceFinancialDocumentNumber(),
                            entry.getReferenceFinancialDocumentTypeCode(),
                            entry.getReferenceFinancialSystemOriginationCode(),
                            entry.getTransactionLedgerEntryDescription(), !debit,
                            entry.getTransactionLedgerEntryAmount(), sequenceHelper);
                    cashGlpe.setFinancialDocumentTypeCode(documentType);
                    document.addPendingEntry(cashGlpe);
                    sequenceHelper.increment();
                }
            }
        }

        if (document.getGeneralLedgerPendingEntries() != null
                && document.getGeneralLedgerPendingEntries().size() > 0) {
            for (GeneralLedgerPendingEntry glpe : document.getGeneralLedgerPendingEntries()) {
                glpe.setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.APPROVED);
            }
        }

    }

    /**
     * Retrieves the next available sequence number from the general ledger pending entry table for this document
     * 
     * @param documentNumber
     *            Document number to find next sequence number
     * @return Next available sequence number
     */
    protected int getNextAvailableSequence(String documentNumber) {
        LOG.debug("getNextAvailableSequence() started");
        Map fieldValues = new HashMap();
        fieldValues.put("financialSystemOriginationCode", PURAP_ORIGIN_CODE);
        fieldValues.put("documentNumber", documentNumber);
        List<GeneralLedgerPendingEntry> glpes = (List<GeneralLedgerPendingEntry>) (SpringContext
                .getBean(BusinessObjectService.class)).findMatching(GeneralLedgerPendingEntry.class, fieldValues);

        int count = 0;
        if (CollectionUtils.isNotEmpty(glpes)) {
            for (GeneralLedgerPendingEntry glpe : glpes) {
                if (glpe.getTransactionLedgerEntrySequenceNumber() > count) {
                    count = glpe.getTransactionLedgerEntrySequenceNumber();
                }
            }
        }
        return count + 1;
    }

    public Collection<Entry> findMatchingGLEntries(PaymentRequestDocument document, String offsetObjectCode) {
        Map<String, String> keyValues = new HashMap<String, String>();
        keyValues.put(KFSPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber());
        keyValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, offsetObjectCode);

        return getBusinessObjectService().findMatching(Entry.class, keyValues);
    }

}