org.kuali.kra.proposaldevelopment.hierarchy.service.impl.ProposalHierarchyServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.kra.proposaldevelopment.hierarchy.service.impl.ProposalHierarchyServiceImpl.java

Source

/*
 * Copyright 2005-2010 The Kuali Foundation
 * 
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.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 org.kuali.kra.proposaldevelopment.hierarchy.service.impl;

import static org.apache.commons.lang.StringUtils.replace;
import static org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyKeyConstants.ERROR_BUDGET_PERIOD_DURATION_INCONSISTENT;
import static org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyKeyConstants.ERROR_BUDGET_START_DATE_INCONSISTENT;
import static org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyKeyConstants.PARAMETER_NAME_DIRECT_COST_ELEMENT;
import static org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyKeyConstants.PARAMETER_NAME_INDIRECT_COST_ELEMENT;
import static org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyKeyConstants.PARAMETER_NAME_INSTITUTE_NARRATIVE_TYPE_GROUP;
import static org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyKeyConstants.QUESTION_EXTEND_PROJECT_DATE_CONFIRM;

import java.io.IOException;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.upload.FormFile;
import org.kuali.kra.budget.BudgetDecimal;
import org.kuali.kra.budget.calculator.BudgetCalculationService;
import org.kuali.kra.budget.core.Budget;
import org.kuali.kra.budget.core.BudgetAssociate;
import org.kuali.kra.budget.core.BudgetService;
import org.kuali.kra.budget.core.CostElement;
import org.kuali.kra.budget.distributionincome.BudgetCostShare;
import org.kuali.kra.budget.distributionincome.BudgetProjectIncome;
import org.kuali.kra.budget.distributionincome.BudgetUnrecoveredFandA;
import org.kuali.kra.budget.document.BudgetDocument;
import org.kuali.kra.budget.nonpersonnel.BudgetLineItem;
import org.kuali.kra.budget.nonpersonnel.BudgetLineItemCalculatedAmount;
import org.kuali.kra.budget.parameters.BudgetPeriod;
import org.kuali.kra.budget.personnel.BudgetPerson;
import org.kuali.kra.budget.personnel.BudgetPersonnelBudgetService;
import org.kuali.kra.budget.personnel.BudgetPersonnelCalculatedAmount;
import org.kuali.kra.budget.personnel.BudgetPersonnelDetails;
import org.kuali.kra.budget.personnel.HierarchyPersonnelSummary;
import org.kuali.kra.budget.versions.BudgetDocumentVersion;
import org.kuali.kra.infrastructure.Constants;
import org.kuali.kra.infrastructure.KraServiceLocator;
import org.kuali.kra.infrastructure.PermissionConstants;
import org.kuali.kra.infrastructure.RoleConstants;
import org.kuali.kra.kew.KraDocumentRejectionService;
import org.kuali.kra.proposaldevelopment.bo.CongressionalDistrict;
import org.kuali.kra.proposaldevelopment.bo.DevelopmentProposal;
import org.kuali.kra.proposaldevelopment.bo.Narrative;
import org.kuali.kra.proposaldevelopment.bo.NarrativeAttachment;
import org.kuali.kra.proposaldevelopment.bo.NarrativeStatus;
import org.kuali.kra.proposaldevelopment.bo.PropScienceKeyword;
import org.kuali.kra.proposaldevelopment.bo.ProposalBudgetStatus;
import org.kuali.kra.proposaldevelopment.bo.ProposalPerson;
import org.kuali.kra.proposaldevelopment.bo.ProposalPersonBiography;
import org.kuali.kra.proposaldevelopment.bo.ProposalPersonBiographyAttachment;
import org.kuali.kra.proposaldevelopment.bo.ProposalPersonExtendedAttributes;
import org.kuali.kra.proposaldevelopment.bo.ProposalPersonUnit;
import org.kuali.kra.proposaldevelopment.bo.ProposalSite;
import org.kuali.kra.proposaldevelopment.budget.bo.BudgetSubAwardAttachment;
import org.kuali.kra.proposaldevelopment.budget.bo.BudgetSubAwardFiles;
import org.kuali.kra.proposaldevelopment.budget.bo.BudgetSubAwards;
import org.kuali.kra.proposaldevelopment.document.ProposalDevelopmentDocument;
import org.kuali.kra.proposaldevelopment.hierarchy.HierarchyBudgetTypeConstants;
import org.kuali.kra.proposaldevelopment.hierarchy.HierarchyStatusConstants;
import org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyErrorDto;
import org.kuali.kra.proposaldevelopment.hierarchy.ProposalHierarchyException;
import org.kuali.kra.proposaldevelopment.hierarchy.bo.HierarchyProposalSummary;
import org.kuali.kra.proposaldevelopment.hierarchy.dao.ProposalHierarchyDao;
import org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService;
import org.kuali.kra.proposaldevelopment.service.NarrativeService;
import org.kuali.kra.proposaldevelopment.service.ProposalPersonBiographyService;
import org.kuali.kra.proposaldevelopment.specialreview.ProposalSpecialReview;
import org.kuali.kra.service.DeepCopyPostProcessor;
import org.kuali.kra.service.KraAuthorizationService;
import org.kuali.rice.kew.dto.DocumentRouteStatusChangeDTO;
import org.kuali.rice.kew.exception.WorkflowException;
import org.kuali.rice.kew.service.WorkflowDocument;
import org.kuali.rice.kew.util.KEWConstants;
import org.kuali.rice.kim.service.IdentityManagementService;
import org.kuali.rice.kns.bo.DocumentHeader;
import org.kuali.rice.kns.document.Document;
import org.kuali.rice.kns.service.BusinessObjectService;
import org.kuali.rice.kns.service.DocumentService;
import org.kuali.rice.kns.service.KualiConfigurationService;
import org.kuali.rice.kns.service.ParameterService;
import org.kuali.rice.kns.util.GlobalVariables;
import org.kuali.rice.kns.util.KNSConstants;
import org.kuali.rice.kns.util.ObjectUtils;
import org.kuali.rice.kns.web.struts.form.KualiForm;
import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
import org.springframework.transaction.annotation.Transactional;

/**
 * This class...
 */
@Transactional
public class ProposalHierarchyServiceImpl implements ProposalHierarchyService {

    private static final Log LOG = LogFactory.getLog(ProposalHierarchyServiceImpl.class);

    private BusinessObjectService businessObjectService;
    private DocumentService documentService;
    private KraAuthorizationService kraAuthorizationService;
    private ProposalHierarchyDao proposalHierarchyDao;
    private NarrativeService narrativeService;
    private BudgetService budgetService;
    private ProposalPersonBiographyService propPersonBioService;
    private ParameterService parameterService;
    private IdentityManagementService identityManagementService;
    private KualiConfigurationService configurationService;
    private KraDocumentRejectionService kraDocumentRejectionService;
    private List<ProposalPersonExtendedAttributes> proposalPersonExtendedAttributesToDelete;

    //Setters for dependency injection
    public void setIdentityManagementService(IdentityManagementService identityManagerService) {
        this.identityManagementService = identityManagerService;
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    public void setDocumentService(DocumentService documentService) {
        this.documentService = documentService;
    }

    public void setKraAuthorizationService(KraAuthorizationService kraAuthorizationService) {
        this.kraAuthorizationService = kraAuthorizationService;
    }

    public void setProposalHierarchyDao(ProposalHierarchyDao proposalHierarchyDao) {
        this.proposalHierarchyDao = proposalHierarchyDao;
    }

    public void setNarrativeService(NarrativeService narrativeService) {
        this.narrativeService = narrativeService;
    }

    public void setBudgetService(BudgetService budgetService) {
        this.budgetService = budgetService;
    }

    public void setPropPersonBioService(ProposalPersonBiographyService propPersonBioService) {
        this.propPersonBioService = propPersonBioService;
    }

    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    public void setConfigurationService(KualiConfigurationService configurationService) {
        this.configurationService = configurationService;
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#createHierarchy(java.lang.String)
     */
    public String createHierarchy(DevelopmentProposal initialChild) throws ProposalHierarchyException {
        LOG.info(String.format("***Create Hierarchy using Proposal #%s", initialChild.getProposalNumber()));
        if (initialChild.isInHierarchy()) {
            throw new ProposalHierarchyException("Cannot create hierarchy: proposal "
                    + initialChild.getProposalNumber() + " is already a member of a hierarchy.");
        }

        // create a new proposal document
        ProposalDevelopmentDocument newDoc;

        // manually assembling a new PDDoc here because the DocumentService will deny initiator permission without context
        // since a person with MAINTAIN_PROPOSAL_HIERARCHY permission is allowed to initiate IF they are creating a parent
        // we circumvent the initiator step altogether. 
        try {
            KualiWorkflowDocument workflowDocument = KraServiceLocator.getService(WorkflowDocumentService.class)
                    .createWorkflowDocument(PROPOSAL_DEVELOPMENT_DOCUMENT_TYPE,
                            GlobalVariables.getUserSession().getPerson());
            GlobalVariables.getUserSession().setWorkflowDocument(workflowDocument);
            DocumentHeader documentHeader = new DocumentHeader();
            documentHeader.setWorkflowDocument(workflowDocument);
            documentHeader.setDocumentNumber(workflowDocument.getRouteHeaderId().toString());
            newDoc = new ProposalDevelopmentDocument();
            newDoc.setDocumentHeader(documentHeader);
            newDoc.setDocumentNumber(documentHeader.getDocumentNumber());
        } catch (WorkflowException x) {
            throw new ProposalHierarchyException("Error creating new document: " + x);
        }

        // copy the initial information to the new parent proposal
        DevelopmentProposal hierarchy = newDoc.getDevelopmentProposal();
        copyInitialData(hierarchy, initialChild);
        hierarchy.setHierarchyStatus(HierarchyStatusConstants.Parent.code());
        String docDescription = initialChild.getProposalDocument().getDocumentHeader().getDocumentDescription();
        newDoc.getDocumentHeader().setDocumentDescription(docDescription);

        // persist the document and add a budget
        try {
            documentService.saveDocument(newDoc);
            budgetService.addBudgetVersion(newDoc, "Hierarchy Budget");
        } catch (WorkflowException x) {
            throw new ProposalHierarchyException("Error saving new document: " + x);
        }
        LOG.info(String.format("***New Hierarchy Parent (#%s) budget created", hierarchy.getProposalNumber()));

        // add aggregator to the document
        String userId = GlobalVariables.getUserSession().getPrincipalId();
        kraAuthorizationService.addRole(userId, RoleConstants.AGGREGATOR, newDoc);

        initializeBudget(hierarchy, initialChild);

        prepareHierarchySync(hierarchy);

        // link the child to the parent
        linkChild(hierarchy, initialChild, HierarchyBudgetTypeConstants.SubBudget.code());
        setInitialPi(hierarchy, initialChild);
        copyInitialAttachments(initialChild, hierarchy);
        aggregateHierarchy(hierarchy);
        LOG.info(String.format("***Initial Child (#%s) linked to Parent (#%s)", initialChild.getProposalNumber(),
                hierarchy.getProposalNumber()));

        finalizeHierarchySync(hierarchy);

        // return the parent id
        LOG.info(String.format("***Hierarchy creation (#%s) complete", hierarchy.getProposalNumber()));
        return hierarchy.getProposalNumber();
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#linkToHierarchy(org.kuali.kra.proposaldevelopment.bo.DevelopmentProposal, org.kuali.kra.proposaldevelopment.bo.DevelopmentProposal, java.lang.String)
     */
    public void linkToHierarchy(DevelopmentProposal hierarchyProposal, DevelopmentProposal newChildProposal,
            String hierarchyBudgetTypeCode) throws ProposalHierarchyException {
        LOG.info(String.format("***Linking Child (#%s) linked to Parent (#%s)",
                newChildProposal.getProposalNumber(), hierarchyProposal.getProposalNumber()));
        if (!hierarchyProposal.isParent()) {
            throw new ProposalHierarchyException(
                    "Proposal " + hierarchyProposal.getProposalNumber() + " is not a hierarchy parent");
        }
        if (newChildProposal.isInHierarchy()) {
            throw new ProposalHierarchyException(
                    "Proposal " + newChildProposal.getProposalNumber() + " is already a member of a hierarchy");
        }
        prepareHierarchySync(hierarchyProposal);
        linkChild(hierarchyProposal, newChildProposal, hierarchyBudgetTypeCode);
        finalizeHierarchySync(hierarchyProposal);
        LOG.info(String.format("***Linking Child (#%s) linked to Parent (#%s) complete",
                newChildProposal.getProposalNumber(), hierarchyProposal.getProposalNumber()));
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#removeFromHierarchy(java.lang.String)
     */
    public void removeFromHierarchy(DevelopmentProposal childProposal) throws ProposalHierarchyException {
        String hierarchyProposalNumber = childProposal.getHierarchyParentProposalNumber();
        DevelopmentProposal hierarchyProposal = getHierarchy(hierarchyProposalNumber);
        BudgetDocument<DevelopmentProposal> hierarchyBudgetDoc = getHierarchyBudget(hierarchyProposal);
        Budget hierarchyBudget = hierarchyBudgetDoc.getBudget();
        LOG.info(String.format("***Removing Child (#%s) from Parent (#%s)", childProposal.getProposalNumber(),
                hierarchyProposal.getProposalNumber()));

        List<String> childProposalNumbers = proposalHierarchyDao
                .getHierarchyChildProposalNumbers(hierarchyProposalNumber);
        boolean isLast = childProposalNumbers.size() == 1;

        childProposal.setHierarchyStatus(HierarchyStatusConstants.None.code());
        childProposal.setHierarchyParentProposalNumber(null);
        if (StringUtils.equalsIgnoreCase(hierarchyProposal.getHierarchyOriginatingChildProposalNumber(),
                childProposal.getProposalNumber())) {
            hierarchyProposal.getPrincipalInvestigator().setHierarchyProposalNumber(null);
        }
        removeChildElements(hierarchyProposal, hierarchyBudget, childProposal.getProposalNumber());

        try {
            documentService.saveDocument(hierarchyBudgetDoc);
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }

        if (isLast) {
            try {
                LOG.info(String.format("***Child (#%s) was last child, cancelling Parent (#%s)",
                        childProposal.getProposalNumber(), hierarchyProposal.getProposalNumber()));
                businessObjectService.save(childProposal);
                Document doc = documentService
                        .getByDocumentHeaderId(hierarchyProposal.getProposalDocument().getDocumentNumber());
                documentService.cancelDocument(doc, "Removed last child from Proposal Hierarchy");
            } catch (WorkflowException e) {
                throw new ProposalHierarchyException("Error cancelling empty parent proposal");
            }
        } else {
            //need to find out what the lowest number is
            //so we can make it the new primary child for budget syncs.
            String lowestProposalNumber = "";
            for (String proposalNumber : childProposalNumbers) {
                if (!StringUtils.equals(proposalNumber, childProposal.getProposalNumber())) {
                    lowestProposalNumber = proposalNumber;
                    break;
                }
            }
            hierarchyProposal.setHierarchyOriginatingChildProposalNumber(lowestProposalNumber);
            businessObjectService.save(childProposal);
            synchronizeAllChildren(hierarchyProposal);
        }
        LOG.info(String.format("***Removing Child (#%s) from Parent (#%s) complete",
                childProposal.getProposalNumber(), hierarchyProposal.getProposalNumber()));
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchySyncService#synchronizeAllChildren(java.lang.String)
     */
    public void synchronizeAllChildren(ProposalDevelopmentDocument pdDoc) throws ProposalHierarchyException {
        prepareHierarchySync(pdDoc);
        synchronizeAll(pdDoc.getDevelopmentProposal());
        finalizeHierarchySync(pdDoc);
    }

    protected void synchronizeAllChildren(DevelopmentProposal hierarchyProposal) throws ProposalHierarchyException {
        prepareHierarchySync(hierarchyProposal);
        synchronizeAll(hierarchyProposal);
        finalizeHierarchySync(hierarchyProposal);
    }

    protected void synchronizeAll(DevelopmentProposal hierarchyProposal) throws ProposalHierarchyException {
        boolean changed = false;
        LOG.info(String.format("***Synchronizing all Children of Parent (#%s)",
                hierarchyProposal.getProposalNumber()));
        if (!hierarchyProposal.isParent()) {
            throw new ProposalHierarchyException(
                    "Proposal " + hierarchyProposal.getProposalNumber() + " is not a hierarchy parent");
        }
        changed = synchronizeAllChildProposals(hierarchyProposal);
        if (changed) {
            aggregateHierarchy(hierarchyProposal);
        }
        LOG.info(String.format("***Synchronizing all Children of Parent (#%s) complete",
                hierarchyProposal.getProposalNumber()));
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchySyncService#synchronizeChild(java.lang.String)
     */
    public void synchronizeChild(DevelopmentProposal childProposal) throws ProposalHierarchyException {
        DevelopmentProposal hierarchy = getHierarchy(childProposal.getHierarchyParentProposalNumber());
        LOG.info(String.format("***Synchronizing Child (#%s) of Parent (#%s)", childProposal.getProposalNumber(),
                hierarchy.getProposalNumber()));

        prepareHierarchySync(hierarchy);
        boolean changed = synchronizeChildProposal(hierarchy, childProposal);
        if (changed) {
            aggregateHierarchy(hierarchy);
        }
        finalizeHierarchySync(hierarchy);
        LOG.info(String.format("***Synchronizing Child (#%s) of Parent (#%s) complete",
                childProposal.getProposalNumber(), hierarchy.getProposalNumber()));
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#lookupParent(org.kuali.kra.proposaldevelopment.bo.DevelopmentProposal)
     */
    public DevelopmentProposal lookupParent(DevelopmentProposal childProposal) throws ProposalHierarchyException {
        return getHierarchy(childProposal.getHierarchyParentProposalNumber());
    }

    protected void linkChild(DevelopmentProposal hierarchyProposal, DevelopmentProposal newChildProposal,
            String hierarchyBudgetTypeCode) throws ProposalHierarchyException {
        // set child to child status
        newChildProposal.setHierarchyStatus(HierarchyStatusConstants.Child.code());
        newChildProposal.setHierarchyParentProposalNumber(hierarchyProposal.getProposalNumber());
        newChildProposal.setHierarchyBudgetType(hierarchyBudgetTypeCode);
        // call synchronize
        synchronizeChildProposal(hierarchyProposal, newChildProposal);
        // call aggregate
        aggregateHierarchy(hierarchyProposal);
    }

    protected void copyInitialData(DevelopmentProposal hierarchyProposal, DevelopmentProposal srcProposal)
            throws ProposalHierarchyException {
        // Required fields for saving document
        hierarchyProposal.setHierarchyOriginatingChildProposalNumber(srcProposal.getProposalNumber());
        hierarchyProposal.setSponsor(srcProposal.getSponsor());
        hierarchyProposal.setSponsorCode(srcProposal.getSponsorCode());
        hierarchyProposal.setProposalTypeCode(srcProposal.getProposalTypeCode());
        hierarchyProposal.setRequestedStartDateInitial(srcProposal.getRequestedStartDateInitial());
        hierarchyProposal.setRequestedEndDateInitial(srcProposal.getRequestedEndDateInitial());
        hierarchyProposal.setOwnedByUnit(srcProposal.getOwnedByUnit());
        hierarchyProposal.setOwnedByUnitNumber(srcProposal.getOwnedByUnitNumber());
        hierarchyProposal.setActivityType(srcProposal.getActivityType());
        hierarchyProposal.setActivityTypeCode(srcProposal.getActivityTypeCode());
        hierarchyProposal.setTitle(srcProposal.getTitle());

        // Sponsor & program information
        hierarchyProposal.setDeadlineDate(srcProposal.getDeadlineDate());
        hierarchyProposal.setDeadlineType(srcProposal.getDeadlineType());
        hierarchyProposal.setNoticeOfOpportunityCode(srcProposal.getNoticeOfOpportunityCode());
        hierarchyProposal.setCfdaNumber(srcProposal.getCfdaNumber());
        hierarchyProposal.setPrimeSponsorCode(srcProposal.getPrimeSponsorCode());
        hierarchyProposal.setNsfCode(srcProposal.getNsfCode());
        hierarchyProposal.setSponsorProposalNumber(srcProposal.getSponsorProposalNumber());
        hierarchyProposal.setAgencyDivisionCode(srcProposal.getAgencyDivisionCode());
        hierarchyProposal.setAgencyProgramCode(srcProposal.getAgencyProgramCode());
        hierarchyProposal.setSubcontracts(srcProposal.getSubcontracts());
        hierarchyProposal.setProgramAnnouncementNumber(srcProposal.getProgramAnnouncementNumber());
        hierarchyProposal.setProgramAnnouncementTitle(srcProposal.getProgramAnnouncementTitle());

        // Organization/location
        ProposalSite newSite;
        hierarchyProposal.getProposalSites().clear();
        for (ProposalSite site : srcProposal.getProposalSites()) {
            newSite = (ProposalSite) ObjectUtils.deepCopy(site);
            newSite.setProposalNumber(null);
            newSite.setVersionNumber(null);
            for (CongressionalDistrict cd : newSite.getCongressionalDistricts()) {
                cd.setProposalNumber(null);
                cd.setCongressionalDistrictId(null);
                cd.setVersionNumber(null);
            }
            hierarchyProposal.addProposalSite(newSite);
        }

        // Delivery info
        hierarchyProposal.setMailBy(srcProposal.getMailBy());
        hierarchyProposal.setMailType(srcProposal.getMailType());
        hierarchyProposal.setMailAccountNumber(srcProposal.getMailAccountNumber());
        hierarchyProposal.setNumberOfCopies(srcProposal.getNumberOfCopies());
        hierarchyProposal.setMailingAddressId(srcProposal.getMailingAddressId());
        hierarchyProposal.setMailDescription(srcProposal.getMailDescription());
    }

    /**
     * Synchronizes all child proposals to the parent.
     * @param hierarchyProposal
     * @return
     * @throws ProposalHierarchyException
     */
    protected boolean synchronizeAllChildProposals(DevelopmentProposal hierarchyProposal)
            throws ProposalHierarchyException {
        boolean changed = false;

        for (DevelopmentProposal childProposal : getHierarchyChildren(hierarchyProposal.getProposalNumber())) {
            /*  TODO restore code below after testing
            if (isSynchronized(childProposal.getProposalNumber())) {
            break;
            }
            */
            List<PropScienceKeyword> oldKeywords = getOldKeywords(hierarchyProposal, childProposal);
            ProposalPerson principalInvestigator = hierarchyProposal.getPrincipalInvestigator();
            BudgetDocument<DevelopmentProposal> hierarchyBudgetDocument = getHierarchyBudget(hierarchyProposal);
            Budget hierarchyBudget = hierarchyBudgetDocument.getBudget();
            BudgetDocument<DevelopmentProposal> childBudgetDocument = getFinalOrLatestChildBudget(childProposal);
            Budget childBudget = childBudgetDocument.getBudget();
            ObjectUtils.materializeAllSubObjects(hierarchyBudget);
            ObjectUtils.materializeAllSubObjects(childBudget);
            childProposal.setHierarchyLastSyncHashCode(computeHierarchyHashCode(childProposal, childBudget));

            removeChildElements(hierarchyProposal, hierarchyBudget, childProposal.getProposalNumber());

            synchronizeKeywords(hierarchyProposal, childProposal, oldKeywords);
            synchronizeSpecialReviews(hierarchyProposal, childProposal);
            synchronizeNarratives(hierarchyProposal, childProposal);
            synchronizePersons(hierarchyProposal, childProposal, principalInvestigator);
            businessObjectService.save(childProposal);

            synchronizeBudget(hierarchyProposal, childProposal, hierarchyBudget, childBudget,
                    hierarchyBudgetDocument);
            changed = true;
        }

        return changed;
    }

    /**
     * Synchronizes the given child proposal to the parent.  
     * <p>
     * If a key protocol person appears in multiple child proposals and is removed from the given child 
     * proposal, then this also aggregates that key person back to the parent proposal from a different child proposal, making sure that all the key persons
     * in all of the child proposals are represented in the parent proposal.
     *  
     * @param hierarchyProposal
     * @param childProposal
     * @return
     * @throws ProposalHierarchyException
     */
    protected boolean synchronizeChildProposal(DevelopmentProposal hierarchyProposal,
            DevelopmentProposal childProposal) throws ProposalHierarchyException {
        /*  TODO restore code below after testing
        if (isSynchronized(childProposal.getProposalNumber())) {
        return false;
        }
         */

        List<PropScienceKeyword> oldKeywords = getOldKeywords(hierarchyProposal, childProposal);
        ProposalPerson principalInvestigator = hierarchyProposal.getPrincipalInvestigator();
        BudgetDocument<DevelopmentProposal> hierarchyBudgetDocument = getHierarchyBudget(hierarchyProposal);
        Budget hierarchyBudget = hierarchyBudgetDocument.getBudget();
        BudgetDocument<DevelopmentProposal> childBudgetDocument = getFinalOrLatestChildBudget(childProposal);
        Budget childBudget = childBudgetDocument.getBudget();
        ObjectUtils.materializeAllSubObjects(hierarchyBudget);
        ObjectUtils.materializeAllSubObjects(childBudget);
        childProposal.setHierarchyLastSyncHashCode(computeHierarchyHashCode(childProposal, childBudget));

        removeChildElements(hierarchyProposal, hierarchyBudget, childProposal.getProposalNumber());

        synchronizeKeywords(hierarchyProposal, childProposal, oldKeywords);
        synchronizeSpecialReviews(hierarchyProposal, childProposal);
        synchronizeNarratives(hierarchyProposal, childProposal);
        synchronizePersonsAndAggregate(hierarchyProposal, childProposal, principalInvestigator);
        businessObjectService.save(childProposal);

        synchronizeBudget(hierarchyProposal, childProposal, hierarchyBudget, childBudget, hierarchyBudgetDocument);
        return true;
    }

    /**
     * Gets the old proposal science keywords before removing them from the parent.
     * @param hierarchyProposal
     * @param childProposal
     * @return
     */
    protected List<PropScienceKeyword> getOldKeywords(DevelopmentProposal hierarchyProposal,
            DevelopmentProposal childProposal) {
        List<PropScienceKeyword> oldKeywords = new ArrayList<PropScienceKeyword>();
        for (PropScienceKeyword keyword : hierarchyProposal.getPropScienceKeywords()) {
            if (StringUtils.equals(childProposal.getProposalNumber(), keyword.getHierarchyProposalNumber())) {
                oldKeywords.add(keyword);
            }
        }
        return oldKeywords;
    }

    /**
     * Synchronizes the proposal science keywords from the child proposal to the parent proposal.
     * @param hierarchyProposal
     * @param childProposal
     * @param oldKeywords
     */
    protected void synchronizeKeywords(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal,
            List<PropScienceKeyword> oldKeywords) {
        for (PropScienceKeyword keyword : childProposal.getPropScienceKeywords()) {
            PropScienceKeyword newKeyword = new PropScienceKeyword(hierarchyProposal.getProposalNumber(),
                    keyword.getScienceKeyword());
            int index = oldKeywords.indexOf(newKeyword);
            if (index > -1) {
                newKeyword = oldKeywords.get(index);
            }
            newKeyword.setHierarchyProposalNumber(childProposal.getProposalNumber());
            hierarchyProposal.addPropScienceKeyword(newKeyword);
        }
    }

    /**
     * Synchronizes the proposal special reviews from the child proposal to the parent proposal.
     * @param hierarchyProposal
     * @param childProposal
     */
    protected void synchronizeSpecialReviews(DevelopmentProposal hierarchyProposal,
            DevelopmentProposal childProposal) {
        for (ProposalSpecialReview review : childProposal.getPropSpecialReviews()) {
            ProposalSpecialReview newReview = (ProposalSpecialReview) ObjectUtils.deepCopy(review);
            newReview.setProposalNumber(hierarchyProposal.getProposalNumber());
            newReview.setSpecialReviewNumber(hierarchyProposal.getProposalDocument()
                    .getDocumentNextValue(Constants.PROPOSAL_SPECIALREVIEW_NUMBER));
            newReview.setVersionNumber(null);
            newReview.setHierarchyProposalNumber(childProposal.getProposalNumber());
            hierarchyProposal.getPropSpecialReviews().add(newReview);
        }
    }

    /**
     * Synchronizes the proposal narratives from the child proposal to the parent proposal.
     * @param hierarchyProposal
     * @param childProposal
     */
    protected void synchronizeNarratives(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal) {
        String instituteNarrativeTypeGroup = parameterService.getParameterValue(ProposalDevelopmentDocument.class,
                PARAMETER_NAME_INSTITUTE_NARRATIVE_TYPE_GROUP);
        for (Narrative narrative : childProposal.getNarratives()) {
            if (!StringUtils.equalsIgnoreCase(narrative.getNarrativeType().getAllowMultiple(), "N")
                    && !StringUtils.equalsIgnoreCase(narrative.getNarrativeType().getNarrativeTypeGroup(),
                            instituteNarrativeTypeGroup)) {
                Map<String, String> primaryKey = new HashMap<String, String>();
                primaryKey.put("proposalNumber", narrative.getProposalNumber());
                primaryKey.put("moduleNumber", narrative.getModuleNumber() + "");
                NarrativeAttachment attachment = (NarrativeAttachment) businessObjectService
                        .findByPrimaryKey(NarrativeAttachment.class, primaryKey);
                narrative.getNarrativeAttachmentList().clear();
                narrative.getNarrativeAttachmentList().add(attachment);

                Narrative newNarrative = (Narrative) ObjectUtils.deepCopy(narrative);
                newNarrative.setVersionNumber(null);
                newNarrative.setHierarchyProposalNumber(childProposal.getProposalNumber());
                narrativeService.addNarrative(hierarchyProposal.getProposalDocument(), newNarrative);
            }
        }
    }

    /**
     * Synchronizes the proposal persons from the child proposal to the parent proposal and then restores any proposal persons that were in the given child
     * proposal (and hence removed from the given parent proposal).
     * <p>
     * This first synchronizes the main proposal persons from the primary child proposal to the parent proposal and then runs the same algorithm on all other
     * children of the parent proposal.
     * @param hierarchyProposal
     * @param primaryChildProposal
     * @param principalInvestigator
     */
    protected void synchronizePersonsAndAggregate(DevelopmentProposal hierarchyProposal,
            DevelopmentProposal primaryChildProposal, ProposalPerson principalInvestigator) {

        synchronizePersons(hierarchyProposal, primaryChildProposal, principalInvestigator);
        for (DevelopmentProposal childProposal : getHierarchyChildren(hierarchyProposal.getProposalNumber())) {
            if (!StringUtils.equals(primaryChildProposal.getProposalNumber(), childProposal.getProposalNumber())) {
                synchronizePersons(hierarchyProposal, childProposal, principalInvestigator);
            }
        }
    }

    /**
     * Synchronizes the proposal persons from the child proposal to the parent proposal.
     * @param hierarchyProposal
     * @param childProposal
     * @param principalInvestigator
     */
    protected void synchronizePersons(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal,
            ProposalPerson principalInvestigator) {
        for (ProposalPerson person : childProposal.getProposalPersons()) {
            int firstIndex = hierarchyProposal.getProposalPersons().indexOf(person);
            int lastIndex = hierarchyProposal.getProposalPersons().lastIndexOf(person);
            ProposalPerson firstInstance = (firstIndex == -1) ? null
                    : hierarchyProposal.getProposalPersons().get(firstIndex);
            if (firstIndex == -1 || (firstIndex == lastIndex && !rolesAreSimilar(person, firstInstance))) {
                ProposalPerson newPerson;
                newPerson = (ProposalPerson) ObjectUtils.deepCopy(person);
                newPerson.setProposalNumber(hierarchyProposal.getProposalNumber());
                newPerson.getProposalPersonYnqs().clear();
                newPerson.getCreditSplits().clear();
                for (ProposalPersonUnit unit : newPerson.getUnits()) {
                    unit.getCreditSplits().clear();
                }
                newPerson.setProposalPersonNumber(null);
                newPerson.setVersionNumber(null);
                newPerson.setHierarchyProposalNumber(childProposal.getProposalNumber());

                if (StringUtils.equalsIgnoreCase(person.getProposalPersonRoleId(),
                        Constants.PRINCIPAL_INVESTIGATOR_ROLE)) {
                    newPerson.setProposalPersonRoleId(Constants.CO_INVESTIGATOR_ROLE);
                }
                if (newPerson.equals(principalInvestigator)
                        && (firstIndex == -1 || !firstInstance.isInvestigator())) {
                    newPerson.setProposalPersonRoleId(Constants.PRINCIPAL_INVESTIGATOR_ROLE);
                }

                if (person.getProposalPersonExtendedAttributes() != null) {
                    ProposalPersonExtendedAttributes newPersonEA = (ProposalPersonExtendedAttributes) ObjectUtils
                            .deepCopy(person.getProposalPersonExtendedAttributes());
                    newPersonEA.setProposalNumber(hierarchyProposal.getProposalNumber());
                    newPersonEA.setProposalPersonNumber(newPerson.getProposalPersonNumber());
                    newPerson.setProposalPersonExtendedAttributes(newPersonEA);
                }

                hierarchyProposal.addProposalPerson(newPerson);
            }
        }
    }

    /**
     * Synchronizes the proposal budget from the child proposal to the parent proposal.
     * @param hierarchyProposal
     * @param childProposal
     * @param hierarchyBudget
     * @param childBudget
     * @param hierarchyBudgetDocument
     * @throws ProposalHierarchyException
     */
    protected void synchronizeBudget(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal,
            Budget hierarchyBudget, Budget childBudget, BudgetDocument hierarchyBudgetDocument)
            throws ProposalHierarchyException {

        LOG.info(String.format("***Beginning Hierarchy Budget Sync for Parent %s and Child %s",
                hierarchyProposal.getProposalNumber(), childProposal.getProposalNumber()));
        synchronizeChildBudget(hierarchyBudget, childBudget, childProposal.getProposalNumber(),
                childProposal.getHierarchyBudgetType(), StringUtils.equals(childProposal.getProposalNumber(),
                        hierarchyProposal.getHierarchyOriginatingChildProposalNumber()));
        if (hierarchyBudget.getEndDate().after(hierarchyProposal.getRequestedEndDateInitial())) {
            hierarchyProposal.setRequestedEndDateInitial(hierarchyBudget.getEndDate());
        }
        if (childProposal.getRequestedEndDateInitial().after(hierarchyProposal.getRequestedEndDateInitial())) {
            hierarchyProposal.setRequestedEndDateInitial(childProposal.getRequestedEndDateInitial());
        }
        try {
            documentService.saveDocument(hierarchyBudgetDocument);
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }
        LOG.info(String.format("***Completed Hierarchy Budget Sync for Parent %s and Child %s",
                hierarchyProposal.getProposalNumber(), childProposal.getProposalNumber()));
    }

    protected void syncProposalPersons(DevelopmentProposal childProposal, DevelopmentProposal hierarchyProposal,
            ProposalPerson pi, List<ProposalPerson> removedPersons) {
        if (proposalPersonExtendedAttributesToDelete == null) {
            proposalPersonExtendedAttributesToDelete = new ArrayList<ProposalPersonExtendedAttributes>();
        }

        //now remove any other attachments for the persons we removed
        for (ProposalPerson removedPerson : removedPersons) {
            List<ProposalPersonBiography> currentBiographies = hierarchyProposal.getPropPersonBios();
            ListIterator<ProposalPersonBiography> iter = currentBiographies.listIterator();
            while (iter.hasNext()) {
                ProposalPersonBiography bio = iter.next();
                if (StringUtils.equalsIgnoreCase(bio.getPersonId(), removedPerson.getPersonId())
                        && removedPerson.getProposalPersonNumber().equals(bio.getProposalPersonNumber())) {
                    iter.remove();
                }
            }

            if (removedPerson.getProposalPersonExtendedAttributes() != null) {
                proposalPersonExtendedAttributesToDelete.add(removedPerson.getProposalPersonExtendedAttributes());
            }
        }
    }

    protected void synchronizeChildBudget(Budget parentBudget, Budget childBudget, String childProposalNumber,
            String hierarchyBudgetTypeCode, boolean isOriginatingChildBudget) throws ProposalHierarchyException {
        try {

            if (isOriginatingChildBudget) {
                parentBudget.setUrRateClassCode(childBudget.getUrRateClassCode());
                parentBudget.setOhRateClassCode(childBudget.getOhRateClassCode());
            }

            BudgetPerson newPerson;
            Map<Integer, BudgetPerson> personMap = new HashMap<Integer, BudgetPerson>();
            for (BudgetPerson person : childBudget.getBudgetPersons()) {
                newPerson = (BudgetPerson) ObjectUtils.deepCopy(person);
                newPerson.setPersonSequenceNumber(parentBudget.getBudgetDocument()
                        .getHackedDocumentNextValue(Constants.PERSON_SEQUENCE_NUMBER));
                //                newPerson.setBudget(parentBudget);
                newPerson.setBudgetId(parentBudget.getBudgetId());
                newPerson.setHierarchyProposalNumber(childProposalNumber);
                newPerson.setVersionNumber(null);
                parentBudget.addBudgetPerson(newPerson);
                personMap.put(person.getPersonSequenceNumber(), newPerson);
            }

            BudgetSubAwards newSubAwards;
            for (BudgetSubAwards childSubAwards : childBudget.getBudgetSubAwards()) {
                childSubAwards.refreshReferenceObject("budgetSubAwardAttachments");
                childSubAwards.refreshReferenceObject("budgetSubAwardFiles");
                newSubAwards = (BudgetSubAwards) ObjectUtils.deepCopy(childSubAwards);
                newSubAwards.setBudgetId(parentBudget.getBudgetId());
                //                newSubAwards.setBudget(parentBudget);
                newSubAwards.setBudgetVersionNumber(parentBudget.getBudgetVersionNumber());
                newSubAwards.setSubAwardNumber(
                        parentBudget.getBudgetDocument().getHackedDocumentNextValue("subAwardNumber") != null
                                ? parentBudget.getBudgetDocument().getHackedDocumentNextValue("subAwardNumber")
                                : 1);
                newSubAwards.setVersionNumber(null);
                newSubAwards.setHierarchyProposalNumber(childProposalNumber);
                for (BudgetSubAwardAttachment attachment : newSubAwards.getBudgetSubAwardAttachments()) {
                    attachment.setSubAwardNumber(newSubAwards.getSubAwardNumber());
                    //                    attachment.setBudget(parentBudget);
                    attachment.setBudgetId(parentBudget.getBudgetId());
                    attachment.setBudgetSubawardAttachmentId(null);
                    attachment.setVersionNumber(null);
                }
                for (BudgetSubAwardFiles files : newSubAwards.getBudgetSubAwardFiles()) {
                    files.setSubAwardNumber(newSubAwards.getSubAwardNumber());
                    //                    files.setBudget(parentBudget);
                    files.setBudgetId(parentBudget.getBudgetId());
                    files.setVersionNumber(null);
                }
                List<BudgetAssociate> listToBeSaved = new ArrayList<BudgetAssociate>();
                listToBeSaved.add(newSubAwards);
                listToBeSaved.addAll(newSubAwards.getBudgetSubAwardFiles());
                listToBeSaved.addAll(newSubAwards.getBudgetSubAwardAttachments());
                businessObjectService.save(listToBeSaved);
                parentBudget.getBudgetSubAwards().add(newSubAwards);
            }

            int parentStartPeriod = getCorrespondingParentPeriod(parentBudget, childBudget);
            if (parentStartPeriod == -1) {
                throw new ProposalHierarchyException(
                        "Cannot find a parent budget period that corresponds to the child period.");
            }

            List<BudgetPeriod> parentPeriods = parentBudget.getBudgetPeriods();
            List<BudgetPeriod> childPeriods = childBudget.getBudgetPeriods();
            BudgetPeriod parentPeriod, childPeriod;
            Long budgetId = parentBudget.getBudgetId();
            Long budgetPeriodId;
            Integer budgetPeriod;
            BudgetCostShare newCostShare;
            for (BudgetCostShare costShare : childBudget.getBudgetCostShares()) {
                if (StringUtils.isNotEmpty(costShare.getSourceAccount())) {
                    newCostShare = (BudgetCostShare) ObjectUtils.deepCopy(costShare);
                    newCostShare.setBudgetId(budgetId);
                    newCostShare.setDocumentComponentId(
                            parentBudget.getHackedDocumentNextValue(newCostShare.getDocumentComponentIdKey()));
                    newCostShare.setObjectId(null);
                    newCostShare.setVersionNumber(null);
                    newCostShare.setHierarchyProposalNumber(childProposalNumber);
                    newCostShare.setHiddenInHierarchy(true);
                    businessObjectService.save(newCostShare);
                }
            }

            BudgetUnrecoveredFandA newUnrecoveredFandA;
            for (BudgetUnrecoveredFandA unrecoveredFandA : childBudget.getBudgetUnrecoveredFandAs()) {
                if (StringUtils.isNotEmpty(unrecoveredFandA.getSourceAccount())) {
                    newUnrecoveredFandA = (BudgetUnrecoveredFandA) ObjectUtils.deepCopy(unrecoveredFandA);
                    newUnrecoveredFandA.setBudgetId(budgetId);
                    newUnrecoveredFandA.setDocumentComponentId(parentBudget
                            .getHackedDocumentNextValue(newUnrecoveredFandA.getDocumentComponentIdKey()));
                    newUnrecoveredFandA.setObjectId(null);
                    newUnrecoveredFandA.setVersionNumber(null);
                    newUnrecoveredFandA.setHierarchyProposalNumber(childProposalNumber);
                    newUnrecoveredFandA.setHiddenInHierarchy(true);
                    businessObjectService.save(newUnrecoveredFandA);
                }
            }

            for (int i = 0, j = parentStartPeriod; i < childPeriods.size(); i++, j++) {
                childPeriod = childPeriods.get(i);
                if (j >= parentPeriods.size()) {
                    parentPeriod = parentBudget.getNewBudgetPeriod();
                    parentPeriod.setBudgetPeriod(j + 1);
                    parentPeriod.setBudget(parentBudget);
                    parentPeriod.setStartDate(childPeriod.getStartDate());
                    parentPeriod.setEndDate(childPeriod.getEndDate());
                    parentPeriod.setBudgetId(budgetId);
                    parentBudget.add(parentPeriod);
                } else {
                    parentPeriod = parentPeriods.get(j);
                }

                budgetPeriodId = parentPeriod.getBudgetPeriodId();
                budgetPeriod = parentPeriod.getBudgetPeriod();
                BudgetLineItem parentLineItem;
                Integer lineItemNumber;

                if (StringUtils.equals(hierarchyBudgetTypeCode, HierarchyBudgetTypeConstants.SubBudget.code())) {
                    for (BudgetLineItem childLineItem : childPeriod.getBudgetLineItems()) {
                        parentLineItem = (BudgetLineItem) (KraServiceLocator.getService(DeepCopyPostProcessor.class)
                                .processDeepCopyWithDeepCopyIgnore(childLineItem));
                        lineItemNumber = parentBudget.getBudgetDocument()
                                .getHackedDocumentNextValue(Constants.BUDGET_LINEITEM_NUMBER);

                        parentLineItem.setHierarchyProposalNumber(childProposalNumber);

                        parentLineItem.setBudgetLineItemId(null);
                        parentLineItem.setBudgetId(budgetId);
                        parentLineItem.setBudgetPeriodId(budgetPeriodId);
                        parentLineItem.setBudgetPeriod(budgetPeriod);
                        parentLineItem.setLineItemNumber(lineItemNumber);
                        parentLineItem.setVersionNumber(null);
                        parentLineItem.setObjectId(null);

                        for (BudgetLineItemCalculatedAmount calAmt : parentLineItem
                                .getBudgetLineItemCalculatedAmounts()) {
                            calAmt.setBudgetLineItemCalculatedAmountId(null);
                            calAmt.setBudgetId(budgetId);
                            calAmt.setBudgetPeriodId(budgetPeriodId);
                            calAmt.setBudgetPeriod(budgetPeriod);
                            calAmt.setLineItemNumber(lineItemNumber);
                            calAmt.setVersionNumber(null);
                            calAmt.setObjectId(null);
                        }
                        BudgetPerson budgetPerson;
                        for (BudgetPersonnelDetails details : parentLineItem.getBudgetPersonnelDetailsList()) {
                            budgetPerson = personMap.get(details.getPersonSequenceNumber());
                            details.setBudgetPerson(budgetPerson);
                            details.setJobCode(budgetPerson.getJobCode());
                            details.setPersonId(budgetPerson.getPersonRolodexTbnId());
                            details.setPersonSequenceNumber(budgetPerson.getPersonSequenceNumber());
                            details.setPersonNumber(parentBudget.getBudgetDocument()
                                    .getHackedDocumentNextValue(Constants.BUDGET_PERSON_LINE_NUMBER));

                            details.setBudgetPersonnelLineItemId(null);
                            details.setBudgetId(budgetId);
                            details.setBudgetPeriodId(budgetPeriodId);
                            details.setBudgetPeriod(budgetPeriod);
                            details.setLineItemNumber(lineItemNumber);
                            details.setVersionNumber(null);
                            details.setObjectId(null);

                            for (BudgetPersonnelCalculatedAmount calAmt : details
                                    .getBudgetPersonnelCalculatedAmounts()) {
                                calAmt.setBudgetPersonnelCalculatedAmountId(null);
                                calAmt.setBudgetId(budgetId);
                                calAmt.setBudgetPeriodId(budgetPeriodId);
                                calAmt.setBudgetPeriod(budgetPeriod);
                                calAmt.setLineItemNumber(lineItemNumber);
                                calAmt.setVersionNumber(null);
                                calAmt.setObjectId(null);
                            }
                        }
                        parentPeriod.getBudgetLineItems().add(parentLineItem);
                    }
                } else { // subproject budget
                    Map<String, String> primaryKeys;
                    CostElement costElement;
                    String directCostElement = parameterService.getParameterValue(BudgetDocument.class,
                            PARAMETER_NAME_DIRECT_COST_ELEMENT);
                    String indirectCostElement = parameterService.getParameterValue(BudgetDocument.class,
                            PARAMETER_NAME_INDIRECT_COST_ELEMENT);

                    if (childPeriod.getTotalIndirectCost().isNonZero()) {
                        primaryKeys = new HashMap<String, String>();
                        primaryKeys.put("costElement", indirectCostElement);
                        costElement = (CostElement) businessObjectService.findByPrimaryKey(CostElement.class,
                                primaryKeys);
                        parentLineItem = parentBudget.getNewBudgetLineItem();
                        parentLineItem.setLineItemDescription(childProposalNumber);
                        parentLineItem.setStartDate(parentPeriod.getStartDate());
                        parentLineItem.setEndDate(parentPeriod.getEndDate());
                        parentLineItem.setBudgetId(budgetId);
                        parentLineItem.setBudgetPeriodId(budgetPeriodId);
                        parentLineItem.setBudgetPeriod(budgetPeriod);
                        parentLineItem.setVersionNumber(null);
                        lineItemNumber = parentBudget.getBudgetDocument()
                                .getHackedDocumentNextValue(Constants.BUDGET_LINEITEM_NUMBER);
                        parentLineItem.setLineItemNumber(lineItemNumber);
                        parentLineItem.setHierarchyProposalNumber(childProposalNumber);
                        parentLineItem.setLineItemCost(childPeriod.getTotalIndirectCost());
                        parentLineItem.setIndirectCost(childPeriod.getTotalIndirectCost());
                        parentLineItem.setCostElementBO(costElement);
                        parentLineItem.setCostElement(costElement.getCostElement());
                        parentLineItem.setBudgetCategoryCode(costElement.getBudgetCategoryCode());
                        parentLineItem.setOnOffCampusFlag(costElement.getOnOffCampusFlag());
                        parentLineItem.setApplyInRateFlag(true);
                        parentPeriod.getBudgetLineItems().add(parentLineItem);
                    }
                    if (childPeriod.getTotalDirectCost().isNonZero()) {
                        primaryKeys = new HashMap<String, String>();
                        primaryKeys.put("costElement", directCostElement);
                        costElement = (CostElement) businessObjectService.findByPrimaryKey(CostElement.class,
                                primaryKeys);
                        parentLineItem = parentBudget.getNewBudgetLineItem();
                        parentLineItem.setLineItemDescription(childProposalNumber);
                        parentLineItem.setStartDate(parentPeriod.getStartDate());
                        parentLineItem.setEndDate(parentPeriod.getEndDate());
                        parentLineItem.setBudgetId(budgetId);
                        parentLineItem.setBudgetPeriodId(budgetPeriodId);
                        parentLineItem.setBudgetPeriod(budgetPeriod);
                        parentLineItem.setVersionNumber(null);
                        lineItemNumber = parentBudget.getBudgetDocument()
                                .getHackedDocumentNextValue(Constants.BUDGET_LINEITEM_NUMBER);
                        parentLineItem.setLineItemNumber(lineItemNumber);
                        parentLineItem.setHierarchyProposalNumber(childProposalNumber);
                        parentLineItem.setLineItemCost(childPeriod.getTotalDirectCost());
                        parentLineItem.setDirectCost(childPeriod.getTotalDirectCost());
                        parentLineItem.setCostElementBO(costElement);
                        parentLineItem.setCostElement(costElement.getCostElement());
                        parentLineItem.setBudgetCategoryCode(costElement.getBudgetCategoryCode());
                        parentLineItem.setOnOffCampusFlag(costElement.getOnOffCampusFlag());
                        parentLineItem.setApplyInRateFlag(true);
                        parentPeriod.getBudgetLineItems().add(parentLineItem);
                    }
                }
            }
            parentBudget.setStartDate(parentBudget.getBudgetPeriod(0).getStartDate());
            parentBudget.setEndDate(
                    parentBudget.getBudgetPeriod(parentBudget.getBudgetPeriods().size() - 1).getEndDate());
        } catch (Exception e) {
            LOG.error("Problem copying line items to parent", e);
            throw new ProposalHierarchyException("Problem copying line items to parent", e);
        }

    }

    protected void aggregateHierarchy(DevelopmentProposal hierarchy) throws ProposalHierarchyException {
        LOG.info(String.format("***Aggregating Proposal Hierarchy #%s", hierarchy.getProposalNumber()));
        List<ProposalPersonBiography> biosToRemove = new ArrayList<ProposalPersonBiography>();
        for (ProposalPersonBiography bio : hierarchy.getPropPersonBios()) {
            loadBioContent(bio);
            String bioPersonId = bio.getPersonId();
            Integer bioRolodexId = bio.getRolodexId();
            boolean keep = false;
            for (ProposalPerson person : hierarchy.getProposalPersons()) {
                if ((bioPersonId != null && bioPersonId.equals(person.getPersonId()))
                        || (bioRolodexId != null && bioRolodexId.equals(person.getRolodexId()))) {
                    bio.setProposalPersonNumber(person.getProposalPersonNumber());
                    for (ProposalPersonBiographyAttachment attachment : bio.getPersonnelAttachmentList()) {
                        attachment.setProposalPersonNumber(person.getProposalPersonNumber());
                    }
                    keep = true;
                    break;
                }
            }
            if (!keep) {
                biosToRemove.add(bio);
            }
        }
        if (!biosToRemove.isEmpty()) {
            hierarchy.getPropPersonBios().removeAll(biosToRemove);
        }

        BudgetDocument<DevelopmentProposal> hierarchyBudgetDocument = getHierarchyBudget(hierarchy);
        Budget hierarchyBudget = hierarchyBudgetDocument.getBudget();

        hierarchyBudget.getBudgetCostShares().clear();
        hierarchyBudget.getBudgetUnrecoveredFandAs().clear();

        Map<String, Object> fieldValues = new HashMap<String, Object>();
        fieldValues.put("hiddenInHierarchy", true);
        fieldValues.put("budgetId", hierarchyBudget.getBudgetId());
        Collection<BudgetCostShare> hiddenCostShares = businessObjectService.findMatching(BudgetCostShare.class,
                fieldValues);
        Collection<BudgetUnrecoveredFandA> hiddenUnrecoveredFandAs = businessObjectService
                .findMatching(BudgetUnrecoveredFandA.class, fieldValues);
        Map<Integer, BudgetCostShare> newCostShares = new HashMap<Integer, BudgetCostShare>();
        Map<Integer, BudgetUnrecoveredFandA> newUnrecoveredFandAs = new HashMap<Integer, BudgetUnrecoveredFandA>();
        BudgetCostShare newCostShare;
        BudgetUnrecoveredFandA newUnrecoveredFandA;
        Integer keyHash;
        for (BudgetCostShare costShare : hiddenCostShares) {
            keyHash = Arrays.hashCode(new Object[] { costShare.getProjectPeriod(), costShare.getSourceAccount() });
            newCostShare = newCostShares.get(keyHash);
            if (newCostShare == null) {
                newCostShare = (BudgetCostShare) ObjectUtils.deepCopy(costShare);
                //newCostShare.setBudgetId(hierarchyBudget.getBudgetId());
                newCostShare.setDocumentComponentId(null);
                newCostShare.setObjectId(null);
                newCostShare.setVersionNumber(null);
                newCostShares.put(keyHash, newCostShare);
            } else {
                newCostShare
                        .setSharePercentage(newCostShare.getSharePercentage().add(costShare.getSharePercentage()));
                if (newCostShare.getSharePercentage().isGreaterThan(new BudgetDecimal(100.0))) {
                    newCostShare.setSharePercentage(new BudgetDecimal(100.0));
                }
                newCostShare.setShareAmount(newCostShare.getShareAmount().add(costShare.getShareAmount()));
            }
        }
        for (BudgetUnrecoveredFandA unrecoveredFandA : hiddenUnrecoveredFandAs) {
            keyHash = Arrays
                    .hashCode(new Object[] { unrecoveredFandA.getFiscalYear(), unrecoveredFandA.getSourceAccount(),
                            unrecoveredFandA.getApplicableRate(), unrecoveredFandA.getOnCampusFlag() });
            newUnrecoveredFandA = newUnrecoveredFandAs.get(keyHash);
            if (newUnrecoveredFandA == null) {
                newUnrecoveredFandA = (BudgetUnrecoveredFandA) ObjectUtils.deepCopy(unrecoveredFandA);
                newUnrecoveredFandA.setBudgetId(hierarchyBudget.getBudgetId());
                newUnrecoveredFandA.setDocumentComponentId(null);
                newUnrecoveredFandA.setObjectId(null);
                newUnrecoveredFandA.setVersionNumber(null);
                newUnrecoveredFandAs.put(keyHash, newUnrecoveredFandA);
            } else {
                newUnrecoveredFandA.setAmount(newUnrecoveredFandA.getAmount().add(unrecoveredFandA.getAmount()));
            }
        }
        for (BudgetCostShare costShare : newCostShares.values()) {
            costShare.setHiddenInHierarchy(false);
            costShare.setHierarchyProposalNumber(null);
            hierarchyBudget.add(costShare);
        }
        for (BudgetUnrecoveredFandA unrecoveredFandA : newUnrecoveredFandAs.values()) {
            unrecoveredFandA.setHiddenInHierarchy(false);
            unrecoveredFandA.setHierarchyProposalNumber(null);
            hierarchyBudget.add(unrecoveredFandA);
        }

        KualiForm oldForm = GlobalVariables.getKualiForm();
        GlobalVariables.setKualiForm(null);
        KraServiceLocator.getService(BudgetCalculationService.class).calculateBudget(hierarchyBudget);
        KraServiceLocator.getService(BudgetCalculationService.class).calculateBudgetSummaryTotals(hierarchyBudget);
        GlobalVariables.setKualiForm(oldForm);
        try {
            documentService.saveDocument(hierarchyBudgetDocument);
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }
    }

    protected DevelopmentProposal getHierarchy(String hierarchyProposalNumber) throws ProposalHierarchyException {
        DevelopmentProposal hierarchy = getDevelopmentProposal(hierarchyProposalNumber);
        if (hierarchy == null || !hierarchy.isParent())
            throw new ProposalHierarchyException("Proposal " + hierarchyProposalNumber + " is not a hierarchy");
        return hierarchy;
    }

    public DevelopmentProposal getDevelopmentProposal(String proposalNumber) {
        Map<String, String> pk = new HashMap<String, String>();
        pk.put("proposalNumber", proposalNumber);
        return (DevelopmentProposal) (businessObjectService.findByPrimaryKey(DevelopmentProposal.class, pk));
    }

    protected boolean isSynchronized(String childProposalNumber) throws ProposalHierarchyException {
        DevelopmentProposal childProposal = getDevelopmentProposal(childProposalNumber);
        Budget childBudget = getFinalOrLatestChildBudget(childProposal).getBudget();
        ObjectUtils.materializeAllSubObjects(childBudget);
        int hc1 = computeHierarchyHashCode(childProposal, childBudget);
        int hc2 = childProposal.getHierarchyLastSyncHashCode();
        return hc1 == hc2;
    }

    protected void setInitialPi(DevelopmentProposal hierarchy, DevelopmentProposal child) {
        ProposalPerson pi = child.getPrincipalInvestigator();
        if (pi != null) {
            int index = hierarchy.getProposalPersons().indexOf(pi);
            if (index > -1) {
                hierarchy.getProposalPerson(index).setProposalPersonRoleId(Constants.PRINCIPAL_INVESTIGATOR_ROLE);
                //hierarchy.getProposalPerson(index).setHierarchyProposalNumber(null);
            }
        }
    }

    protected List<BudgetDocument<DevelopmentProposal>> getHierarchyBudgets(DevelopmentProposal hierarchyProposal)
            throws ProposalHierarchyException {
        List<BudgetDocument<DevelopmentProposal>> hierarchyBudgets = new ArrayList<BudgetDocument<DevelopmentProposal>>();

        try {
            for (BudgetDocumentVersion budgetDocumentVersion : hierarchyProposal.getProposalDocument()
                    .getBudgetDocumentVersions()) {
                String budgetDocumentNumber = budgetDocumentVersion.getBudgetVersionOverview().getDocumentNumber();
                hierarchyBudgets.add((BudgetDocument<DevelopmentProposal>) documentService
                        .getByDocumentHeaderId(budgetDocumentNumber));
            }
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }

        return hierarchyBudgets;
    }

    protected BudgetDocument<DevelopmentProposal> getHierarchyBudget(DevelopmentProposal hierarchyProposal)
            throws ProposalHierarchyException {
        String budgetDocumentNumber = hierarchyProposal.getProposalDocument().getBudgetDocumentVersions().get(0)
                .getBudgetVersionOverview().getDocumentNumber();
        BudgetDocument<DevelopmentProposal> budgetDocument = null;
        try {
            budgetDocument = (BudgetDocument<DevelopmentProposal>) documentService
                    .getByDocumentHeaderId(budgetDocumentNumber);
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }
        return budgetDocument;//.getBudget();
    }

    protected BudgetDocument<DevelopmentProposal> getFinalOrLatestChildBudget(DevelopmentProposal childProposal)
            throws ProposalHierarchyException {
        String budgetDocumentNumber = null;
        for (BudgetDocumentVersion version : childProposal.getProposalDocument().getBudgetDocumentVersions()) {
            budgetDocumentNumber = version.getDocumentNumber();
            if (version.getBudgetVersionOverview().isFinalVersionFlag()) {
                break;
            }
        }
        BudgetDocument<DevelopmentProposal> budgetDocument = null;
        try {
            budgetDocument = (BudgetDocument<DevelopmentProposal>) documentService
                    .getByDocumentHeaderId(budgetDocumentNumber);
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }
        return budgetDocument;//.getBudget();
    }

    protected void initializeBudget(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal)
            throws ProposalHierarchyException {
        BudgetDocument<DevelopmentProposal> parentBudgetDoc = getHierarchyBudget(hierarchyProposal);
        Budget parentBudget = parentBudgetDoc.getBudget();
        BudgetDocument<DevelopmentProposal> childBudgetDocument = getFinalOrLatestChildBudget(childProposal);
        Budget childBudget = childBudgetDocument.getBudget();
        BudgetPeriod parentPeriod, childPeriod;
        for (int i = 0; i < childBudget.getBudgetPeriods().size(); i++) {
            parentPeriod = parentBudget.getBudgetPeriod(i);
            childPeriod = childBudget.getBudgetPeriod(i);
            parentPeriod.setStartDate(childPeriod.getStartDate());
            parentPeriod.setEndDate(childPeriod.getEndDate());
            parentPeriod.setBudgetPeriod(childPeriod.getBudgetPeriod());
        }

        parentBudget.setCostSharingAmount(new BudgetDecimal(0));
        parentBudget.setTotalCost(new BudgetDecimal(0));
        parentBudget.setTotalDirectCost(new BudgetDecimal(0));
        parentBudget.setTotalIndirectCost(new BudgetDecimal(0));
        parentBudget.setUnderrecoveryAmount(new BudgetDecimal(0));

        parentBudget.setOhRateClassCode(childBudget.getOhRateClassCode());
        parentBudget.setOhRateTypeCode(childBudget.getOhRateTypeCode());
        parentBudget.setUrRateClassCode(childBudget.getUrRateClassCode());
        try {
            documentService.saveDocument(parentBudgetDoc);
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }
    }

    public ProposalHierarchyErrorDto validateChildBudgetPeriods(DevelopmentProposal hierarchyProposal,
            DevelopmentProposal childProposal, boolean allowEndDateChange) throws ProposalHierarchyException {
        BudgetDocument<DevelopmentProposal> parentBudgetDoc = getHierarchyBudget(hierarchyProposal);
        Budget parentBudget = parentBudgetDoc.getBudget();
        BudgetDocument<DevelopmentProposal> childBudgetDocument = getFinalOrLatestChildBudget(childProposal);
        Budget childBudget = childBudgetDocument.getBudget();

        ProposalHierarchyErrorDto retval = null;
        // check that child budget starts on one of the budget period starts
        int correspondingStart = getCorrespondingParentPeriod(parentBudget, childBudget);
        if (correspondingStart == -1) {
            retval = new ProposalHierarchyErrorDto(ERROR_BUDGET_START_DATE_INCONSISTENT,
                    childProposal.getProposalNumber());
        }
        // check that child budget periods map to parent periods
        else {
            List<BudgetPeriod> parentPeriods = parentBudget.getBudgetPeriods();
            List<BudgetPeriod> childPeriods = childBudget.getBudgetPeriods();
            BudgetPeriod parentPeriod, childPeriod;
            int i;
            int j;
            for (i = correspondingStart, j = 0; i < parentPeriods.size() && j < childPeriods.size(); i++, j++) {
                parentPeriod = parentPeriods.get(i);
                childPeriod = childPeriods.get(j);
                if (!parentPeriod.getStartDate().equals(childPeriod.getStartDate())
                        || !parentPeriod.getEndDate().equals(childPeriod.getEndDate())) {
                    retval = new ProposalHierarchyErrorDto(ERROR_BUDGET_PERIOD_DURATION_INCONSISTENT,
                            childProposal.getProposalNumber());
                    break;
                }
            }
            if (retval == null && !allowEndDateChange && (j < childPeriods.size() || childProposal
                    .getRequestedEndDateInitial().after(hierarchyProposal.getRequestedEndDateInitial()))) {
                retval = new ProposalHierarchyErrorDto(QUESTION_EXTEND_PROJECT_DATE_CONFIRM,
                        childProposal.getProposalNumber());
            }
        }

        return retval;
    }

    protected int getCorrespondingParentPeriod(Budget parentBudget, Budget childBudget) {
        int correspondingStart = -1;

        // using start date of first period as start date and end date of last period
        // as end because budget start and end are not particularly reliable
        Date childStart = childBudget.getBudgetPeriod(0).getStartDate();
        Date parentStart = parentBudget.getBudgetPeriod(0).getStartDate();
        Date parentEnd = parentBudget.getBudgetPeriod(parentBudget.getBudgetPeriods().size() - 1).getEndDate();
        // check that child budget starts somewhere during parent budget
        if (childStart.compareTo(parentStart) >= 0 && childStart.compareTo(parentEnd) < 0) {
            // check that child budget starts on one of the budget period starts
            List<BudgetPeriod> parentPeriods = parentBudget.getBudgetPeriods();
            for (int i = 0; i < parentPeriods.size(); i++) {
                if (childStart.equals(parentPeriods.get(i).getStartDate())) {
                    correspondingStart = i;
                    break;
                }
            }
        }
        return correspondingStart;
    }

    protected void removeChildElements(DevelopmentProposal parentProposal, Budget parentBudget,
            String childProposalNumber) {
        if (this.proposalPersonExtendedAttributesToDelete == null) {
            this.proposalPersonExtendedAttributesToDelete = new ArrayList<ProposalPersonExtendedAttributes>();
        }
        List<ProposalPerson> persons = parentProposal.getProposalPersons();
        for (int i = persons.size() - 1; i >= 0; i--) {
            if (StringUtils.equals(childProposalNumber, persons.get(i).getHierarchyProposalNumber())) {
                if (persons.get(i).getProposalPersonExtendedAttributes() != null) {
                    this.proposalPersonExtendedAttributesToDelete
                            .add(persons.get(i).getProposalPersonExtendedAttributes());
                }
                persons.remove(i);
            }
        }

        List<PropScienceKeyword> keywords = parentProposal.getPropScienceKeywords();
        for (int i = keywords.size() - 1; i >= 0; i--) {
            if (StringUtils.equals(childProposalNumber, keywords.get(i).getHierarchyProposalNumber())) {
                keywords.remove(i);
            }
        }

        List<ProposalSpecialReview> reviews = parentProposal.getPropSpecialReviews();
        for (int i = reviews.size() - 1; i >= 0; i--) {
            if (StringUtils.equals(childProposalNumber, reviews.get(i).getHierarchyProposalNumber())) {
                reviews.remove(i);
            }
        }

        List<Narrative> narratives = parentProposal.getNarratives();
        for (int i = narratives.size() - 1; i >= 0; i--) {
            if (StringUtils.equals(childProposalNumber, narratives.get(i).getHierarchyProposalNumber())) {
                businessObjectService.delete(narratives.remove(i));
            }
        }

        List<BudgetSubAwards> subAwards = parentBudget.getBudgetSubAwards();
        for (int i = subAwards.size() - 1; i >= 0; i--) {
            if (StringUtils.equals(childProposalNumber, subAwards.get(i).getHierarchyProposalNumber())) {
                subAwards.remove(i);
            }
        }

        List<BudgetProjectIncome> projectIncomes = parentBudget.getBudgetProjectIncomes();
        for (int i = projectIncomes.size() - 1; i >= 0; i--) {
            if (StringUtils.equals(childProposalNumber, projectIncomes.get(i).getHierarchyProposalNumber())) {
                projectIncomes.remove(i);
            }
        }

        Map<String, String> fieldValues = new HashMap<String, String>();
        fieldValues.put("hierarchyProposalNumber", childProposalNumber);
        businessObjectService.deleteMatching(BudgetCostShare.class, fieldValues);
        businessObjectService.deleteMatching(BudgetUnrecoveredFandA.class, fieldValues);

        BudgetPersonnelBudgetService budgetPersonnelBudgetService = KraServiceLocator
                .getService(BudgetPersonnelBudgetService.class);
        List<BudgetPeriod> periods = parentBudget.getBudgetPeriods();
        List<BudgetLineItem> lineItems;
        List<BudgetPersonnelDetails> personnelDetailsList;
        BudgetPeriod period = null;
        BudgetLineItem lineItem = null;
        for (int i = periods.size() - 1; i >= 0; i--) {
            period = periods.get(i);
            lineItems = period.getBudgetLineItems();
            for (int j = lineItems.size() - 1; j >= 0; j--) {
                lineItem = lineItems.get(j);
                if (StringUtils.equals(childProposalNumber, lineItem.getHierarchyProposalNumber())) {
                    personnelDetailsList = lineItem.getBudgetPersonnelDetailsList();
                    for (int k = personnelDetailsList.size() - 1; k >= 0; k--) {
                        budgetPersonnelBudgetService.deleteBudgetPersonnelDetails(parentBudget, i, j, k);
                    }
                    lineItems.remove(j);
                    parentBudget.setBudgetLineItemDeleted(true);
                }
            }
            if (lineItems.isEmpty() && periods.indexOf(period) == periods.size() - 1
                    && periods.indexOf(period) > 0) {
                periods.remove(period);
            }
        }
        parentBudget.setEndDate(periods.get(periods.size() - 1).getEndDate());
        List<BudgetPerson> budgetPersons = parentBudget.getBudgetPersons();
        for (int i = budgetPersons.size() - 1; i >= 0; i--) {
            if (StringUtils.equals(childProposalNumber, budgetPersons.get(i).getHierarchyProposalNumber())) {
                budgetPersonnelBudgetService.deleteBudgetPersonnelDetailsForPerson(parentBudget,
                        budgetPersons.get(i));
                budgetPersons.remove(i);
            }
        }
    }

    protected void prepareHierarchySync(DevelopmentProposal hierarchyProposal) {
        prepareHierarchySync(hierarchyProposal.getProposalDocument());
    }

    protected void prepareHierarchySync(ProposalDevelopmentDocument pdDoc) {
        pdDoc.refreshReferenceObject("documentNextvalues");
    }

    protected void finalizeHierarchySync(ProposalDevelopmentDocument pdDoc) throws ProposalHierarchyException {
        pdDoc.refreshBudgetDocumentVersions();
        try {
            documentService.saveDocument(pdDoc);
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(e);
        }
    }

    protected void finalizeHierarchySync(DevelopmentProposal hierarchyProposal) throws ProposalHierarchyException {
        if (proposalPersonExtendedAttributesToDelete != null
                && !proposalPersonExtendedAttributesToDelete.isEmpty()) {
            businessObjectService.delete(proposalPersonExtendedAttributesToDelete);
            proposalPersonExtendedAttributesToDelete.clear();
        }
        businessObjectService.save(hierarchyProposal.getProposalDocument().getDocumentNextvalues());
        businessObjectService.save(hierarchyProposal);
        /**
         * now we need to save any properal person extended attribute objects
         */
        for (ProposalPerson person : hierarchyProposal.getProposalPersons()) {
            if (person.getProposalPersonExtendedAttributes() != null) {
                businessObjectService.save(person.getProposalPersonExtendedAttributes());
            }
        }
    }

    protected void copyInitialAttachments(DevelopmentProposal srcProposal, DevelopmentProposal destProposal) {
        String instituteNarrativeTypeGroup = parameterService.getParameterValue(ProposalDevelopmentDocument.class,
                PARAMETER_NAME_INSTITUTE_NARRATIVE_TYPE_GROUP);

        ProposalPersonBiography destPropPersonBio;
        ProposalPerson srcPerson = null;
        ProposalPerson destPerson = null;
        for (ProposalPersonBiography srcPropPersonBio : srcProposal.getPropPersonBios()) {
            for (ProposalPerson person : srcProposal.getProposalPersons()) {
                if (person.getProposalPersonNumber().equals(srcPropPersonBio.getProposalPersonNumber())) {
                    srcPerson = person;
                    break;
                }
            }
            for (ProposalPerson person : destProposal.getProposalPersons()) {
                if (person.equals(srcPerson)) {
                    destPerson = person;
                    break;
                }
            }
            loadBioContent(srcPropPersonBio);
            destPropPersonBio = (ProposalPersonBiography) ObjectUtils.deepCopy(srcPropPersonBio);
            destPropPersonBio.setProposalPersonNumber(destPerson.getProposalPersonNumber());
            destPropPersonBio.setPersonId(destPerson.getPersonId());
            destPropPersonBio.setRolodexId(destPerson.getRolodexId());
            propPersonBioService.addProposalPersonBiography(destProposal.getProposalDocument(), destPropPersonBio);
        }

        Narrative destNarrative;
        for (Narrative srcNarrative : srcProposal.getNarratives()) {
            if (StringUtils.equalsIgnoreCase(srcNarrative.getNarrativeType().getAllowMultiple(), "N")
                    && !srcProposal.getInstituteAttachments().contains(srcNarrative)
                    && !StringUtils.equalsIgnoreCase(srcNarrative.getNarrativeType().getNarrativeTypeGroup(),
                            instituteNarrativeTypeGroup)) {
                loadAttachmentContent(srcNarrative);
                destNarrative = (Narrative) ObjectUtils.deepCopy(srcNarrative);
                destNarrative.setModuleStatusCode("I");
                narrativeService.addNarrative(destProposal.getProposalDocument(), destNarrative);
            }
        }
    }

    protected void loadAttachmentContent(Narrative narrative) {
        Map<String, String> primaryKey = new HashMap<String, String>();
        primaryKey.put("proposalNumber", narrative.getProposalNumber());
        primaryKey.put("moduleNumber", narrative.getModuleNumber() + "");
        NarrativeAttachment attachment = (NarrativeAttachment) businessObjectService
                .findByPrimaryKey(NarrativeAttachment.class, primaryKey);
        narrative.getNarrativeAttachmentList().clear();
        narrative.getNarrativeAttachmentList().add(attachment);
    }

    protected void loadBioContent(ProposalPersonBiography bio) {
        Map<String, String> primaryKey = new HashMap<String, String>();
        primaryKey.put("proposalNumber", bio.getProposalNumber());
        primaryKey.put("biographyNumber", bio.getBiographyNumber() + "");
        primaryKey.put("proposalPersonNumber", bio.getProposalPersonNumber() + "");
        ProposalPersonBiographyAttachment attachment = (ProposalPersonBiographyAttachment) businessObjectService
                .findByPrimaryKey(ProposalPersonBiographyAttachment.class, primaryKey);
        bio.getPersonnelAttachmentList().clear();
        bio.getPersonnelAttachmentList().add(attachment);
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#getHierarchyChildRouteStatus(java.lang.String, java.lang.String)
     */
    public String getHierarchyChildRouteStatus(String oldStatus, String newStatus) {

        LOG.info(String.format("Route status change %s:%s", oldStatus, newStatus));

        String retCd = null;
        if (StringUtils.equals(newStatus, KEWConstants.ROUTE_HEADER_ENROUTE_CD)
                && (StringUtils.equals(oldStatus, KEWConstants.ROUTE_HEADER_INITIATED_CD)
                        || StringUtils.equals(oldStatus, KEWConstants.ROUTE_HEADER_SAVED_CD)
                        || StringUtils.equals(KEWConstants.ROUTE_HEADER_ENROUTE_CD, oldStatus))) {
            retCd = renderMessage(HIERARCHY_CHILD_ENROUTE_APPSTATUS);
        } else if (StringUtils.equals(newStatus, KEWConstants.ROUTE_HEADER_FINAL_CD)) {
            retCd = renderMessage(HIERARCHY_CHILD_FINAL_APPSTATUS);
        } else if (StringUtils.equals(newStatus, KEWConstants.ROUTE_HEADER_DISAPPROVED_CD)) {
            retCd = renderMessage(HIERARCHY_CHILD_DISAPPROVE_APPSTATUS);
        } else if (StringUtils.equals(newStatus, KEWConstants.ROUTE_HEADER_CANCEL_CD)) {
            retCd = renderMessage(HIERARCHY_CHILD_CANCEL_APPSTATUS);
        } else {
            LOG.warn(String.format("Do not know how to calculate hierarchy child status for %s to %s", oldStatus,
                    newStatus));
        }
        if (LOG.isDebugEnabled())
            LOG.debug(String.format("Route status for children:%s", retCd));
        return retCd;
    }

    /**
     * Creates a hash of the data pertinent to a hierarchy for comparison during hierarchy syncing. 
     */
    protected int computeHierarchyHashCode(DevelopmentProposal proposal, Budget budget) {
        int prime = 31;
        int result = 1;
        KraServiceLocator.getService(BudgetCalculationService.class).calculateBudget(budget);
        KraServiceLocator.getService(BudgetCalculationService.class).calculateBudgetSummaryTotals(budget);
        for (ProposalPerson person : proposal.getProposalPersons()) {
            result = prime * result + person.hashCode();
        }
        for (Narrative narrative : proposal.getNarratives()) {
            result = prime * result + narrative.hierarchyHashCode();
        }
        for (PropScienceKeyword keyword : proposal.getPropScienceKeywords()) {
            result = prime * result + keyword.getScienceKeywordCode().hashCode();
        }
        for (ProposalSpecialReview review : proposal.getPropSpecialReviews()) {
            result = prime * result + review.hierarchyHashCode();
        }
        result = prime * result + budget.getBudgetSummaryTotals().hashCode();
        return result;
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#getChildProposalDevelopmentDocuments(java.lang.String)
     */
    public List<ProposalDevelopmentDocument> getChildProposalDevelopmentDocuments(String parentProposalNumber)
            throws ProposalHierarchyException {

        List<ProposalDevelopmentDocument> outList = new ArrayList<ProposalDevelopmentDocument>();
        for (DevelopmentProposal child : getHierarchyChildren(parentProposalNumber)) {
            try {
                outList.add((ProposalDevelopmentDocument) documentService
                        .getByDocumentHeaderId(child.getProposalDocument().getDocumentNumber()));
            } catch (WorkflowException e) {
                LOG.error(String.format("Could not find document for child proposal number %s",
                        parentProposalNumber, child.getProposalNumber()), e);
                throw new ProposalHierarchyException(
                        String.format("Could not find document for child proposal number %s", parentProposalNumber,
                                child.getProposalNumber()),
                        e);
            }

        }

        return outList;

    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#getHierarchyChildren(java.lang.String)
     */
    public List<DevelopmentProposal> getHierarchyChildren(String parentProposalNumber) {
        List<DevelopmentProposal> children = new ArrayList<DevelopmentProposal>();
        for (String childProposalNumber : proposalHierarchyDao
                .getHierarchyChildProposalNumbers(parentProposalNumber)) {
            children.add(getDevelopmentProposal(childProposalNumber));
        }
        return children;
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#getParentWorkflowStatus(org.kuali.kra.proposaldevelopment.bo.DevelopmentProposal)
     */
    public KualiWorkflowDocument getParentWorkflowDocument(ProposalDevelopmentDocument child)
            throws ProposalHierarchyException {
        return getParentDocument(child).getDocumentHeader().getWorkflowDocument();
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#getParentDocument(org.kuali.kra.proposaldevelopment.document.ProposalDevelopmentDocument)
     */
    public ProposalDevelopmentDocument getParentDocument(ProposalDevelopmentDocument child)
            throws ProposalHierarchyException {
        try {
            DevelopmentProposal parentProposal = getHierarchy(
                    child.getDevelopmentProposal().getHierarchyParentProposalNumber());
            String parentDocumentNumber = parentProposal.getProposalDocument().getDocumentNumber();
            return (ProposalDevelopmentDocument) documentService.getByDocumentHeaderId(parentDocumentNumber);
        } catch (WorkflowException e) {
            LOG.error("Workflow exception thrown getting hierarchy routing status.", e);
            throw new ProposalHierarchyException(
                    String.format("Could not lookup hierarchy workflow status for child:%s",
                            child.getDocumentHeader().getDocumentNumber()),
                    e);
        }
    }

    /**
     * Reject a proposal by sending it to the first node ( as named by PROPOSALDEVELOPMENTDOCUMENT_KEW_INITIAL_NODE_NAME )
     * @param proposalDoc The ProposalDevelopmentDocument that should be rejected.
     * @param appDocStatus the application status to set in the workflow document.
     * @param principalId the principal we are rejecting the document as.
     * @param appDocStatus the application document status to apply ( does not apply if null )
     * @throws WorkflowException
     */
    protected void rejectProposal(ProposalDevelopmentDocument proposalDoc, String reason, String principalId,
            String appDocStatus) throws WorkflowException {
        kraDocumentRejectionService.reject(proposalDoc, reason, principalId, appDocStatus);
    }

    /**
     * Reject an entire proposal hierarchy.  This works by first rejecting each child, and then rejecting the parent.
     * @param hierarchyParent The hierarchy to reject
     * @param reason the reason to be applied to the annotation field.  The reason will be pre-pended with static text indicating if it was a child or the parent.
     * @param principalName the name of the principal that is rejecting the document.  
     * @throws ProposalHierarchyException If hierarchyParent is not a hierarchy, or there was a problem rejecting one of the documents.
     */
    protected void rejectProposalHierarchy(ProposalDevelopmentDocument hierarchyParent, String reason,
            String principalId) throws ProposalHierarchyException {

        //1. reject the parent.
        try {
            rejectProposal(hierarchyParent, renderMessage(PROPOSAL_ROUTING_REJECTED_ANNOTATION, reason),
                    principalId, renderMessage(HIERARCHY_REJECTED_APPSTATUS));
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(
                    String.format("WorkflowException encountered rejecting proposal hierarchy parent %s",
                            hierarchyParent.getDevelopmentProposal().getProposalNumber()),
                    e);
        }

        //2. Try to reject all of the children.
        for (ProposalDevelopmentDocument child : getChildProposalDevelopmentDocuments(
                hierarchyParent.getDevelopmentProposal().getProposalNumber())) {
            try {
                rejectProposal(child, renderMessage(HIERARCHY_ROUTING_PARENT_REJECTED_ANNOTATION, reason),
                        identityManagementService.getPrincipalByPrincipalName(KNSConstants.SYSTEM_USER)
                                .getPrincipalId(),
                        renderMessage(HIERARCHY_CHILD_REJECTED_APPSTATUS));
            } catch (WorkflowException e) {
                throw new ProposalHierarchyException(
                        String.format("WorkflowException encountered rejecting child document %s",
                                child.getDevelopmentProposal().getProposalNumber()),
                        e);
            }
        }

    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#rejectProposalDevelopmentDocument(java.lang.String, java.lang.String)
     */
    public void rejectProposalDevelopmentDocument(String proposalNumber, String reason, String principalName,
            FormFile rejectFile) throws WorkflowException, ProposalHierarchyException, IOException {
        DevelopmentProposal pbo = getDevelopmentProposal(proposalNumber);
        ProposalDevelopmentDocument pDoc = (ProposalDevelopmentDocument) documentService
                .getByDocumentHeaderId(pbo.getProposalDocument().getDocumentNumber());
        if (!pbo.isInHierarchy()) {
            rejectProposal(pDoc, renderMessage(PROPOSAL_ROUTING_REJECTED_ANNOTATION, reason), principalName,
                    renderMessage(HIERARCHY_REJECTED_APPSTATUS));
        } else if (pbo.isParent()) {
            rejectProposalHierarchy(pDoc, reason, principalName);
        } else {
            //it is a child or in some unknown state, either way we do not support rejecting it.
            throw new UnsupportedOperationException(
                    String.format("Cannot reject proposal %s it is a hierarchy child or ", proposalNumber));
        }

        if (rejectFile != null && rejectFile.getFileData().length > 0) {
            Narrative narrative = new Narrative();
            narrative.setFileName(rejectFile.getFileName());
            narrative.setComments(reason);
            narrative.setNarrativeFile(rejectFile);
            narrative.setNarrativeTypeCode("18");
            Map keys = new HashMap();
            keys.put("NARRATIVE_STATUS_CODE", "C");
            NarrativeStatus status = (NarrativeStatus) this.businessObjectService
                    .findByPrimaryKey(NarrativeStatus.class, keys);
            narrative.setNarrativeStatus(status);
            narrative.setModuleStatusCode(status.getNarrativeStatusCode());
            narrative.setModuleTitle("Proposal rejection attachment.");
            narrative.setContactName(GlobalVariables.getUserSession().getPrincipalName());
            narrative.setPhoneNumber(GlobalVariables.getUserSession().getPerson().getPhoneNumber());
            narrative.setEmailAddress(GlobalVariables.getUserSession().getPerson().getEmailAddress());
            pDoc.getDevelopmentProposal().addInstituteAttachment(narrative);
            this.businessObjectService.save(pDoc);
        }

    }

    /**
     * Based on the hierarchy, and route status change of the parent, calculate what route action should be taken on the children.
     * @param hierarchy the heirarchy being routed
     * @param dto the route status change information.
     * @return The route action to take on the children.
     */
    protected String calculateChildRouteStatus(ProposalDevelopmentDocument hierarchy,
            DocumentRouteStatusChangeDTO dto) {

        String parentOldStatus = dto.getOldRouteStatus();
        String parentNewStatus = dto.getNewRouteStatus();
        String newChildStatusTarget = "";

        if (StringUtils.equals(parentOldStatus, KEWConstants.ROUTE_HEADER_INITIATED_CD)) {
            // nothing to do here.
        } else if (StringUtils.equals(parentOldStatus, KEWConstants.ROUTE_HEADER_SAVED_CD)) {
            // previous status was saved
            newChildStatusTarget = parentNewStatus;
            if (StringUtils.equals(parentNewStatus, KEWConstants.ROUTE_HEADER_ENROUTE_CD)) {
                // nothing to do
            } else if (StringUtils.equals(parentNewStatus, KEWConstants.ROUTE_HEADER_CANCEL_CD)) {
                // nothing to do.
            } else {
                throw new UnsupportedOperationException(String.format(
                        "Do not know how to handle children of hierarchy for route status chnage from %s to %s",
                        parentOldStatus, parentNewStatus));
            }
        } else if (StringUtils.equals(parentOldStatus, KEWConstants.ROUTE_HEADER_ENROUTE_CD)) {
            // we are moving from enroute to some other state.

            if (StringUtils.equals(parentNewStatus, KEWConstants.ROUTE_HEADER_CANCEL_CD)
                    || StringUtils.equals(parentNewStatus, KEWConstants.ROUTE_HEADER_DISAPPROVED_CD)) {
                newChildStatusTarget = parentNewStatus;
            } else if (StringUtils.equals(parentNewStatus, KEWConstants.ROUTE_HEADER_APPROVED_CD)) {
                // nothing to do here, wait for the document to go final.
            } else if (StringUtils.equals(parentNewStatus, KEWConstants.ROUTE_HEADER_ENROUTE_CD)) {
                //special case, document has been rejected and being approved again to simulate entry into workflow.
                //this value will trigger an approve.
                newChildStatusTarget = KEWConstants.ROUTE_HEADER_ENROUTE_CD;
            } else {
                throw new UnsupportedOperationException(String.format(
                        "Do not know how to handle children of hierarchy for route status chnage from %s to %s",
                        parentOldStatus, parentNewStatus));
            }

        } else if (StringUtils.equals(parentOldStatus, KEWConstants.ROUTE_HEADER_APPROVED_CD)) {
            // nothing to do here.
        } else if (StringUtils.equals(parentOldStatus, KEWConstants.ROUTE_HEADER_PROCESSED_CD)) {
            if (StringUtils.equals(parentNewStatus, KEWConstants.ROUTE_HEADER_FINAL_CD)) {
                newChildStatusTarget = parentNewStatus;
            } else {
                throw new UnsupportedOperationException(String.format(
                        "Do not know how to handle children of hierarchy for route status chnage from %s to %s",
                        parentOldStatus, parentNewStatus));
            }
        } else {
            throw new UnsupportedOperationException(String.format(
                    "Do not know how to handle children of hierarchy for route status chnage from %s to %s",
                    parentOldStatus, parentNewStatus));
        }
        return newChildStatusTarget;
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#routeHierarchyChildren(org.kuali.kra.proposaldevelopment.document.ProposalDevelopmentDocument, org.kuali.rice.kew.dto.DocumentRouteStatusChangeDTO, java.lang.String)
     */
    public void routeHierarchyChildren(ProposalDevelopmentDocument proposalDevelopmentDocument,
            DocumentRouteStatusChangeDTO dto) throws ProposalHierarchyException {

        String childStatusTarget = calculateChildRouteStatus(proposalDevelopmentDocument, dto);
        WorkflowDocument workdoc;
        ProposalDevelopmentDocument child = null;
        try {
            LOG.info(IdentityManagementService.class);
            for (ProposalDevelopmentDocument c : getChildProposalDevelopmentDocuments(
                    proposalDevelopmentDocument.getDevelopmentProposal().getProposalNumber())) {
                child = c;
                if (!StringUtils.equals("", childStatusTarget)) {

                    if (StringUtils.equals(KEWConstants.ROUTE_HEADER_ENROUTE_CD, childStatusTarget)) {
                        //The user currently must initially route the child documents in order for them to hold in the system users action list.

                        workdoc = new WorkflowDocument(
                                child.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId(),
                                child.getDocumentHeader().getWorkflowDocument().getRouteHeaderId());
                        workdoc.updateAppDocStatus(
                                getHierarchyChildRouteStatus(dto.getOldRouteStatus(), dto.getNewRouteStatus()));
                        if (!workdoc.stateIsEnroute()) {
                            workdoc.routeDocument(renderMessage(HIERARCHY_ROUTING_PARENT_SUBMITTED_ANNOTATION));

                        } else {
                            //this means the status change is actually in the form of an approve action on a document that was moved back to the initial node.
                            //we need to do an approval.
                            workdoc.approve(renderMessage(HIERARCHY_ROUTING_PARENT_RESUBMITTED_ANNOTATION));
                            workdoc.updateAppDocStatus(renderMessage(HIERARCHY_CHILD_ENROUTE_APPSTATUS));
                        }

                    } else {
                        workdoc = new WorkflowDocument(
                                identityManagementService.getPrincipalByPrincipalName(KNSConstants.SYSTEM_USER)
                                        .getPrincipalId(),
                                child.getDocumentHeader().getWorkflowDocument().getRouteHeaderId());
                        workdoc.updateAppDocStatus(
                                getHierarchyChildRouteStatus(dto.getOldRouteStatus(), dto.getNewRouteStatus()));

                        if (StringUtils.equals(KEWConstants.ROUTE_HEADER_CANCEL_CD, childStatusTarget)) {
                            workdoc.cancel(renderMessage(HIERARCHY_ROUTING_PARENT_CANCELLED_ANNOTATION));
                            workdoc.updateAppDocStatus(renderMessage(HIERARCHY_CHILD_CANCEL_APPSTATUS));

                        } else if (StringUtils.equals(KEWConstants.ROUTE_HEADER_FINAL_CD, childStatusTarget)) {
                            workdoc.approve(renderMessage(HIERARCHY_ROUTING_PARENT_APPROVED_ANNOTATION));
                            workdoc.updateAppDocStatus(renderMessage(HIERARCHY_CHILD_FINAL_APPSTATUS));

                        } else if (StringUtils.equals(KEWConstants.ROUTE_HEADER_DISAPPROVED_CD,
                                childStatusTarget)) {
                            workdoc.disapprove(renderMessage(HIERARCHY_ROUTING_PARENT_DISAPPROVED_ANNOTATION));
                            workdoc.updateAppDocStatus(renderMessage(HIERARCHY_CHILD_DISAPPROVE_APPSTATUS));
                        } else {
                            throw new UnsupportedOperationException(String
                                    .format("Do not know how to handle new child status of %s", childStatusTarget));
                        }

                    }
                }
            }
        } catch (WorkflowException we) {
            throw new ProposalHierarchyException(String.format(
                    "WorkflowException encountrered while attempting to route child proposal %s ( document #%s ) of proposal hierarchy %s ( document #%s )",
                    child.getDevelopmentProposal().getProposalNumber(), child.getDocumentNumber(),
                    proposalDevelopmentDocument.getDevelopmentProposal().getProposalNumber(),
                    proposalDevelopmentDocument.getDocumentNumber()), we);
        }

    }

    public void calculateAndSetProposalAppDocStatus(ProposalDevelopmentDocument doc,
            DocumentRouteStatusChangeDTO dto) throws ProposalHierarchyException {
        String principalId = GlobalVariables.getUserSession().getPrincipalId();
        if (StringUtils.equals(dto.getNewRouteStatus(), KEWConstants.ROUTE_HEADER_ENROUTE_CD)) {
            updateAppDocStatus(doc, principalId, HIERARCHY_ENROUTE_APPSTATUS);
        } else if (StringUtils.equals(dto.getNewRouteStatus(), KEWConstants.ROUTE_HEADER_CANCEL_CD)) {
            updateAppDocStatus(doc, principalId, HIERARCHY_CANCEL_APPSTATUS);
        } else if (StringUtils.equals(dto.getNewRouteStatus(), KEWConstants.ROUTE_HEADER_DISAPPROVED_CD)) {
            updateAppDocStatus(doc, principalId, HIERARCHY_DISAPPROVE_APPSTATUS);
        } else if (StringUtils.equals(dto.getNewRouteStatus(), KEWConstants.ROUTE_HEADER_FINAL_CD)) {
            updateAppDocStatus(doc, principalId, HIERARCHY_FINAL_APPSTATUS);
        } else if (StringUtils.equals(dto.getNewRouteStatus(), KEWConstants.ROUTE_HEADER_PROCESSED_CD)) {
            updateAppDocStatus(doc, principalId, HIERARCHY_PROCESSED_APPSTATUS);
        }
    }

    public void updateAppDocStatus(ProposalDevelopmentDocument doc, String principalId, String newStatus)
            throws ProposalHierarchyException {
        try {
            WorkflowDocument wdoc = new WorkflowDocument(principalId,
                    doc.getDocumentHeader().getWorkflowDocument().getRouteHeaderId());
            wdoc.updateAppDocStatus(renderMessage(newStatus));
        } catch (WorkflowException e) {
            throw new ProposalHierarchyException(String.format(
                    "WorkflowException encountrered while attempting to update App Doc Status of proposal %s ( document #%s )",
                    doc.getDevelopmentProposal().getProposalNumber(), doc.getDocumentNumber()), e);
        }
    }

    public boolean allChildBudgetsAreComplete(String parentProposalNumber) {
        boolean retval = true;
        String completeCode = parameterService.getParameterValue(BudgetDocument.class,
                Constants.BUDGET_STATUS_COMPLETE_CODE);
        for (ProposalBudgetStatus status : proposalHierarchyDao
                .getHierarchyChildProposalBudgetStatuses(parentProposalNumber)) {
            if (!StringUtils.equalsIgnoreCase(completeCode, status.getBudgetStatusCode())) {
                retval = false;
                break;
            }
        }
        return retval;
    }

    protected boolean rolesAreSimilar(ProposalPerson person1, ProposalPerson person2) {
        boolean isInvestigator1 = StringUtils.equals(person1.getProposalPersonRoleId(),
                Constants.PRINCIPAL_INVESTIGATOR_ROLE)
                || StringUtils.equals(person1.getProposalPersonRoleId(), Constants.CO_INVESTIGATOR_ROLE);
        boolean isInvestigator2 = StringUtils.equals(person2.getProposalPersonRoleId(),
                Constants.PRINCIPAL_INVESTIGATOR_ROLE)
                || StringUtils.equals(person2.getProposalPersonRoleId(), Constants.CO_INVESTIGATOR_ROLE);
        return isInvestigator1 == isInvestigator2;
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#getHierarchyPersonnelSummaries(java.lang.String)
     */
    public List<HierarchyPersonnelSummary> getHierarchyPersonnelSummaries(String parentProposalNumber)
            throws ProposalHierarchyException {
        List<HierarchyPersonnelSummary> summaries = new ArrayList<HierarchyPersonnelSummary>();

        List<String> proposalNumbers = new ArrayList<String>();
        proposalNumbers.addAll(proposalHierarchyDao.getHierarchyChildProposalNumbers(parentProposalNumber));
        Collections.sort(proposalNumbers);

        for (String proposalNumber : proposalNumbers) {
            HierarchyPersonnelSummary summary = new HierarchyPersonnelSummary();

            DevelopmentProposal childProposal = getDevelopmentProposal(proposalNumber);
            List<Budget> hierarchyBudgets = new ArrayList<Budget>();
            for (BudgetDocument<DevelopmentProposal> hierarchyBudgetDocument : getHierarchyBudgets(childProposal)) {
                hierarchyBudgets.add(hierarchyBudgetDocument.getBudget());
            }
            Collections.sort(hierarchyBudgets);

            summary.setProposalNumber(proposalNumber);
            summary.setHierarchyBudgets(hierarchyBudgets);
            summaries.add(summary);
        }

        return summaries;
    }

    /**
     * @see org.kuali.kra.proposaldevelopment.hierarchy.service.ProposalHierarchyService#getHierarchyProposalSummaries(java.lang.String)
     */
    public List<HierarchyProposalSummary> getHierarchyProposalSummaries(String proposalNumber)
            throws ProposalHierarchyException {
        DevelopmentProposal proposal = getDevelopmentProposal(proposalNumber);
        List<HierarchyProposalSummary> summaries = new ArrayList<HierarchyProposalSummary>();

        List<String> proposalNumbers = new ArrayList<String>();
        if (proposal.isParent()) {
            proposalNumbers.add(proposalNumber);
        } else if (proposal.isChild()) {
            proposalNumbers.add(proposal.getHierarchyParentProposalNumber());
        } else {
            throw new ProposalHierarchyException("Proposal " + proposalNumber + " is not a member of a hierarchy.");
        }
        proposalNumbers.addAll(proposalHierarchyDao.getHierarchyChildProposalNumbers(proposalNumbers.get(0)));

        HierarchyProposalSummary summary;
        for (String number : proposalNumbers) {
            summary = new HierarchyProposalSummary();
            summary.setProposalNumber(number);
            if (!StringUtils.equals(number, proposalNumbers.get(0))) {
                summary.setSynced(isSynchronized(number));
            }
            summaries.add(summary);
        }
        return summaries;
    }

    public boolean validateRemovePermissions(DevelopmentProposal childProposal, String principalId) {
        boolean valid = true;
        valid &= kraAuthorizationService.hasPermission(principalId, childProposal.getProposalDocument(),
                PermissionConstants.MAINTAIN_PROPOSAL_HIERARCHY);
        try {
            valid &= kraAuthorizationService.hasPermission(principalId,
                    getHierarchy(childProposal.getHierarchyParentProposalNumber()).getProposalDocument(),
                    PermissionConstants.MAINTAIN_PROPOSAL_HIERARCHY);
        } catch (ProposalHierarchyException e) {
            valid = false;
        }
        return valid;
    }

    protected String renderMessage(String key, String... params) {
        String msg = configurationService.getPropertyString(key);
        for (int i = 0; i < params.length; i++) {
            msg = replace(msg, "{" + i + "}", params[i]);
        }
        return msg;

    }

    public KraDocumentRejectionService getKraDocumentRejectionService() {
        return kraDocumentRejectionService;
    }

    public void setKraDocumentRejectionService(KraDocumentRejectionService kraDocumentRejectionService) {
        this.kraDocumentRejectionService = kraDocumentRejectionService;
    }

}