/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.ar.document.service.impl;

import org.kuali.kfs.coa.service.ChartService;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.krad.exception.InfrastructureException;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.ar.ArPropertyConstants;
import org.kuali.kfs.module.ar.businessobject.AccountsReceivableDocumentHeader;
import org.kuali.kfs.module.ar.businessobject.CashControlDetail;
import org.kuali.kfs.module.ar.businessobject.SystemInformation;
import org.kuali.kfs.module.ar.document.CashControlDocument;
import org.kuali.kfs.module.ar.document.PaymentApplicationDocument;
import org.kuali.kfs.module.ar.document.service.AccountsReceivableDocumentHeaderService;
import org.kuali.kfs.module.ar.document.service.CashControlDocumentService;
import org.kuali.kfs.module.ar.document.service.SystemInformationService;
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.ElectronicPaymentClaim;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.SystemOptions;
import org.kuali.kfs.sys.document.service.AccountingDocumentRuleHelperService;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.kfs.sys.service.OptionsService;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.KewApiServiceLocator;
import org.kuali.rice.kew.api.doctype.DocumentType;
import org.kuali.rice.kew.api.doctype.DocumentTypeService;
import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue;
import org.kuali.rice.kew.api.exception.WorkflowException;

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

public class CashControlDocumentServiceImpl implements CashControlDocumentService {

    private AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService;
    private DocumentService documentService;
    private BusinessObjectService businessObjectService;
    private GeneralLedgerPendingEntryService glpeService;
    private OptionsService optionsService;
    private SystemInformationService systemInformationService;
    private DataDictionaryService dataDictionaryService;
    private ChartService chartService;
    private UniversityDateService universityDateService;
    private BankService bankService;
    private AccountingDocumentRuleHelperService accountingDocumentRuleHelperService;
    private DocumentTypeService documentTypeService;

    /**
     * @see org.kuali.kfs.module.ar.document.service.CashControlDocumentService#createAndSavePaymentApplicationDocument(java.lang.String,
     * org.kuali.kfs.module.ar.document.CashControlDocument, org.kuali.kfs.module.ar.businessobject.CashControlDetail)
     */
    @Override
    public PaymentApplicationDocument createAndSavePaymentApplicationDocument(String description, CashControlDocument cashControlDocument, CashControlDetail cashControlDetail) throws WorkflowException {

        // create a new PaymentApplicationdocument
        PaymentApplicationDocument doc = (PaymentApplicationDocument) documentService.getNewDocument(PaymentApplicationDocument.class);

        // set a description to say that this application document has been created by the CashControldocument
        doc.getDocumentHeader().setDocumentDescription(description);

        // the line amount for the new PaymentApplicationDocument should be the line amount in the new cash control detail
        doc.getFinancialSystemDocumentHeader().setFinancialDocumentTotalAmount(cashControlDetail.getFinancialDocumentLineAmount());

        //  re-use the Processing Chart/Org from the CashControlDoc's arDocHeader
        String processingChartCode = cashControlDocument.getAccountsReceivableDocumentHeader().getProcessingChartOfAccountCode();
        String processingOrgCode = cashControlDocument.getAccountsReceivableDocumentHeader().getProcessingOrganizationCode();

        //  create the arDocHeader
        // NOTE that we re-use the processing chart/org from the CashControl document, rather than
        // pull from the current user.  This is done to bypass some challenges in the Lockbox batch process,
        // and we dont believe it will impact the processing CashControl processing, as the PayApps created
        // always have the same processing chart/org as the cashcontrol doc that creates is
        AccountsReceivableDocumentHeader arDocHeader = new AccountsReceivableDocumentHeader();
        arDocHeader.setProcessingChartOfAccountCode(processingChartCode);
        arDocHeader.setProcessingOrganizationCode(processingOrgCode);
        arDocHeader.setDocumentNumber(doc.getDocumentNumber());
        arDocHeader.setCustomerNumber(cashControlDetail.getCustomerNumber());
        doc.setAccountsReceivableDocumentHeader(arDocHeader);

        doc.getDocumentHeader().setOrganizationDocumentNumber(cashControlDocument.getDocumentNumber());

        // refresh nonupdatable references and save the PaymentApplicationDocument
        doc.refreshNonUpdateableReferences();

        // This attribute is transient but is necessary to sort of bootstrap, allowing some validation rules
        // to succeed before the document is saved again.
        doc.setCashControlDetail(cashControlDetail);

        doc.setNonAppliedHolding(null);

        documentService.saveDocument(doc);

        //RICE20 replaced searchableAttributeProcessingService.indexDocument with DocumentAttributeIndexingQueue.indexDocument
        DocumentType documentType = documentTypeService.getDocumentTypeByName(doc.getFinancialDocumentTypeCode());
        DocumentAttributeIndexingQueue queue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(documentType.getApplicationId());
        queue.indexDocument(doc.getDocumentNumber());

        return doc;
    }

    /**
     * @see org.kuali.kfs.module.ar.document.service.CashControlDocumentService#addNewCashControlDetail(java.lang.String,
     * org.kuali.kfs.module.ar.document.CashControlDocument, org.kuali.kfs.module.ar.businessobject.CashControlDetail)
     */
    @Override
    public void addNewCashControlDetail(String description, CashControlDocument cashControlDocument, CashControlDetail cashControlDetail) throws WorkflowException {

        // add cash control detail
        cashControlDocument.addCashControlDetail(cashControlDetail);

        // create a new PaymentApplicationdocument
        // This has to happen after adding the cash control detail so that payment application saving rules succeed.
        PaymentApplicationDocument paymentApplicationDocument = createAndSavePaymentApplicationDocument(description, cashControlDocument, cashControlDetail);

        // update new cash control detail fields to refer to the new created PaymentApplicationDocument
        cashControlDetail.setReferenceFinancialDocument(paymentApplicationDocument);
        cashControlDetail.setReferenceFinancialDocumentNumber(paymentApplicationDocument.getDocumentNumber());

        // Save the cash control document, but do NOT do a full workflow-save, just persist the state
        paymentApplicationDocument.populateDocumentForRouting();
        paymentApplicationDocument.prepareForSave();
        documentService.prepareWorkflowDocument(paymentApplicationDocument);
        documentService.saveDocument(cashControlDocument);

        //RICE20 replaced searchableAttributeProcessingService.indexDocument with DocumentAttributeIndexingQueue.indexDocument
        DocumentType cashDocumentType = documentTypeService.getDocumentTypeByName(cashControlDocument.getFinancialDocumentTypeCode());
        DocumentAttributeIndexingQueue cashQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(cashDocumentType.getApplicationId());
        cashQueue.indexDocument(cashControlDocument.getDocumentNumber());

        DocumentType payDocumentType = documentTypeService.getDocumentTypeByName(paymentApplicationDocument.getFinancialDocumentTypeCode());
        DocumentAttributeIndexingQueue payQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(payDocumentType.getApplicationId());
        payQueue.indexDocument(paymentApplicationDocument.getDocumentNumber());
    }

    /**
     * @see org.kuali.kfs.module.ar.document.service.CashControlDocumentService#saveGLPEs(org.kuali.kfs.module.ar.document.CashControlDocument)
     */
    @Override
    public void saveGLPEs(CashControlDocument cashControlDocument) {
        businessObjectService.save(cashControlDocument.getGeneralLedgerPendingEntries());
    }

    /**
     * @see org.kuali.kfs.module.ar.document.service.CashControlDocumentService#createCashReceiptGLPEs(org.kuali.kfs.module.ar.document.CashControlDocument,
     * org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
     */
    @Override
    public boolean createCashReceiptGLPEs(CashControlDocument cashControlDocument, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {

        boolean isSuccess = true;
        AccountingLine accountingLine = null;
        GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();

        Integer currentFiscalYear = universityDateService.getCurrentFiscalYear();

        // get accounts receivable document header to get processing chart of accounts and organization
        AccountsReceivableDocumentHeader accountsReceivableDocumentHeader = cashControlDocument.getAccountsReceivableDocumentHeader();
        String processingChartOfAccountCode = accountsReceivableDocumentHeader.getProcessingChartOfAccountCode();
        String processingOrganizationCode = accountsReceivableDocumentHeader.getProcessingOrganizationCode();

        // get system information by processing chart of accounts and organization
        Map criteria = new HashMap();
        criteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, currentFiscalYear);
        criteria.put(KFSPropertyConstants.PROCESSING_CHART_OF_ACCT_CD, processingChartOfAccountCode);
        criteria.put(KFSPropertyConstants.PROCESSING_ORGANIZATION_CODE, processingOrganizationCode);
        SystemInformation systemInformation = businessObjectService.findByPrimaryKey(SystemInformation.class, criteria);

        // if there is no system information for this processing chart of accounts and organization return false; glpes cannot be
        // created
        if (ObjectUtils.isNull(systemInformation)) {
            return false;
        }

        // get current year options
        SystemOptions options = optionsService.getCurrentYearOptions();

        // build an accounting line that will be used to create the glpe
        // ignore university clearing sub-object-code KULAR-633
        accountingLine = buildAccountingLine(systemInformation.getUniversityClearingAccountNumber(), systemInformation.getUniversityClearingSubAccountNumber(), systemInformation.getUniversityClearingObjectCode(), null, systemInformation.getUniversityClearingChartOfAccountsCode(), KFSConstants.GL_CREDIT_CODE, cashControlDocument.getCashControlTotalAmount());
        // get document type for the glpes
        String financialSystemDocumentTypeCode = getDataDictionaryService().getDocumentTypeNameByClass(cashControlDocument.getClass());
        // create and add the new explicit entry based on this accounting line
        explicitEntry = createAndAddNewExplicitEntry(cashControlDocument, sequenceHelper, accountingLine, options, financialSystemDocumentTypeCode);
        // create and add the offset entry
        isSuccess &= createAndAddTheOffsetEntry(cashControlDocument, explicitEntry, accountingLine, sequenceHelper);

        return isSuccess;
    }

    /**
     * Creates bank offset GLPEs for the cash control document
     *
     * @param cashControlDocument the document to create cash control GLPEs for
     * @param sequenceHelper      the sequence helper which will sequence the new GLPEs
     * @return true if the new bank offset GLPEs were created successfully, false otherwise
     */
    @Override
    public boolean createBankOffsetGLPEs(CashControlDocument cashControlDocument, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        boolean isSuccess = true;
        if (bankService.isBankSpecificationEnabled()) {
            // get associated bank
            Bank bank = bankService.getByPrimaryId(cashControlDocument.getBankCode());
            GeneralLedgerPendingEntry bankOffsetEntry = new GeneralLedgerPendingEntry();
            final KualiDecimal totalAmount = glpeService.getOffsetToCashAmount(cashControlDocument).negated();
            // add additional GLPE's based on bank code
            if (glpeService.populateBankOffsetGeneralLedgerPendingEntry(bank, totalAmount, cashControlDocument, cashControlDocument.getPostingYear(), sequenceHelper, bankOffsetEntry, ArPropertyConstants.CashControlDocumentFields.BANK_CODE)) {
                bankOffsetEntry.setTransactionLedgerEntryDescription(accountingDocumentRuleHelperService.formatProperty(KFSKeyConstants.Bank.DESCRIPTION_GLPE_BANK_OFFSET));
                cashControlDocument.addPendingEntry(bankOffsetEntry);
                sequenceHelper.increment();
                GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(bankOffsetEntry);
                isSuccess &= glpeService.populateOffsetGeneralLedgerPendingEntry(cashControlDocument.getPostingYear(), bankOffsetEntry, sequenceHelper, offsetEntry);
                cashControlDocument.addPendingEntry(offsetEntry);
                sequenceHelper.increment();
            } else {
                isSuccess = false;
            }
        }
        return isSuccess;
    }

    /**
     * @see org.kuali.kfs.module.ar.document.service.CashControlDocumentService#createDistributionOfIncomeAndExpenseGLPEs(org.kuali.kfs.module.ar.document.CashControlDocument, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
     */
    @Override
    public boolean createDistributionOfIncomeAndExpenseGLPEs(CashControlDocument cashControlDocument, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        boolean isSuccess = true;

        AccountingLine accountingLine = null;
        GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();

        Integer currentFiscalYear = universityDateService.getCurrentFiscalYear();

        // get the accounts receivable document header to get the processing chart of accounts code and organization
        AccountsReceivableDocumentHeader accountsReceivableDocumentHeader = cashControlDocument.getAccountsReceivableDocumentHeader();
        String processingChartOfAccountCode = accountsReceivableDocumentHeader.getProcessingChartOfAccountCode();
        String processingOrganizationCode = accountsReceivableDocumentHeader.getProcessingOrganizationCode();

        // get system information by processing chart of acccounts and organization
        Map criteria = new HashMap();
        criteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, currentFiscalYear);
        criteria.put(KFSPropertyConstants.PROCESSING_CHART_OF_ACCT_CD, processingChartOfAccountCode);
        criteria.put(KFSPropertyConstants.PROCESSING_ORGANIZATION_CODE, processingOrganizationCode);
        SystemInformation systemInformation = businessObjectService.findByPrimaryKey(SystemInformation.class, criteria);

        // if there is no system information set up for this processing chart of accounts and organization return false, glpes
        // cannot be created
        if (ObjectUtils.isNull(systemInformation)) {
            return false;
        }

        // get current year options
        SystemOptions options = optionsService.getCurrentYearOptions();

        // build dummy accounting line for gl population
        // ignore university clearing sub-object-code KULAR-633
        accountingLine = buildAccountingLine(systemInformation.getUniversityClearingAccountNumber(), systemInformation.getUniversityClearingSubAccountNumber(), systemInformation.getUniversityClearingObjectCode(), null, systemInformation.getUniversityClearingChartOfAccountsCode(), KFSConstants.GL_CREDIT_CODE, cashControlDocument.getCashControlTotalAmount());
        // get document type for the glpes
        String financialSystemDocumentTypeCode = getDataDictionaryService().getDocumentTypeNameByClass(cashControlDocument.getClass());
        // create and add the new explicit entry based on this accounting line
        explicitEntry = createAndAddNewExplicitEntry(cashControlDocument, sequenceHelper, accountingLine, options, financialSystemDocumentTypeCode);
        // create and add the offset entry
        isSuccess &= createAndAddTheOffsetEntry(cashControlDocument, explicitEntry, accountingLine, sequenceHelper);

        // get Advance Deposit accounting lines by getting Electronic Payment Claims
        Map criteria2 = new HashMap();
        criteria2.put(ArPropertyConstants.CashControlDetailFields.REFERENCE_FINANCIAL_DOC_NBR, cashControlDocument.getDocumentNumber());
        Collection<ElectronicPaymentClaim> electronicPaymentClaims = businessObjectService.findMatching(ElectronicPaymentClaim.class, criteria2);

        // no EFT claims found
        if (ObjectUtils.isNull(electronicPaymentClaims)) {
            return false;
        }

        // for each Advance Deposit accounting line, create a reverse GLPE in CashControl document.
        for (ElectronicPaymentClaim electronicPaymentClaim : electronicPaymentClaims) {
            Map criteria3 = new HashMap();
            criteria3.put(KFSPropertyConstants.DOCUMENT_NUMBER, electronicPaymentClaim.getDocumentNumber());
            criteria3.put(ArPropertyConstants.SEQUENCE_NUMBER, electronicPaymentClaim.getFinancialDocumentLineNumber());
            criteria3.put(KFSPropertyConstants.FINANCIAL_DOCUMENT_LINE_TYPE_CODE, KFSConstants.SOURCE_ACCT_LINE_TYPE_CODE);
            SourceAccountingLine advanceDepositAccountingLine = businessObjectService.findByPrimaryKey(SourceAccountingLine.class, criteria3);

            // build dummy accounting line for gl creation
            accountingLine = buildAccountingLine(advanceDepositAccountingLine.getAccountNumber(), advanceDepositAccountingLine.getSubAccountNumber(), advanceDepositAccountingLine.getFinancialObjectCode(), advanceDepositAccountingLine.getFinancialSubObjectCode(), advanceDepositAccountingLine.getChartOfAccountsCode(), KFSConstants.GL_DEBIT_CODE, advanceDepositAccountingLine.getAmount());
            // create and add the new explicit entry based on this accounting line
            explicitEntry = createAndAddNewExplicitEntry(cashControlDocument, sequenceHelper, accountingLine, options, financialSystemDocumentTypeCode);
            // create and add the offset entry
            isSuccess &= createAndAddTheOffsetEntry(cashControlDocument, explicitEntry, accountingLine, sequenceHelper);
        }

        return isSuccess;
    }

    /**
     * @see org.kuali.kfs.module.ar.document.service.CashControlDocumentService#createGeneralErrorCorrectionGLPEs(org.kuali.kfs.module.ar.document.CashControlDocument, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
     */
    @Override
    public boolean createGeneralErrorCorrectionGLPEs(CashControlDocument cashControlDocument, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        boolean isSuccess = true;

        Integer currentFiscalYear = universityDateService.getCurrentFiscalYear();
        AccountingLine accountingLine = null;

        // get accounts receivable document header to get processing chart of accounts and organization code
        AccountsReceivableDocumentHeader accountsReceivableDocumentHeader = cashControlDocument.getAccountsReceivableDocumentHeader();
        String processingChartOfAccountCode = accountsReceivableDocumentHeader.getProcessingChartOfAccountCode();
        String processingOrganizationCode = accountsReceivableDocumentHeader.getProcessingOrganizationCode();

        // get system information by processing chart of accounts and organization code
        Map criteria = new HashMap();
        criteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, currentFiscalYear);
        criteria.put(KFSPropertyConstants.PROCESSING_CHART_OF_ACCT_CD, processingChartOfAccountCode);
        criteria.put(KFSPropertyConstants.PROCESSING_ORGANIZATION_CODE, processingOrganizationCode);
        SystemInformation systemInformation = businessObjectService.findByPrimaryKey(SystemInformation.class, criteria);

        // if no system information is set up for this processing chart of accounts and organization code return false, the glpes
        // cannot be created
        if (ObjectUtils.isNull(systemInformation)) {
            return false;
        }

        GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();

        SystemOptions options = optionsService.getCurrentYearOptions();

        // build dummy accounting line for gl creation
        // ignore university clearing sub-object-code KULAR-633
        accountingLine = buildAccountingLine(systemInformation.getUniversityClearingAccountNumber(), systemInformation.getUniversityClearingSubAccountNumber(), systemInformation.getCreditCardObjectCode(), null, systemInformation.getUniversityClearingChartOfAccountsCode(), KFSConstants.GL_DEBIT_CODE, cashControlDocument.getCashControlTotalAmount());
        //get document type for the glpes
        String financialSystemDocumentTypeCode = getDataDictionaryService().getDocumentTypeNameByClass(cashControlDocument.getClass());
        // create and add the new explicit entry based on this accounting line
        createAndAddNewExplicitEntry(cashControlDocument, sequenceHelper, accountingLine, options, financialSystemDocumentTypeCode);

        // build dummy accounting line for gl creation
        // ignore university clearing sub-object-code KULAR-633
        accountingLine = buildAccountingLine(systemInformation.getUniversityClearingAccountNumber(), systemInformation.getUniversityClearingSubAccountNumber(), systemInformation.getUniversityClearingObjectCode(), null, systemInformation.getUniversityClearingChartOfAccountsCode(), KFSConstants.GL_CREDIT_CODE, cashControlDocument.getCashControlTotalAmount());
        // create and add the new explicit entry based on this accounting line
        createAndAddNewExplicitEntry(cashControlDocument, sequenceHelper, accountingLine, options, financialSystemDocumentTypeCode);

        return isSuccess;
    }

    /**
     * @see org.kuali.kfs.module.ar.document.service.CashControlDocumentService#getLockboxNumber(org.kuali.kfs.module.ar.document.CashControlDocument)
     */
    @Override
    public String getLockboxNumber(CashControlDocument cashControlDocument) {

        Integer currentFiscalYear = universityDateService.getCurrentFiscalYear();
        String chartOfAccountsCode = cashControlDocument.getAccountsReceivableDocumentHeader().getProcessingChartOfAccountCode();
        String processingOrgCode = cashControlDocument.getAccountsReceivableDocumentHeader().getProcessingOrganizationCode();
        Map criteria = new HashMap();
        criteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, currentFiscalYear);
        criteria.put(KFSPropertyConstants.PROCESSING_CHART_OF_ACCT_CD, chartOfAccountsCode);
        criteria.put(KFSPropertyConstants.PROCESSING_ORGANIZATION_CODE, processingOrgCode);
        SystemInformation systemInformation = businessObjectService.findByPrimaryKey(SystemInformation.class, criteria);

        return (systemInformation == null) ? null : systemInformation.getLockboxNumber();

    }

    /**
     * This method creates an accounting line.
     *
     * @param systemInformation System Information used to set accounting line data
     * @param debitOrCredit     Tells if it is debit or credit
     * @param amount            The amount
     * @return The created accounting line
     */
    protected AccountingLine buildAccountingLine(String accountNumber, String subAccountNumber, String objectCode, String subObjectCode, String chartOfAccountsCode, String debitOrCredit, KualiDecimal amount) {

        AccountingLine accountingLine = null;

        // get new accounting line
        try {
            accountingLine = SourceAccountingLine.class.newInstance();
        } catch (IllegalAccessException e) {
            throw new InfrastructureException("unable to access sourceAccountingLineClass", e);
        } catch (InstantiationException e) {
            throw new InfrastructureException("unable to instantiate sourceAccountingLineClass", e);
        }

        accountingLine.setAccountNumber(accountNumber);
        accountingLine.setFinancialObjectCode(objectCode);
        accountingLine.setSubAccountNumber(subAccountNumber);
        accountingLine.setChartOfAccountsCode(chartOfAccountsCode);
        accountingLine.setFinancialSubObjectCode(subObjectCode);
        accountingLine.setDebitCreditCode(debitOrCredit);
        accountingLine.setAmount(amount);

        return accountingLine;

    }

    /**
     * This method creates and adds a new explicit glpe
     *
     * @param cashControlDocument the cash control document
     * @param sequenceHelper      sequence helper
     * @param accountingLine      the accounting line based on which the glpe is created
     * @param options             the current year oprions
     * @param documentType        the document type to be associated with the glpe
     */
    protected GeneralLedgerPendingEntry createAndAddNewExplicitEntry(CashControlDocument cashControlDocument, GeneralLedgerPendingEntrySequenceHelper sequenceHelper, AccountingLine accountingLine, SystemOptions options, String documentType) {

        GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();

        glpeService.populateExplicitGeneralLedgerPendingEntry(cashControlDocument, accountingLine, sequenceHelper, explicitEntry);

        explicitEntry.setFinancialDocumentTypeCode(documentType);
        explicitEntry.setDocumentNumber(cashControlDocument.getDocumentNumber());

        // add the new explicit entry to the document now
        cashControlDocument.addPendingEntry(explicitEntry);

        cashControlDocument.customizeExplicitGeneralLedgerPendingEntry(accountingLine, explicitEntry);

        // increment the sequence counter
        sequenceHelper.increment();
        return explicitEntry;

    }

    /**
     * This method creates and adds an offset entry for the given explicit entry and accounting line.
     *
     * @param cashControlDocument the cash control document
     * @param explicitEntry       the explicit entry for which we create the offset entry
     * @param accountingLine      the accounting line used to populate the offset entry
     * @param sequenceHelper      the sequence helper
     * @return true if successfuly created and added, false otherwise
     */
    protected boolean createAndAddTheOffsetEntry(CashControlDocument cashControlDocument, GeneralLedgerPendingEntry explicitEntry, AccountingLine accountingLine, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
        boolean isSuccess = true;

        // create offset
        GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(explicitEntry);
        isSuccess &= glpeService.populateOffsetGeneralLedgerPendingEntry(cashControlDocument.getPostingYear(), explicitEntry, sequenceHelper, offsetEntry);
        cashControlDocument.customizeOffsetGeneralLedgerPendingEntry(accountingLine, explicitEntry, offsetEntry);

        // add the offset
        cashControlDocument.addPendingEntry(offsetEntry);

        // increment the sequence counter
        sequenceHelper.increment();

        return isSuccess;
    }


    /**
     * This method gets the system information servcie
     *
     * @return the system information service
     */
    public SystemInformationService getSystemInformationService() {
        return systemInformationService;
    }

    /**
     * This method sets the system information service
     *
     * @param systemInformationService
     */
    public void setSystemInformationService(SystemInformationService systemInformationService) {
        this.systemInformationService = systemInformationService;
    }

    /**
     * This method gets the chart service
     *
     * @return
     */
    public ChartService getChartService() {
        return chartService;
    }

    /**
     * This method sets the chart service
     *
     * @param chartService
     */
    public void setChartService(ChartService chartService) {
        this.chartService = chartService;
    }

    /**
     * Gets the dataDictionaryService attribute.
     *
     * @return Returns the dataDictionaryService.
     */
    public DataDictionaryService getDataDictionaryService() {
        return dataDictionaryService;
    }

    /**
     * Sets the dataDictionaryService attribute value.
     *
     * @param dataDictionaryService The dataDictionaryService to set.
     */
    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
        this.dataDictionaryService = dataDictionaryService;
    }

    /**
     * This method gets the glpe service
     *
     * @return
     */
    public GeneralLedgerPendingEntryService getGlpeService() {
        return glpeService;
    }

    /**
     * This method sets the glpe service
     *
     * @param glpeService
     */
    public void setGlpeService(GeneralLedgerPendingEntryService glpeService) {
        this.glpeService = glpeService;
    }

    /**
     * This method gets the business object service
     *
     * @return the business object service
     */
    public BusinessObjectService getBusinessObjectService() {
        return businessObjectService;
    }

    /**
     * This method sets the business object service
     *
     * @param businessObjectService
     */
    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    /**
     * This method gets the option service
     *
     * @return the option service
     */
    public OptionsService getOptionsService() {
        return optionsService;
    }

    /**
     * This method sets the option service
     *
     * @param optionsService
     */
    public void setOptionsService(OptionsService optionsService) {
        this.optionsService = optionsService;
    }

    /**
     * This method gets the accounts receivable header
     *
     * @return the accounts receivable header
     */
    public AccountsReceivableDocumentHeaderService getAccountsReceivableDocumentHeaderService() {
        return accountsReceivableDocumentHeaderService;
    }

    /**
     * This method sets the accounts receivable header
     *
     * @param accountsReceivableDocumentHeaderService
     */
    public void setAccountsReceivableDocumentHeaderService(AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService) {
        this.accountsReceivableDocumentHeaderService = accountsReceivableDocumentHeaderService;
    }

    /**
     * This method gets the document service
     *
     * @return the document service
     */
    public DocumentService getDocumentService() {
        return documentService;
    }

    /**
     * This method sets the document service
     *
     * @param documentService
     */
    public void setDocumentService(DocumentService documentService) {
        this.documentService = documentService;
    }

    public UniversityDateService getUniversityDateService() {
        return universityDateService;
    }

    public void setUniversityDateService(UniversityDateService universityDateService) {
        this.universityDateService = universityDateService;
    }

    @NonTransactional
    public void setBankService(BankService bankService) {
        this.bankService = bankService;
    }

    /**
     * @return the implementation of the AccountingDocumentRuleHelperService to use
     */
    public AccountingDocumentRuleHelperService getAccountingDocumentRuleHelperService() {
        return accountingDocumentRuleHelperService;
    }

    /**
     * Sets the implementation of the AccountingDocumentRuleHelperService to use
     *
     * @param accountingDocumentRuleService the implementation of the AccountingDocumentRuleHelperService to use
     */
    public void setAccountingDocumentRuleHelperService(AccountingDocumentRuleHelperService accountingDocumentRuleService) {
        this.accountingDocumentRuleHelperService = accountingDocumentRuleService;
    }

    public void setDocumentTypeService(DocumentTypeService documentTypeService) {
        this.documentTypeService = documentTypeService;
    }
}
