/*
 * 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.cab.document.service.impl;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.integration.purap.CapitalAssetLocation;
import org.kuali.kfs.integration.purap.ItemCapitalAsset;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.util.KRADConstants;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.cab.CabConstants;
import org.kuali.kfs.module.cab.CabPropertyConstants;
import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry;
import org.kuali.kfs.module.cab.businessobject.Pretag;
import org.kuali.kfs.module.cab.businessobject.PretagDetail;
import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument;
import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset;
import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount;
import org.kuali.kfs.module.cab.document.service.PurApInfoService;
import org.kuali.kfs.module.cab.document.service.PurApLineDocumentService;
import org.kuali.kfs.module.cab.document.service.PurApLineService;
import org.kuali.kfs.module.cab.document.web.PurApLineSession;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.CamsPropertyConstants;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail;
import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail;
import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
import org.kuali.kfs.module.cam.businessobject.AssetType;
import org.kuali.kfs.module.cam.businessobject.defaultvalue.NextAssetNumberFinder;
import org.kuali.kfs.module.cam.document.AssetPaymentDocument;
import org.kuali.kfs.module.cam.document.service.AssetGlobalService;
import org.kuali.kfs.module.cam.document.service.AssetService;
import org.kuali.kfs.module.purap.PurapPropertyConstants;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderCapitalAssetSystem;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.sys.businessobject.Building;
import org.kuali.kfs.sys.businessobject.Room;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.validation.event.DocumentSystemSaveEvent;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.location.api.campus.Campus;
import org.kuali.rice.location.api.campus.CampusService;

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


/**
 * This class provides default implementations of {@link PurApLineService}
 */
public class PurApLineDocumentServiceImpl implements PurApLineDocumentService {
    private static final Logger LOG = Logger.getLogger(PurApLineDocumentServiceImpl.class);
    private BusinessObjectService businessObjectService;
    private DocumentService documentService;
    private PurApLineService purApLineService;
    private PurApInfoService purApInfoService;
    private AssetGlobalService assetGlobalService;

    public static final String DOCUMENT_DESC_PREFIX = "CAB created for ";

    /**
     * @see org.kuali.kfs.module.cab.document.service.PurApLineDocumentService#processApplyPayment(PurchasingAccountsPayableItemAsset,
     * List, PurApLineSession, Integer)
     */
    @Override
    public String processApplyPayment(PurchasingAccountsPayableItemAsset selectedItem, List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession, Integer requisitionIdentifer) throws WorkflowException {
        AssetPaymentDocument newDocument = (AssetPaymentDocument) documentService.getNewDocument(AssetPaymentDocument.class);
        if (ObjectUtils.isNotNull(selectedItem) && ObjectUtils.isNotNull(selectedItem.getPurchasingAccountsPayableDocument())) {
            newDocument.getDocumentHeader().setDocumentDescription(DOCUMENT_DESC_PREFIX + selectedItem.getPurchasingAccountsPayableDocument().getDocumentTypeCode() + " " + selectedItem.getDocumentNumber());
        }
        // set assetPaymentDetail list
        createAssetPaymentDetails(newDocument.getSourceAccountingLines(), selectedItem, newDocument.getDocumentNumber(), requisitionIdentifer);

        // If PurAp user entered capitalAssetNumbers, include them in the Asset Payment Document.
        if (selectedItem.getPurApItemAssets() != null && !selectedItem.getPurApItemAssets().isEmpty()) {
            createAssetPaymentAssetDetails(newDocument.getAssetPaymentAssetDetail(), selectedItem, newDocument.getDocumentNumber());

        }
        // set origin code in the Asset Payment Document
        newDocument.setCapitalAssetBuilderOriginIndicator(true);
        documentService.saveDocument(newDocument);

        postProcessCreatingDocument(selectedItem, purApDocs, purApLineSession, newDocument.getDocumentNumber());
        return newDocument.getDocumentNumber();
    }


    /**
     * Create AssetPaymentAssetDetail List for assetPaymentDocument.
     *
     * @param assetPaymentAssetDetails
     * @param selectedItem
     * @param documentNumber
     */
    protected void createAssetPaymentAssetDetails(List assetPaymentAssetDetails, PurchasingAccountsPayableItemAsset selectedItem, String documentNumber) {
        for (ItemCapitalAsset capitalAssetNumber : selectedItem.getPurApItemAssets()) {
            // check if capitalAssetNumber is a valid value or not.
            if (isAssetNumberValid(capitalAssetNumber.getCapitalAssetNumber())) {
                AssetPaymentAssetDetail assetDetail = new AssetPaymentAssetDetail();
                assetDetail.setDocumentNumber(documentNumber);

                assetDetail.setCapitalAssetNumber(capitalAssetNumber.getCapitalAssetNumber());
                assetDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentAssetDetail.ASSET);

                AssetService assetService = SpringContext.getBean(AssetService.class);
                Asset candidateAsset = assetDetail.getAsset();
                // asset must be an active & not retired. Duplication check is done during feeding asset numbers from PurAp.
                if (ObjectUtils.isNotNull(candidateAsset) && assetService.isCapitalAsset(candidateAsset) && !assetService.isAssetRetired(candidateAsset)) {
                    assetDetail.setPreviousTotalCostAmount(assetDetail.getAsset().getTotalCostAmount());
                    assetPaymentAssetDetails.add(assetDetail);
                }

            }
        }
    }


    /**
     * Check the asset table if given capitalAssetNumber is valid or not.
     *
     * @param capitalAssetNumber
     * @return
     */
    protected boolean isAssetNumberValid(Long capitalAssetNumber) {
        Map pKeys = new HashMap<String, Object>();

        pKeys.put(CamsPropertyConstants.Asset.CAPITAL_ASSET_NUMBER, capitalAssetNumber);

        Asset asset = businessObjectService.findByPrimaryKey(Asset.class, pKeys);

        return ObjectUtils.isNotNull(asset);
    }


    /**
     * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processCreateAsset(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset,
     * org.kuali.kfs.module.cab.document.web.struts.PurApLineForm)
     */
    @Override
    public String processCreateAsset(PurchasingAccountsPayableItemAsset selectedItem, List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession, Integer requisitionIdentifier) throws WorkflowException {
        // Create new CAMS asset global document
        MaintenanceDocument newDocument = (MaintenanceDocument) documentService.getNewDocument(CamsConstants.DocumentTypeName.ASSET_ADD_GLOBAL);
        newDocument.getNewMaintainableObject().setMaintenanceAction(KRADConstants.MAINTENANCE_NEW_ACTION);
        if (ObjectUtils.isNotNull(selectedItem) && ObjectUtils.isNotNull(selectedItem.getPurchasingAccountsPayableDocument())) {
            newDocument.getDocumentHeader().setDocumentDescription(DOCUMENT_DESC_PREFIX + selectedItem.getPurchasingAccountsPayableDocument().getDocumentTypeCode() + " " + selectedItem.getDocumentNumber());
        }

        // populate pre-tagging entry
        Integer poId = selectedItem.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier();
        Pretag preTag = purApLineService.getPreTagLineItem(poId, selectedItem.getItemLineNumber());

        // create asset global BO instance
        AssetGlobal assetGlobal = createAssetGlobal(selectedItem, newDocument.getDocumentNumber(), preTag, requisitionIdentifier);

        // save asset global BO to the document
        newDocument.getNewMaintainableObject().setBusinessObject(assetGlobal);

        // save without doing validation - this is just to get us to the asset global maint. screen, the validation will be applied there
        documentService.saveDocument(newDocument, DocumentSystemSaveEvent.class);

        postProcessCreatingDocument(selectedItem, purApDocs, purApLineSession, newDocument.getDocumentNumber());

        // Save for in-active pre-tag detail if it got feed into CAMS
        if (isItemPretagged(preTag)) {
            businessObjectService.save(preTag);
        }
        return newDocument.getDocumentNumber();
    }


    /**
     * Process item line, cab document after creating CAMs document.
     *
     * @param selectedItem
     * @param purApForm
     * @param purApLineSession
     * @param documentNumber
     */
    protected void postProcessCreatingDocument(PurchasingAccountsPayableItemAsset selectedItem, List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession, String documentNumber) {
        // save CAMS document number in CAB
        selectedItem.setCapitalAssetManagementDocumentNumber(documentNumber);

        // in-activate item, item account and glEntry(conditionally)
        inActivateItem(selectedItem);

        // update submit amount in the associated general ledger entries.
        updateGlEntrySubmitAmount(selectedItem, purApLineSession.getGlEntryUpdateList());

        // in-activate document if all the associated items are inactive.
        if (ObjectUtils.isNotNull(selectedItem.getPurchasingAccountsPayableDocument())) {
            // update document status code as 'Enroute' when all its items are in CAMs. Link reference from item to document should
            // be set in PurApLineAction.getSelectedLineItem().
            conditionalyUpdateDocumentStatusAsEnroute(selectedItem.getPurchasingAccountsPayableDocument());
        }

        // persistent to the table
        purApLineService.processSaveBusinessObjects(purApDocs, purApLineSession);
        // In-activate general ledger afterwards because we don't maintain the non-persistent relationship from GL to account, so
        // account need to persistent changes first.
        List<GeneralLedgerEntry> glEntryUpdatesList = getGlEntryInActivedList(selectedItem);
        if (glEntryUpdatesList != null && !glEntryUpdatesList.isEmpty()) {
            businessObjectService.save(glEntryUpdatesList);
        }
    }


    /**
     * set doc status as enroute when all its items are in CAMs
     *
     * @param selectedDoc
     */
    protected void conditionalyUpdateDocumentStatusAsEnroute(PurchasingAccountsPayableDocument selectedDoc) {
        for (PurchasingAccountsPayableItemAsset item : selectedDoc.getPurchasingAccountsPayableItemAssets()) {
            if (item.isActive()) {
                return;
            }
        }

        selectedDoc.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
    }


    /**
     * Update transactionLedgerSubmitAmount in the associated generalLedgerEntry for each item account.
     *
     * @param selectedItem
     */
    protected void updateGlEntrySubmitAmount(PurchasingAccountsPayableItemAsset selectedItem, List glEntryList) {
        GeneralLedgerEntry glEntry = null;
        for (PurchasingAccountsPayableLineAssetAccount account : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
            glEntry = account.getGeneralLedgerEntry();

            if (ObjectUtils.isNotNull(glEntry)) {
                // Add account amount to GL entry submit amount.
                if (glEntry.getTransactionLedgerSubmitAmount() != null) {
                    glEntry.setTransactionLedgerSubmitAmount(glEntry.getTransactionLedgerSubmitAmount().add(account.getItemAccountTotalAmount()));
                } else {
                    glEntry.setTransactionLedgerSubmitAmount(new KualiDecimal(account.getItemAccountTotalAmount().toString()));
                }
            }
            // add to the session for persistence
            glEntryList.add(glEntry);
        }
    }


    /**
     * Build asset details/shared details/unique details lists for new asset global document
     *
     * @param selectedItem
     * @param newDocument
     * @param assetGlobal
     */
    protected void setAssetGlobalDetails(PurchasingAccountsPayableItemAsset selectedItem, AssetGlobal assetGlobal, Pretag preTag, PurchaseOrderCapitalAssetSystem capitalAssetSystem) {
        // build assetGlobalDetail list( will be used for creating unique details list at the same time)
        List<AssetGlobalDetail> assetDetailsList = assetGlobal.getAssetGlobalDetails();
        // shared location details list
        List<AssetGlobalDetail> sharedDetails = assetGlobal.getAssetSharedDetails();
        for (int i = 0; i < selectedItem.getAccountsPayableItemQuantity().intValue(); i++) {
            AssetGlobalDetail assetGlobalDetail = new AssetGlobalDetail();
            assetGlobalDetail.setDocumentNumber(assetGlobal.getDocumentNumber());
            assetGlobalDetail.setCapitalAssetNumber(NextAssetNumberFinder.getLongValue());
            assetDetailsList.add(assetGlobalDetail);
            // build assetSharedDetails and assetGlobalUniqueDetails list. There two lists will be used to rebuild
            // assetGlobalDetails list when AssetGlobalMaintainableImpl.prepareForSave() is called during save the document.
            AssetGlobalDetail sharedDetail = new AssetGlobalDetail();
            // added as unique detail
            sharedDetail.getAssetGlobalUniqueDetails().add(assetGlobalDetail);
            sharedDetails.add(sharedDetail);
        }

        // feeding data from pre-tag details into shared location details list and unique detail list
        if (isItemPretagged(preTag)) {
            setAssetDetailFromPreTag(preTag, sharedDetails, assetDetailsList);
        }

        // feeding location data from PurAp into assetGlobalDetail List.
        if (!isItemFullyPretagged(preTag, assetGlobal) && ObjectUtils.isNotNull(capitalAssetSystem)) {
            setAssetGlobalDetailFromPurAp(capitalAssetSystem, sharedDetails);
        }
    }

    /**
     * Check if the all new assets get pre-tagged by pre-tagging.
     *
     * @param preTag
     * @param assetGlobal
     * @return
     */
    protected boolean isItemFullyPretagged(Pretag preTag, AssetGlobal assetGlobal) {
        if (isItemPretagged(preTag)) {
            List<PretagDetail> pretagDetails = preTag.getPretagDetails();
            int pretagSize = 0;
            for (PretagDetail pretagDetail : pretagDetails) {
                if (pretagDetail.isActive()) {
                    pretagSize++;
                }
            }
            return pretagSize >= assetGlobal.getAssetSharedDetails().size();
        }
        return false;
    }


    /**
     * Set asset global detail location information from PurAp input. In this method, no grouping for shared location because
     * AssetGlobalMaintainableImpl.processAfterRetrieve() will group the shared location anyway...
     *
     * @param capitalAssetSystem
     * @param assetDetailsList
     */
    protected void setAssetGlobalDetailFromPurAp(PurchaseOrderCapitalAssetSystem capitalAssetSystem, List<AssetGlobalDetail> assetSharedDetail) {
        List<CapitalAssetLocation> capitalAssetLocations = capitalAssetSystem.getCapitalAssetLocations();

        if (ObjectUtils.isNotNull(capitalAssetLocations) && !capitalAssetLocations.isEmpty()) {
            Iterator<CapitalAssetLocation> locationIterator = capitalAssetLocations.iterator();
            int locationQuantity = 0;
            CapitalAssetLocation assetLocation = null;
            for (AssetGlobalDetail assetDetail : assetSharedDetail) {
                // if it's already pre-tagged, skip it.
                if (StringUtils.isNotEmpty(assetDetail.getCampusCode())) {
                    continue;
                }

                // Each line item can have multiple locations and each location can have a quantity value with it.
                if (locationQuantity <= 0 && locationIterator.hasNext()) {
                    // when we consume the current location quantity, we need to move to the next PurAp location.
                    assetLocation = locationIterator.next();
                    // initialize location quantity by PurAp setting
                    if (assetLocation.getItemQuantity() != null) {
                        locationQuantity = assetLocation.getItemQuantity().intValue();
                    } else {
                        // if Purap not set item quantity, we set it to 1.
                        locationQuantity = 1;
                    }
                } else if (locationQuantity <= 0 && !locationIterator.hasNext()) {
                    // Consume the current location quantity and no more PurAp locations can be used, stop here.
                    break;
                }
                // set PurAp asset location into asset global document
                setNewAssetByPurApLocation(assetLocation, assetDetail);

                locationQuantity--;
            }
        }

    }

    /**
     * Set asset global detail by PurAp asset location.
     *
     * @param assetLocation
     * @param assetDetail
     */
    protected void setNewAssetByPurApLocation(CapitalAssetLocation assetLocation, AssetGlobalDetail assetDetail) {
        String campusCode = assetLocation.getCampusCode();
        // Set campus code only when it is a valid value. Otherwise, when save document, invalid data will violate data integrity
        // and block save.
        if (!StringUtils.isBlank(campusCode) && checkCampusCodeValid(campusCode)) {
            assetDetail.setCampusCode(campusCode);

            // for on-campus
            if (!assetLocation.isOffCampusIndicator()) {
                String buildingCode = assetLocation.getBuildingCode();
                // Set building code only when it is a valid value. Otherwise, when save document, invalid data will violate data
                // integrity and block save.
                if (!StringUtils.isBlank(buildingCode) && checkBuildingCodeValid(campusCode, buildingCode)) {
                    assetDetail.setBuildingCode(buildingCode);

                    String buildingRoomNumber = assetLocation.getBuildingRoomNumber();
                    // Set building room number only when it is a valid value. Otherwise, when save document, invalid data will
                    // violate data integrity and block save.
                    if (!StringUtils.isBlank(buildingRoomNumber) && checkBuildingRoomNumberValid(campusCode, buildingCode, buildingRoomNumber)) {
                        assetDetail.setBuildingRoomNumber(buildingRoomNumber);
                    }
                }
            } else {
                // off-campus
                assetDetail.setOffCampusCityName(assetLocation.getCapitalAssetCityName());
                assetDetail.setOffCampusAddress(assetLocation.getCapitalAssetLine1Address());
                assetDetail.setOffCampusCountryCode(assetLocation.getCapitalAssetCountryCode());
                assetDetail.setOffCampusStateCode(assetLocation.getCapitalAssetStateCode());
                assetDetail.setOffCampusZipCode(assetLocation.getCapitalAssetPostalCode());
            }
        }
    }


    /**
     * Check the given buildingCode and campusCode valid.
     *
     * @param campusCode
     * @param buildingCode
     * @param buildingRoomNumber
     * @return
     */
    protected boolean checkBuildingRoomNumberValid(String campusCode, String buildingCode, String buildingRoomNumber) {
        Map<String, Object> pKeys = new HashMap<String, Object>();
        pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAMPUS_CODE, campusCode);
        pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.BUILDING_CODE, buildingCode);
        pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.BUILDING_ROOM_NUMBER, buildingRoomNumber);
        Room room = this.getBusinessObjectService().findByPrimaryKey(Room.class, pKeys);
        return ObjectUtils.isNotNull(room) && room.isActive();
    }


    /**
     * Check the given buildingCode and campusCode valid.
     *
     * @param buildingCode
     * @return
     */
    protected boolean checkBuildingCodeValid(String campusCode, String buildingCode) {
        Map<String, Object> pKeys = new HashMap<String, Object>();
        pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAMPUS_CODE, campusCode);
        pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.BUILDING_CODE, buildingCode);
        Building building = this.getBusinessObjectService().findByPrimaryKey(Building.class, pKeys);
        return ObjectUtils.isNotNull(building) && building.isActive();
    }


    /**
     * check the given campus code existing and active status.
     *
     * @param campusCode
     * @return
     */
    protected boolean checkCampusCodeValid(String campusCode) {
        Map<String, Object> criteria = new HashMap<String, Object>();
        criteria.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAMPUS_CODE, campusCode);
        Campus campus = SpringContext.getBean(CampusService.class).getCampus(campusCode/*RICE_20_REFACTORME  criteria */);
        return ObjectUtils.isNotNull(campus) && campus.isActive();
    }

    /**
     * Feeding data into assetGlobalDetail list from preTagDetail
     *
     * @param preTag
     * @param assetDetailsList
     */
    protected void setAssetDetailFromPreTag(Pretag preTag, List<AssetGlobalDetail> assetSharedDetails, List<AssetGlobalDetail> assetUniqueDetails) {
        Iterator<AssetGlobalDetail> sharedDetailsIterator = assetSharedDetails.iterator();
        Iterator<AssetGlobalDetail> uniqueDetailsIterator = assetUniqueDetails.iterator();
        for (PretagDetail preTagDetail : preTag.getPretagDetails()) {
            if (!preTagDetail.isActive()) {
                continue;
            }
            if (sharedDetailsIterator.hasNext()) {
                // set shared location details
                AssetGlobalDetail sharedDetail = sharedDetailsIterator.next();
                sharedDetail.setBuildingCode(preTagDetail.getBuildingCode());
                sharedDetail.setBuildingRoomNumber(preTagDetail.getBuildingRoomNumber());
                sharedDetail.setBuildingSubRoomNumber(preTagDetail.getBuildingSubRoomNumber());
                sharedDetail.setCampusCode(preTagDetail.getCampusCode());
                // In-activate pre-tagging detail, and will be persistent to the DB.
                preTagDetail.setActive(false);
            }
            if (uniqueDetailsIterator.hasNext()) {
                // set asset unique detail
                AssetGlobalDetail uniqueDetail = uniqueDetailsIterator.next();
                uniqueDetail.setGovernmentTagNumber(preTagDetail.getGovernmentTagNumber());
                uniqueDetail.setNationalStockNumber(preTagDetail.getNationalStockNumber());
                uniqueDetail.setCampusTagNumber(preTagDetail.getCampusTagNumber());
                uniqueDetail.setOrganizationInventoryName(preTag.getOrganizationInventoryName());
                uniqueDetail.setSerialNumber(preTagDetail.getSerialNumber());
                uniqueDetail.setRepresentativeUniversalIdentifier(preTag.getRepresentativeUniversalIdentifier());
                // In-activate pre-tagging detail and will be persistent to the DB.
                preTagDetail.setActive(false);
            }
        }
        // In-activate preTag if possible.
        inActivatePreTag(preTag);
    }

    /**
     * In-activate preTag if all its preTagDetail entry are inactive.
     *
     * @param preTag
     */
    protected void inActivatePreTag(Pretag preTag) {
        // get the number of inactive pre-tag detail.
        int inActiveCounter = 0;
        for (PretagDetail preTagDetail : preTag.getPretagDetails()) {
            if (!preTagDetail.isActive()) {
                inActiveCounter++;
            }
        }
        // if the number of inactive preTagDetail is equal or greater than (when quantityInvoiced is decimal) quantityInvoiced,
        // in-activate the preTag active field.
        if (preTag.getQuantityInvoiced().isLessEqual(new KualiDecimal(inActiveCounter))) {
            preTag.setActive(false);
        }
    }

    /**
     * Build asset payment details list for new asset global document.
     *
     * @param selectedItem
     * @param assetGlobal
     * @param requisitionIdentifier
     */
    protected void createAssetPaymentDetails(List<AssetPaymentDetail> assetPaymentList, PurchasingAccountsPayableItemAsset selectedItem, String documentNumber, Integer requisitionIdentifier) {
        int seq = 1;

        for (PurchasingAccountsPayableLineAssetAccount account : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
            GeneralLedgerEntry glEntry = account.getGeneralLedgerEntry();

            if (ObjectUtils.isNotNull(glEntry)) {
                AssetPaymentDetail assetPaymentDetail = new AssetPaymentDetail();
                // initialize payment detail fields
                assetPaymentDetail.setDocumentNumber(documentNumber);
                assetPaymentDetail.setSequenceNumber(Integer.valueOf(seq++));
                assetPaymentDetail.setChartOfAccountsCode(glEntry.getChartOfAccountsCode());
                assetPaymentDetail.setAccountNumber(replaceFiller(glEntry.getAccountNumber()));
                assetPaymentDetail.setSubAccountNumber(replaceFiller(glEntry.getSubAccountNumber()));
                assetPaymentDetail.setFinancialObjectCode(replaceFiller(glEntry.getFinancialObjectCode()));
                assetPaymentDetail.setFinancialSubObjectCode(replaceFiller(glEntry.getFinancialSubObjectCode()));
                assetPaymentDetail.setProjectCode(replaceFiller(glEntry.getProjectCode()));
                assetPaymentDetail.setOrganizationReferenceId(glEntry.getOrganizationReferenceId());
                assetPaymentDetail.setPostingYear(glEntry.getUniversityFiscalYear());
                assetPaymentDetail.setPostingPeriodCode(glEntry.getUniversityFiscalPeriodCode());
                assetPaymentDetail.setExpenditureFinancialSystemOriginationCode(replaceFiller(glEntry.getFinancialSystemOriginationCode()));
                assetPaymentDetail.setExpenditureFinancialDocumentNumber(glEntry.getDocumentNumber());
                assetPaymentDetail.setExpenditureFinancialDocumentTypeCode(replaceFiller(glEntry.getFinancialDocumentTypeCode()));
                assetPaymentDetail.setExpenditureFinancialDocumentPostedDate(glEntry.getTransactionDate());
                assetPaymentDetail.setAmount(account.getItemAccountTotalAmount());
                assetPaymentDetail.setPurchaseOrderNumber(replaceFiller(glEntry.getReferenceFinancialDocumentNumber()));
                assetPaymentDetail.setRequisitionNumber(requisitionIdentifier.toString());
                assetPaymentDetail.setTransferPaymentIndicator(false);

                // add to payment list
                assetPaymentList.add(assetPaymentDetail);
            }
        }
    }


    /**
     * In-activate item, item Account and generalLedgerEntry active indicator.
     *
     * @param selectedItem
     * @param glEntryList
     */
    protected void inActivateItem(PurchasingAccountsPayableItemAsset selectedItem) {
        // in-active each account.
        for (PurchasingAccountsPayableLineAssetAccount selectedAccount : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
            selectedAccount.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
        }
        // in-activate selected Item
        selectedItem.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
    }

    /**
     * Update GL Entry status as "enroute" if all its amount are consumed by submit CAMs document.Return the general ledger entry
     * changes as a list.
     *
     * @param glEntryList
     * @param selectedAccount
     * @param glEntry
     */
    protected List<GeneralLedgerEntry> getGlEntryInActivedList(PurchasingAccountsPayableItemAsset selectedItem) {
        List<GeneralLedgerEntry> glEntryUpdateList = new ArrayList<GeneralLedgerEntry>();
        for (PurchasingAccountsPayableLineAssetAccount selectedAccount : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
            GeneralLedgerEntry glEntry = selectedAccount.getGeneralLedgerEntry();
            KualiDecimal relatedAccountAmount = KualiDecimal.ZERO;

            // get persistent account list which should be save before hand
            glEntry.refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.PURAP_LINE_ASSET_ACCOUNTS);
            // check if all accounts are inactive status
            for (PurchasingAccountsPayableLineAssetAccount account : glEntry.getPurApLineAssetAccounts()) {
                if (!account.isActive()) {
                    relatedAccountAmount = relatedAccountAmount.add(account.getItemAccountTotalAmount());
                }
            }

            // if one account shows active, won't in-activate this general ledger entry.
            if (relatedAccountAmount.compareTo(glEntry.getAmount()) == 0) {
                glEntry.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
                glEntryUpdateList.add(glEntry);
            }
        }
        return glEntryUpdateList;
    }


    protected String replaceFiller(String val) {
        return val == null ? "" : val.trim().replaceAll("-", "");
    }

    /**
     * Create AssetGlobal BO and feed data from pre-asset tagging table.
     *
     * @param selectedItem
     * @param newDocument
     * @param preTag
     * @return
     */
    protected AssetGlobal createAssetGlobal(PurchasingAccountsPayableItemAsset selectedItem, String documentNumber, Pretag preTag, Integer requisitionIdentifier) {
        // instantiate AssetGlobal BO
        AssetGlobal assetGlobal = new AssetGlobal();
        assetGlobal.setDocumentNumber(documentNumber);
        assetGlobal.setCapitalAssetDescription(selectedItem.getAccountsPayableLineItemDescription());
        assetGlobal.setConditionCode(CamsConstants.Asset.CONDITION_CODE_E);
        assetGlobal.setAcquisitionTypeCode(getAssetGlobalService().getNewAcquisitionTypeCode());
        assetGlobal.setInventoryStatusCode(CamsConstants.InventoryStatusCode.CAPITAL_ASSET_ACTIVE_IDENTIFIABLE);
        // set vendor name from Purchase Order Document
        PurchaseOrderDocument purApdocument = this.getPurApInfoService().getCurrentDocumentForPurchaseOrderIdentifier(selectedItem.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier());
        if (purApdocument != null) {
            assetGlobal.setVendorName(purApdocument.getVendorName());
        }
        // set origin code in the Asset Payment Document
        assetGlobal.setCapitalAssetBuilderOriginIndicator(true);

        PurchaseOrderCapitalAssetSystem capitalAssetSystem = null;
        // check and set if purAp has new asset information
        if (selectedItem.getCapitalAssetSystemIdentifier() != null) {
            capitalAssetSystem = findCapitalAssetSystem(selectedItem.getCapitalAssetSystemIdentifier());
            if (ObjectUtils.isNotNull(capitalAssetSystem)) {
                setAssetGlobalFromPurAp(assetGlobal, capitalAssetSystem);
            }
        }
        // feeding data from pre-asset tagging table into assetGlboal. Here, if there are data overlap in pretag and PurAp, we
        // respect data in Pretagging.
        if (isItemPretagged(preTag)) {
            setAssetGlobalFromPreTag(preTag, assetGlobal);
        }

        // set asset global detail list
        setAssetGlobalDetails(selectedItem, assetGlobal, preTag, capitalAssetSystem);

        // Set Organization Inventory Name for each asset unique detail from PO
        setOrgInventoryNameForAssetDetail(assetGlobal.getAssetGlobalDetails(), purApdocument);

        // build payments list for asset global
        createAssetPaymentDetails(assetGlobal.getAssetPaymentDetails(), selectedItem, documentNumber, requisitionIdentifier);

        // set total cost
        setAssetGlobalTotalCost(assetGlobal);
        // Set Asset Global organization owner account, which is the account that contributed the most dollars.
        setAssetGlobalOrgOwnerAccount(assetGlobal);

        return assetGlobal;
    }

    /**
     * Set organization inventory name for each asset detail by PO Contact name or if empty, by Requestor Name.
     *
     * @param assetGlobalDetails
     * @param purApdocument
     */
    protected void setOrgInventoryNameForAssetDetail(List<AssetGlobalDetail> assetGlobalDetails, PurchaseOrderDocument purApdocument) {
        if (ObjectUtils.isNotNull(purApdocument)) {
            String orgInventoryName = purApdocument.getInstitutionContactName();

            if (StringUtils.isBlank(orgInventoryName)) {
                orgInventoryName = purApdocument.getRequestorPersonName();
            }

            for (AssetGlobalDetail assetGlobalDetail : assetGlobalDetails) {
                assetGlobalDetail.setOrganizationInventoryName(orgInventoryName);
            }
        }
    }


    /**
     * check if item is pre-tagged already.
     *
     * @param preTag
     * @return
     */
    protected boolean isItemPretagged(Pretag preTag) {
        return ObjectUtils.isNotNull(preTag) && preTag.isActive() && ObjectUtils.isNotNull(preTag.getPretagDetails()) && !preTag.getPretagDetails().isEmpty();
    }


    /**
     * Set asset information from PurAp PurchaseOrderCapitalAssetSystem.
     *
     * @param assetGlobal
     * @param capitalAssetSystem
     */
    protected void setAssetGlobalFromPurAp(AssetGlobal assetGlobal, PurchaseOrderCapitalAssetSystem capitalAssetSystem) {
        assetGlobal.setManufacturerName(capitalAssetSystem.getCapitalAssetManufacturerName());
        assetGlobal.setManufacturerModelNumber(capitalAssetSystem.getCapitalAssetModelDescription());
        String capitalAssetTypeCode = capitalAssetSystem.getCapitalAssetTypeCode();
        if (!StringUtils.isBlank(capitalAssetTypeCode) && checkCapitalAssetTypeCodeExist(capitalAssetTypeCode)) {
            assetGlobal.setCapitalAssetTypeCode(capitalAssetSystem.getCapitalAssetTypeCode());
        }
    }


    /**
     * check the given capital asset type code exists in CAM
     *
     * @param capitalAssetTypeCode
     * @return
     */
    protected boolean checkCapitalAssetTypeCodeExist(String capitalAssetTypeCode) {
        Map<String, Object> pKeys = new HashMap<String, Object>();
        pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAPITAL_ASSET_TYPE_CODE, capitalAssetTypeCode);
        AssetType assetType = this.getBusinessObjectService().findByPrimaryKey(AssetType.class, pKeys);
        return ObjectUtils.isNotNull(assetType);
    }


    /**
     * Get PurAp PurchaseOrderCapitalAssetSystem Object if exists.
     *
     * @param capitalAssetSystemIdentifier
     * @return
     */
    protected PurchaseOrderCapitalAssetSystem findCapitalAssetSystem(Integer capitalAssetSystemIdentifier) {
        Map pKeys = new HashMap<String, Object>();

        pKeys.put(PurapPropertyConstants.CAPITAL_ASSET_SYSTEM_IDENTIFIER, capitalAssetSystemIdentifier);
        return businessObjectService.findByPrimaryKey(PurchaseOrderCapitalAssetSystem.class, pKeys);
    }


    /**
     * Set Asset Global org owner account and chart code. It's assigned by selecting the account that contributed the most dollars
     * on the payment request.
     *
     * @param assetGlobal
     */
    protected void setAssetGlobalOrgOwnerAccount(AssetGlobal assetGlobal) {
        AssetPaymentDetail maxCostPayment = null;
        // get the maximum payment cost
        for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
            if (maxCostPayment == null || assetPaymentDetail.getAmount().isGreaterThan(maxCostPayment.getAmount())) {
                maxCostPayment = assetPaymentDetail;
            }
        }

        if (maxCostPayment != null) {
            assetGlobal.setOrganizationOwnerAccountNumber(maxCostPayment.getAccountNumber());
            assetGlobal.setOrganizationOwnerChartOfAccountsCode(maxCostPayment.getChartOfAccountsCode());
        }
    }


    /**
     * Set Asset Global total cost amount.
     *
     * @param assetGlobal
     */
    protected void setAssetGlobalTotalCost(AssetGlobal assetGlobal) {
        KualiDecimal totalCost = KualiDecimal.ZERO;
        for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
            totalCost = totalCost.add(assetPaymentDetail.getAmount());
        }

        assetGlobal.setTotalCostAmount(totalCost);
    }


    /**
     * Feeding data from preTag and set into asset global for shared information. PreTag data may override PurAp asset data since
     * the strategy choose to respect Pretagging
     *
     * @param preTag
     * @param assetGlobal
     */
    protected void setAssetGlobalFromPreTag(Pretag preTag, AssetGlobal assetGlobal) {
        if (StringUtils.isNotBlank(preTag.getManufacturerName())) {
            assetGlobal.setManufacturerName(preTag.getManufacturerName());
        }
        if (StringUtils.isNotBlank(preTag.getManufacturerModelNumber())) {
            assetGlobal.setManufacturerModelNumber(preTag.getManufacturerModelNumber());
        }

        if (StringUtils.isNotBlank(preTag.getCapitalAssetTypeCode())) {
            assetGlobal.setCapitalAssetTypeCode(preTag.getCapitalAssetTypeCode());
        }
        assetGlobal.setOrganizationText(preTag.getOrganizationText());
        assetGlobal.setRepresentativeUniversalIdentifier(preTag.getRepresentativeUniversalIdentifier());
        if (StringUtils.isNotBlank(preTag.getVendorName())) {
            assetGlobal.setVendorName(preTag.getVendorName());
        }
        // acquisition type code is set to "P"(Pre-asset tagging)
        assetGlobal.setAcquisitionTypeCode(CamsConstants.AssetGlobal.PRE_TAGGING_ACQUISITION_TYPE_CODE);
    }

    /**
     * Gets the businessObjectService attribute.
     *
     * @return Returns the businessObjectService.
     */
    public BusinessObjectService getBusinessObjectService() {
        return businessObjectService;
    }

    /**
     * Sets the businessObjectService attribute value.
     *
     * @param businessObjectService The businessObjectService to set.
     */
    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    /**
     * Gets the documentService attribute.
     *
     * @return Returns the documentService.
     */
    public DocumentService getDocumentService() {
        return documentService;
    }


    /**
     * Sets the documentService attribute value.
     *
     * @param documentService The documentService to set.
     */
    public void setDocumentService(DocumentService documentService) {
        this.documentService = documentService;
    }

    /**
     * Gets the purApLineService attribute.
     *
     * @return Returns the purApLineService.
     */
    public PurApLineService getPurApLineService() {
        return purApLineService;
    }

    /**
     * Sets the purApLineService attribute value.
     *
     * @param purApLineService The purApLineService to set.
     */
    public void setPurApLineService(PurApLineService purApLineService) {
        this.purApLineService = purApLineService;
    }


    /**
     * Gets the purApInfoService attribute.
     *
     * @return Returns the purApInfoService.
     */
    public PurApInfoService getPurApInfoService() {
        return purApInfoService;
    }


    /**
     * Sets the purApInfoService attribute value.
     *
     * @param purApInfoService The purApInfoService to set.
     */
    public void setPurApInfoService(PurApInfoService purApInfoService) {
        this.purApInfoService = purApInfoService;
    }


    private AssetGlobalService getAssetGlobalService() {
        return assetGlobalService;
    }


    public void setAssetGlobalService(AssetGlobalService assetGlobalService) {
        this.assetGlobalService = assetGlobalService;
    }
}
