org.kuali.coeus.propdev.impl.budget.hierarchy.ProposalBudgetHierarchyServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.coeus.propdev.impl.budget.hierarchy.ProposalBudgetHierarchyServiceImpl.java

Source

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

import static org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyKeyConstants.ERROR_BUDGET_PERIOD_DURATION_INCONSISTENT;
import static org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyKeyConstants.ERROR_BUDGET_START_DATE_INCONSISTENT;
import static org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyKeyConstants.PARAMETER_NAME_DIRECT_COST_ELEMENT;
import static org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyKeyConstants.PARAMETER_NAME_INDIRECT_COST_ELEMENT;
import static org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyKeyConstants.QUESTION_EXTEND_PROJECT_DATE_CONFIRM;

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

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.kuali.coeus.common.budget.framework.calculator.BudgetCalculationService;
import org.kuali.coeus.common.budget.framework.core.Budget;
import org.kuali.coeus.common.budget.framework.core.CostElement;
import org.kuali.coeus.common.budget.framework.distribution.BudgetCostShare;
import org.kuali.coeus.common.budget.framework.distribution.BudgetUnrecoveredFandA;
import org.kuali.coeus.common.budget.framework.income.BudgetProjectIncome;
import org.kuali.coeus.common.budget.framework.nonpersonnel.BudgetLineItem;
import org.kuali.coeus.common.budget.framework.nonpersonnel.BudgetLineItemCalculatedAmount;
import org.kuali.coeus.common.budget.framework.period.BudgetPeriod;
import org.kuali.coeus.common.budget.framework.personnel.BudgetPerson;
import org.kuali.coeus.common.budget.framework.personnel.BudgetPersonnelCalculatedAmount;
import org.kuali.coeus.common.budget.framework.personnel.BudgetPersonnelDetails;
import org.kuali.coeus.common.budget.framework.summary.BudgetSummaryService;
import org.kuali.coeus.propdev.impl.budget.ProposalBudgetService;
import org.kuali.coeus.propdev.impl.budget.ProposalDevelopmentBudgetExt;
import org.kuali.coeus.propdev.impl.budget.subaward.BudgetSubAwardAttachment;
import org.kuali.coeus.propdev.impl.budget.subaward.BudgetSubAwardFiles;
import org.kuali.coeus.propdev.impl.budget.subaward.BudgetSubAwards;
import org.kuali.coeus.propdev.impl.core.DevelopmentProposal;
import org.kuali.coeus.propdev.impl.hierarchy.HierarchyBudgetTypeConstants;
import org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyDao;
import org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyErrorWarningDto;
import org.kuali.coeus.propdev.impl.hierarchy.ProposalHierarchyException;
import org.kuali.coeus.sys.api.model.ScaleTwoDecimal;
import org.kuali.coeus.sys.framework.model.KcDataObject;
import org.kuali.kra.infrastructure.Constants;
import org.kuali.rice.core.api.criteria.PredicateFactory;
import org.kuali.rice.core.api.criteria.QueryByCriteria;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.data.CopyOption;
import org.kuali.rice.krad.data.DataObjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component("proposalBudgetHierarchyService")
@Transactional
public class ProposalBudgetHierarchyServiceImpl implements ProposalBudgetHierarchyService {

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

    @Autowired
    @Qualifier("dataObjectService")
    private DataObjectService dataObjectService;

    @Autowired
    @Qualifier("parameterService")
    private ParameterService parameterService;

    @Autowired
    @Qualifier("proposalBudgetService")
    private ProposalBudgetService budgetService;

    @Autowired
    @Qualifier("budgetSummaryService")
    private BudgetSummaryService budgetSummaryService;

    @Autowired
    @Qualifier("budgetCalculationService")
    private BudgetCalculationService budgetCalculationService;

    @Autowired
    @Qualifier("proposalHierarchyDao")
    private ProposalHierarchyDao proposalHierarchyDao;

    public void persistProposalHierarchyBudget(DevelopmentProposal hierarchyProposal) {
        dataObjectService.save(getHierarchyBudget(hierarchyProposal));
    }

    public void synchronizeChildBudget(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal,
            List<BudgetPeriod> oldBudgetPeriods) {
        synchronizeChildBudget(hierarchyProposal, childProposal, getSyncableBudget(childProposal),
                oldBudgetPeriods);
    }

    public void synchronizeChildBudget(DevelopmentProposal hierarchyProposal, ProposalDevelopmentBudgetExt budget) {
        synchronizeChildBudget(hierarchyProposal, budget.getBudgetParent(), budget, budget.getBudgetPeriods());
    }

    public void synchronizeAllChildBudgets(DevelopmentProposal hierarchyProposal) {
        List<BudgetPeriod> oldBudgetPeriods = getHierarchyBudget(hierarchyProposal).getBudgetPeriods();
        for (DevelopmentProposal childProposal : proposalHierarchyDao
                .getHierarchyChildProposals(hierarchyProposal.getProposalNumber())) {
            ProposalDevelopmentBudgetExt budget = getSyncableBudget(childProposal);
            synchronizeChildBudget(hierarchyProposal, childProposal, budget, oldBudgetPeriods);
            dataObjectService.save(budget);
            dataObjectService.save(childProposal);
        }
    }

    protected void synchronizeChildBudget(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal,
            ProposalDevelopmentBudgetExt childBudget, List<BudgetPeriod> oldBudgetPeriods) {
        String hierarchyBudgetTypeCode = childProposal.getHierarchyBudgetType();
        String childProposalNumber = childProposal.getProposalNumber();
        ProposalDevelopmentBudgetExt parentBudget = getHierarchyBudget(hierarchyProposal);

        removeChildBudgetElements(hierarchyProposal, parentBudget, childProposal.getProposalNumber());

        boolean isOriginatingChildBudget = StringUtils.equals(childProposal.getProposalNumber(),
                hierarchyProposal.getHierarchyOriginatingChildProposalNumber());
        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) deepCopy(person);
                newPerson.setBudget(parentBudget);
                newPerson.setPersonSequenceNumber(parentBudget.getNextValue(Constants.PERSON_SEQUENCE_NUMBER));
                newPerson.setBudgetId(parentBudget.getBudgetId());
                newPerson.setHierarchyProposalNumber(childProposalNumber);
                newPerson.setHierarchyProposal(childProposal);
                newPerson.setVersionNumber(null);
                parentBudget.addBudgetPerson(newPerson);
                personMap.put(person.getPersonSequenceNumber(), newPerson);
            }

            BudgetSubAwards newSubAwards;
            Map<Integer, BudgetSubAwards> subAwardMap = new HashMap<>();
            for (BudgetSubAwards childSubAwards : childBudget.getBudgetSubAwards()) {
                //pre-fetch all lob objects from the subawards as JPA/Eclipselink doesn't do this for lazy loaded lobs
                //during the deep-copy operation below
                childSubAwards.getSubAwardXmlFileData();
                for (BudgetSubAwardAttachment origAttachment : childSubAwards.getBudgetSubAwardAttachments()) {
                    origAttachment.getData();
                }
                for (BudgetSubAwardFiles budgetSubAwardFiles : childSubAwards.getBudgetSubAwardFiles()) {
                    budgetSubAwardFiles.getSubAwardXmlFileData();
                }
                newSubAwards = (BudgetSubAwards) deepCopy(childSubAwards);
                newSubAwards.setBudget(parentBudget);
                newSubAwards.setBudgetId(parentBudget.getBudgetId());
                newSubAwards.setBudgetVersionNumber(parentBudget.getBudgetVersionNumber());
                newSubAwards.setSubAwardNumber(parentBudget.getNextValue("subAwardNumber") != null
                        ? parentBudget.getNextValue("subAwardNumber")
                        : 1);
                newSubAwards.setVersionNumber(null);
                newSubAwards.setHierarchyProposalNumber(childProposalNumber);
                for (BudgetSubAwardAttachment attachment : newSubAwards.getBudgetSubAwardAttachments()) {
                    attachment.setBudgetSubAward(newSubAwards);
                    attachment.setId(null);
                    attachment.setVersionNumber(null);
                }
                for (BudgetSubAwardFiles files : newSubAwards.getBudgetSubAwardFiles()) {
                    files.setBudgetSubAward(newSubAwards);
                    files.setVersionNumber(null);
                }
                parentBudget.getBudgetSubAwards().add(newSubAwards);
                subAwardMap.put(childSubAwards.getSubAwardNumber(), newSubAwards);
            }

            int parentStartPeriod = getCorrespondingParentPeriod(oldBudgetPeriods, 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) deepCopy(costShare);
                    newCostShare.setBudget(parentBudget);
                    newCostShare.setBudgetId(budgetId);
                    newCostShare.setDocumentComponentId(
                            parentBudget.getNextValue(newCostShare.getDocumentComponentIdKey()));
                    newCostShare.setObjectId(null);
                    newCostShare.setVersionNumber(null);
                    newCostShare.setHierarchyProposalNumber(childProposalNumber);
                    newCostShare.setHiddenInHierarchy(true);
                    parentBudget.add(newCostShare);
                }
            }

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

            for (int i = 0; i < childPeriods.size(); i++) {
                childPeriod = childPeriods.get(i);
                parentPeriod = findOrCreateMatchingPeriod(childPeriod, parentBudget);
                budgetPeriodId = parentPeriod.getBudgetPeriodId();
                budgetPeriod = parentPeriod.getBudgetPeriod();
                BudgetLineItem parentLineItem;
                Integer lineItemNumber;

                if (StringUtils.equals(hierarchyBudgetTypeCode, HierarchyBudgetTypeConstants.SubBudget.code())) {
                    for (BudgetLineItem childLineItem : childPeriod.getBudgetLineItems()) {
                        parentLineItem = (BudgetLineItem) deepCopy(childLineItem);
                        lineItemNumber = parentBudget.getNextValue(Constants.BUDGET_LINEITEM_NUMBER);

                        parentLineItem.setHierarchyProposalNumber(childProposalNumber);
                        parentLineItem.setHierarchyProposal(childProposal);

                        parentLineItem.setBudgetLineItemId(null);
                        parentLineItem.setBudgetPeriodId(parentPeriod.getBudgetPeriodId());
                        parentLineItem.setBudgetPeriod(parentPeriod.getBudgetPeriod());
                        parentLineItem.setBudgetPeriodBO(parentPeriod);
                        parentLineItem.setBudget(parentBudget);
                        parentLineItem.setBudgetId(parentBudget.getBudgetId());
                        parentLineItem.setBudgetPeriod(budgetPeriod);
                        parentLineItem.setLineItemNumber(lineItemNumber);
                        parentLineItem.setVersionNumber(null);
                        parentLineItem.setObjectId(null);

                        if (parentLineItem.getSubAwardNumber() != null) {
                            BudgetSubAwards subAward = subAwardMap.get(childLineItem.getSubAwardNumber());
                            parentLineItem.setSubAwardNumber(subAward.getSubAwardNumber());
                            parentLineItem.setBudgetSubAward(subAward);
                        }

                        for (BudgetLineItemCalculatedAmount calAmt : parentLineItem
                                .getBudgetLineItemCalculatedAmounts()) {
                            calAmt.setBudgetLineItem(parentLineItem);
                            calAmt.setBudgetLineItemCalculatedAmountId(null);
                            calAmt.setBudgetLineItemId(null);
                            calAmt.setBudgetId(budgetId);

                            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.setBudgetLineItem(parentLineItem);
                            details.setBudgetPerson(budgetPerson);
                            details.setJobCode(budgetPerson.getJobCode());
                            details.setPersonId(budgetPerson.getPersonRolodexTbnId());
                            details.setPersonSequenceNumber(budgetPerson.getPersonSequenceNumber());
                            details.setPersonNumber(parentBudget.getNextValue(Constants.BUDGET_PERSON_LINE_NUMBER));

                            details.setBudgetPersonnelLineItemId(null);
                            details.setBudget(parentBudget);
                            details.setBudgetId(budgetId);
                            details.setBudgetPeriod(budgetPeriod);
                            details.setLineItemNumber(lineItemNumber);
                            details.setVersionNumber(null);
                            details.setObjectId(null);

                            for (BudgetPersonnelCalculatedAmount calAmt : details
                                    .getBudgetPersonnelCalculatedAmounts()) {
                                calAmt.setBudgetPersonnelCalculatedAmountId(null);
                                calAmt.setBudgetPersonnelLineItem(details);
                                calAmt.setBudgetId(budgetId);

                                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.getParameterValueAsString(Budget.class,
                            PARAMETER_NAME_DIRECT_COST_ELEMENT);
                    String indirectCostElement = parameterService.getParameterValueAsString(Budget.class,
                            PARAMETER_NAME_INDIRECT_COST_ELEMENT);

                    if (childPeriod.getTotalIndirectCost().isNonZero()) {
                        costElement = (CostElement) dataObjectService.findUnique(CostElement.class,
                                QueryByCriteria.Builder.forAttribute("costElement", indirectCostElement).build());
                        parentLineItem = parentBudget.getNewBudgetLineItem();
                        parentLineItem.setLineItemDescription(childProposalNumber);
                        parentLineItem.setStartDate(parentPeriod.getStartDate());
                        parentLineItem.setEndDate(parentPeriod.getEndDate());
                        parentLineItem.setHierarchyProposalNumber(childProposalNumber);
                        parentLineItem.setHierarchyProposal(childProposal);
                        parentLineItem.setBudget(parentBudget);
                        parentLineItem.setBudgetId(parentBudget.getBudgetId());
                        parentLineItem.setBudgetPeriodId(parentPeriod.getBudgetPeriodId());
                        parentLineItem.setBudgetPeriod(parentPeriod.getBudgetPeriod());
                        parentLineItem.setBudgetPeriodBO(parentPeriod);
                        parentLineItem.setBudgetPeriod(budgetPeriod);
                        parentLineItem.setVersionNumber(null);
                        lineItemNumber = parentBudget.getNextValue(Constants.BUDGET_LINEITEM_NUMBER);
                        parentLineItem.setLineItemNumber(lineItemNumber);
                        parentLineItem.setLineItemSequence(lineItemNumber);
                        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()) {
                        costElement = (CostElement) dataObjectService.findUnique(CostElement.class,
                                QueryByCriteria.Builder.forAttribute("costElement", directCostElement).build());
                        parentLineItem = parentBudget.getNewBudgetLineItem();
                        parentLineItem.setLineItemDescription(childProposalNumber);
                        parentLineItem.setStartDate(parentPeriod.getStartDate());
                        parentLineItem.setEndDate(parentPeriod.getEndDate());
                        parentLineItem.setHierarchyProposalNumber(childProposalNumber);
                        parentLineItem.setHierarchyProposal(childProposal);
                        parentLineItem.setBudget(parentBudget);
                        parentLineItem.setBudgetId(parentBudget.getBudgetId());
                        parentLineItem.setBudgetPeriodId(parentPeriod.getBudgetPeriodId());
                        parentLineItem.setBudgetPeriod(parentPeriod.getBudgetPeriod());
                        parentLineItem.setBudgetPeriodBO(parentPeriod);
                        parentLineItem.setBudgetPeriod(budgetPeriod);
                        parentLineItem.setVersionNumber(null);
                        lineItemNumber = parentBudget.getNextValue(Constants.BUDGET_LINEITEM_NUMBER);
                        parentLineItem.setLineItemNumber(lineItemNumber);
                        parentLineItem.setLineItemSequence(lineItemNumber);
                        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);
        }

        budgetService.recalculateBudget(parentBudget);
        childProposal.setLastSyncedBudget(childBudget);
        childBudget.setHierarchyLastSyncHashCode(computeHierarchyHashCode(childBudget));
    }

    protected BudgetPeriod findOrCreateMatchingPeriod(BudgetPeriod childPeriod, Budget parentBudget) {
        int priorPeriodIndex = 0;
        int index = 0;
        for (BudgetPeriod period : parentBudget.getBudgetPeriods()) {
            if (period.getStartDate().equals(childPeriod.getStartDate())
                    && period.getEndDate().equals(childPeriod.getEndDate())) {
                return period;
            } else if (period.getEndDate().before(childPeriod.getStartDate())) {
                priorPeriodIndex = index;
            }
            index++;
        }
        //otherwise add it after most recent budget period which should be valid based on previous validations.
        BudgetPeriod result = new BudgetPeriod();
        result.setStartDate(childPeriod.getStartDate());
        result.setEndDate(childPeriod.getEndDate());
        result.setBudgetPeriod(parentBudget.getBudgetPeriods().get(priorPeriodIndex).getBudgetPeriod() + 1);
        result.setBudget(parentBudget);

        getBudgetSummaryService().addBudgetPeriod(parentBudget, result);
        return result;
    }

    public void removeChildBudgetElements(DevelopmentProposal parentProposal,
            ProposalDevelopmentBudgetExt parentBudget, String childProposalNumber) {
        QueryByCriteria query = QueryByCriteria.Builder.fromPredicates(
                PredicateFactory.equal("hierarchyProposalNumber", childProposalNumber),
                PredicateFactory.equal("budgetId", parentBudget.getBudgetId()));
        dataObjectService.deleteMatching(BudgetProjectIncome.class, query);
        dataObjectService.deleteMatching(BudgetCostShare.class, query);
        dataObjectService.deleteMatching(BudgetUnrecoveredFandA.class, query);

        for (Iterator<BudgetSubAwards> iter = parentBudget.getBudgetSubAwards().iterator(); iter.hasNext();) {
            BudgetSubAwards subAward = iter.next();
            if (StringUtils.equals(childProposalNumber, subAward.getHierarchyProposalNumber())) {
                List<BudgetLineItem> lineItems = getDataObjectService()
                        .findMatching(BudgetLineItem.class,
                                QueryByCriteria.Builder.fromPredicates(
                                        PredicateFactory.equal("budgetId", subAward.getBudgetId()),
                                        PredicateFactory.equal("subAwardNumber", subAward.getSubAwardNumber())))
                        .getResults();
                for (BudgetPeriod period : parentBudget.getBudgetPeriods()) {
                    period.getBudgetLineItems().removeAll(lineItems);
                }
                iter.remove();
            }
        }

        for (Iterator<BudgetPerson> iter = parentBudget.getBudgetPersons().iterator(); iter.hasNext();) {
            BudgetPerson person = iter.next();
            if (StringUtils.equals(childProposalNumber, person.getHierarchyProposalNumber())) {
                iter.remove();
            }
        }
        List<BudgetPeriod> periodsToDelete = new ArrayList<>();
        for (int i = parentBudget.getBudgetPeriods().size() - 1; i >= 0; i--) {
            boolean deletePeriods = false;
            BudgetPeriod period = parentBudget.getBudgetPeriod(i);
            for (Iterator<BudgetLineItem> lineItemIter = period.getBudgetLineItems().iterator(); lineItemIter
                    .hasNext();) {
                BudgetLineItem lineItem = lineItemIter.next();
                if (StringUtils.equals(childProposalNumber, lineItem.getHierarchyProposalNumber())) {
                    deletePeriods = true;
                    lineItemIter.remove();
                }
            }
            if (deletePeriods) {
                if (parentBudget.getBudgetPeriods().get(i).getBudgetLineItems().size() == 0 && i > 0) {
                    periodsToDelete.add(parentBudget.getBudgetPeriods().get(i));
                }
            }
        }
        for (BudgetPeriod period : periodsToDelete) {
            if (period.getBudgetModular() != null) {
                getDataObjectService().delete(period.getBudgetModular());
            }
            parentBudget.getBudgetPeriods().remove(period);
        }

        parentBudget.setEndDate(
                parentBudget.getBudgetPeriods().get(parentBudget.getBudgetPeriods().size() - 1).getEndDate());
    }

    public void initializeBudget(DevelopmentProposal hierarchyProposal, DevelopmentProposal childProposal)
            throws ProposalHierarchyException {
        budgetService.addBudgetVersion(hierarchyProposal.getProposalDocument(), "Hierarchy Budget", null);

        ProposalDevelopmentBudgetExt parentBudget = getHierarchyBudget(hierarchyProposal);

        Budget childBudget = getSyncableBudget(childProposal);
        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 ScaleTwoDecimal(0));
        parentBudget.setTotalCost(new ScaleTwoDecimal(0));
        parentBudget.setTotalDirectCost(new ScaleTwoDecimal(0));
        parentBudget.setTotalIndirectCost(new ScaleTwoDecimal(0));
        parentBudget.setUnderrecoveryAmount(new ScaleTwoDecimal(0));

        parentBudget.setOhRateClassCode(childBudget.getOhRateClassCode());
        parentBudget.setOhRateTypeCode(childBudget.getOhRateTypeCode());
        parentBudget.setUrRateClassCode(childBudget.getUrRateClassCode());
        dataObjectService.save(parentBudget);
    }

    public List<ProposalHierarchyErrorWarningDto> validateChildBudgetPeriods(DevelopmentProposal hierarchyProposal,
            DevelopmentProposal childProposal, boolean allowEndDateChange) throws ProposalHierarchyException {
        ProposalDevelopmentBudgetExt parentBudget = getHierarchyBudget(hierarchyProposal);
        Budget childBudget = getSyncableBudget(childProposal);

        List<ProposalHierarchyErrorWarningDto> retval = new ArrayList<>();
        // check that child budget starts on one of the budget period starts
        int correspondingStart = getCorrespondingParentPeriod(parentBudget.getBudgetPeriods(), childBudget);
        if (correspondingStart == -1) {
            retval.add(new ProposalHierarchyErrorWarningDto(ERROR_BUDGET_START_DATE_INCONSISTENT, Boolean.TRUE,
                    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.add(new ProposalHierarchyErrorWarningDto(ERROR_BUDGET_PERIOD_DURATION_INCONSISTENT,
                            Boolean.TRUE, childProposal.getProposalNumber()));
                    break;
                }
            }
            if (retval == null && !allowEndDateChange && (j < childPeriods.size() || childProposal
                    .getRequestedEndDateInitial().after(hierarchyProposal.getRequestedEndDateInitial()))) {
                retval.add(new ProposalHierarchyErrorWarningDto(QUESTION_EXTEND_PROJECT_DATE_CONFIRM, Boolean.TRUE,
                        childProposal.getProposalNumber()));
            }
        }

        return retval;
    }

    public ProposalDevelopmentBudgetExt getHierarchyBudget(DevelopmentProposal hierarchyProposal)
            throws ProposalHierarchyException {
        if (!hierarchyProposal.getBudgets().isEmpty()) {
            return hierarchyProposal.getBudgets().get(0);
        } else {
            return null;
        }
    }

    public ProposalDevelopmentBudgetExt getSyncableBudget(DevelopmentProposal childProposal)
            throws ProposalHierarchyException {
        if (childProposal.getFinalBudget() == null) {
            return childProposal.getLatestBudget();
        } else {
            return childProposal.getFinalBudget();
        }
    }

    protected int getCorrespondingParentPeriod(List<BudgetPeriod> oldBudgetPeriods, 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 = oldBudgetPeriods.get(0).getStartDate();
        Date parentEnd = oldBudgetPeriods.get(oldBudgetPeriods.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 = oldBudgetPeriods;
            for (int i = 0; i < parentPeriods.size(); i++) {
                if (childStart.equals(parentPeriods.get(i).getStartDate())) {
                    correspondingStart = i;
                    break;
                }
            }
        }
        return correspondingStart;
    }

    /**
     * Creates a hash of the data pertinent to a hierarchy for comparison during hierarchy syncing. 
     */
    public int computeHierarchyHashCode(Budget budget) {
        int prime = 31;
        int result = 1;
        budgetCalculationService.calculateBudget(budget);
        budgetCalculationService.calculateBudgetSummaryTotals(budget);
        result = prime * result + budget.getBudgetSummaryTotals().hashCode();
        return result;
    }

    public List<ProposalDevelopmentBudgetExt> getHierarchyBudgets(DevelopmentProposal hierarchyProposal)
            throws ProposalHierarchyException {
        List<ProposalDevelopmentBudgetExt> hierarchyBudgets = new ArrayList<>();
        for (ProposalDevelopmentBudgetExt budgetVersion : hierarchyProposal.getProposalDocument()
                .getDevelopmentProposal().getBudgets()) {
            hierarchyBudgets.add(budgetVersion);
        }

        return hierarchyBudgets;
    }

    protected KcDataObject deepCopy(KcDataObject oldObject) {
        return getDataObjectService().copyInstance(oldObject, CopyOption.RESET_OBJECT_ID,
                CopyOption.RESET_PK_FIELDS, CopyOption.RESET_VERSION_NUMBER);
    }

    public DataObjectService getDataObjectService() {
        return dataObjectService;
    }

    public void setDataObjectService(DataObjectService dataObjectService) {
        this.dataObjectService = dataObjectService;
    }

    public ParameterService getParameterService() {
        return parameterService;
    }

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

    public BudgetSummaryService getBudgetSummaryService() {
        return budgetSummaryService;
    }

    public void setBudgetSummaryService(BudgetSummaryService budgetSummaryService) {
        this.budgetSummaryService = budgetSummaryService;
    }

    public ProposalBudgetService getBudgetService() {
        return budgetService;
    }

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