edu.bu.kuali.kra.award.sapintegration.SapIntegrationServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for edu.bu.kuali.kra.award.sapintegration.SapIntegrationServiceImpl.java

Source

/*
 * Copyright (c) 2014. Boston University
 *
 * 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 edu.bu.kuali.kra.award.sapintegration;

import java.io.Serializable;
import org.kuali.coeus.common.framework.rolodex.Rolodex;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.kuali.kra.infrastructure.Constants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.ws.BindingProvider;
import org.kuali.coeus.common.api.rolodex.RolodexContract;
import edu.bu.kuali.kra.infrastructure.BUConstants;
import org.kuali.kra.award.budget.AwardBudgetExt;
import org.kuali.kra.award.budget.AwardBudgetService;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.log4j.Logger;
import org.kuali.coeus.common.api.rolodex.RolodexService;
import org.kuali.coeus.common.budget.framework.version.BudgetVersionOverview;
import org.kuali.coeus.common.framework.org.Organization;
import org.kuali.coeus.common.framework.sponsor.Sponsor;
import org.kuali.coeus.common.framework.unit.Unit;
import org.kuali.coeus.common.framework.unit.UnitService;
import org.kuali.coeus.common.framework.version.history.VersionHistory;
import org.kuali.coeus.common.framework.version.history.VersionHistoryService;
import org.kuali.coeus.common.framework.version.sequence.associate.SequenceAssociate;
import org.kuali.coeus.sys.api.model.ScaleTwoDecimal;
import org.kuali.coeus.sys.framework.service.KcServiceLocator;
import org.kuali.coeus.sys.framework.validation.ErrorReporter;
import org.kuali.kra.award.AwardAmountInfoService;
import org.kuali.kra.award.awardhierarchy.AwardHierarchy;
import org.kuali.kra.award.awardhierarchy.AwardHierarchyService;
import org.kuali.kra.award.budget.AwardBudgetVersionOverviewExt;
import org.kuali.kra.award.commitments.AwardCostShare;
import org.kuali.kra.award.contacts.AwardPerson;
import org.kuali.kra.award.contacts.AwardPersonUnit;
import org.kuali.kra.award.contacts.AwardPersonUnitCreditSplit;
import org.kuali.kra.award.contacts.AwardSponsorContact;
import org.kuali.kra.award.contacts.AwardUnitContact;
import org.kuali.kra.award.document.AwardDocument;
import org.kuali.kra.award.home.Award;
import org.kuali.kra.award.home.AwardAmountInfo;
import org.kuali.kra.award.home.AwardSponsorTerm;
import org.kuali.kra.award.home.ValuableItem;
import org.kuali.kra.award.home.approvedsubawards.AwardApprovedSubaward;
import org.kuali.kra.award.paymentreports.awardreports.AwardReportTerm;
import org.kuali.kra.award.paymentreports.paymentschedule.AwardPaymentSchedule;

import org.kuali.kra.timeandmoney.AwardHierarchyNode;
import org.kuali.kra.timeandmoney.document.TimeAndMoneyDocument;
import org.kuali.rice.core.api.config.property.ConfigContext;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;

import org.kuali.rice.krad.util.GlobalVariables;

import org.springframework.beans.factory.InitializingBean;

import com.sap.document.sap.rfc.functions.BAPIRET2;
import com.sap.document.sap.rfc.functions.GMSPPROGRAMFMBT;
import com.sap.document.sap.rfc.functions.GMSPPROGRAMFMBTTT;
import com.sap.document.sap.rfc.functions.ObjectFactory;
import com.sap.document.sap.rfc.functions.ZBAPI0035HEADER;
import com.sap.document.sap.rfc.functions.ZBAPI0035HEADERADD;
import com.sap.document.sap.rfc.functions.ZBAPI0035RESPONSIBLE;
import com.sap.document.sap.rfc.functions.ZBAPI0035RESPONSIBLET;
import com.sap.document.sap.rfc.functions.ZBAPI0035SPONSOREDOBJECTS;
import com.sap.document.sap.rfc.functions.ZBAPI0035SPONSOREDOBJECTST;
import com.sap.document.sap.rfc.functions.ZFIGMSPRESPONSIBLETABKCRM;
import com.sap.document.sap.rfc.functions.ZFIGRANTDATA;
import com.sap.document.sap.rfc.functions.ZFIKCRMSPXWALK;
import com.sap.document.sap.rfc.functions.ZGMBILLINGPLANSTRUCTURE;
import com.sap.document.sap.rfc.functions.ZGMFACREDIT;
import com.sap.document.sap.rfc.functions.ZGMFACREDITT;
import com.sap.document.sap.rfc.functions.ZGMGRANTSTRUCTURE;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACE;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACE.BILLINGPLAN;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACE.SPONSOR;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACE.SPONSOREDPROGRAMS;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACE.SPONSOREDPROGRAMSGRP;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACEResponse;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACEResponse.GRANTMESSAGES;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACEResponse.RETURN;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACEResponse.SPONSOREDPROGRAMSMESSAGES;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACEResponse.SPONSORMESSAGES;
import com.sap.document.sap.rfc.functions.ZGMKCRMINTERFACEResponse.SPXWALKT;
import com.sap.document.sap.rfc.functions.ZGMSPONSORSTRUCTURE;
import com.sap.document.sap.rfc.functions.ZGMSPPROGRAMGRPSTRUCTURE;
import com.sap.document.sap.rfc.functions.ZGMSPPROGRAMSTRUCTURE;
import com.sap.document.sap.rfc.functions.ZGMSPRESPONSIBLEKCRM;
import com.sap.document.sap.rfc.functions.ZGRANTMESSAGES;
import com.sap.document.sap.rfc.functions.ZSPONSORMESSAGES;
import com.sap.document.sap.rfc.functions.ZSPPROGRAMMESSAGES;
import org.kuali.coeus.common.framework.unit.admin.UnitAdministrator;
import edu.bu.kuali.kra.award.home.AwardExtension;
import edu.bu.kuali.kra.bo.AwardTransmission;
import edu.bu.kuali.kra.bo.AwardTransmissionChild;
import edu.bu.sap.kcrm.SIKCRMPROCESSOUTBOUND;
import edu.bu.sap.kcrm.SIKCRMPROCESSOUTBOUNDService;

/**
 * <p>A service implementation which provides integration with Boston University's SAP implementation.
 * Provides two primary operations, one for performing validation against incoming
 * award hierarchy data, and other for transmitting award hierarchy data to SAP.
 * <p/>
 * <p>It is intended that invocation of the {@link #transmit(SapTransmission)}
 * method, will invoke validation prior to performing submission.  The additional
 * {@link #validate(SapTransmission)} method is provided to allow for identifying
 * and reporting on possible validation issues prior to submission of the
 * SAP transmission.
 *
 * @author Eric Westfall (ewestfal@gmail.com)
 */

public class SapIntegrationServiceImpl implements SapIntegrationService, InitializingBean {
    private Logger LOG = Logger.getLogger(SapIntegrationServiceImpl.class);

    private static final String SAP_SERVICE_WSDL_URL_PARAM = "sapService.wsdl.url";
    private static final String SAP_SERVICE_URL_PARAM = "sapService.url";
    private static final String SAP_SERVICE_USERNAME_PARAM = "sapService.username";
    private static final String SAP_SERVICE_PASSWORD_PARAM = "sapService.password";
    private static final String SAP_SERVICE_CONNECTION_TIMEOUT_PARAM = "sapService.connectionTimeout";
    private static final String SAP_SERVICE_RECEIVE_TIMEOUT_PARAM = "sapService.receiveTimeout";

    private static final Integer FEDERAL_CODE = 1;
    private static final Integer NON_FEDERAL_CODE = 2;

    private static final Integer AWARD_TYPE_CODE_SUBAWARD = 6;

    private static final String MANUAL_BASIS_OF_PAYMENT = "2";
    private static final String RRB_BASIS_OF_PAYMENT = "1";
    private static final String MILESTONE_BASIS_OF_PAYMENT = "4";

    private static final String DHHS_LOC_MATHOD_OF_PAYMENT = "6";

    private static final String INTERFACE_NEW = "N";
    private static final String INTERFACE_UPDATE = "U";

    private static final String ARRA_FUNDING_INFORMATION = "ARRA";

    private static final String PAYMENT_INVOICES_REPORT_CLASS_CODE = "6";
    private static final String DEFAULT_SPONSOR_TYPE_FOR_BILLING_PARTNER = "13";

    private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd";

    private static final String ERROR_MESSAGE_TYPE = "E";
    private static final String WARNING_MESSAGE_TYPE = "W";

    // BU Constants
    public static final int AWARD_TRANSACTION_TYPE_NO_COST_EXTENSION = 6;
    public static final int AWARD_TRANSACTION_TYPE_ADMINISTRATION_CHANGE = 10;
    private AwardAmountInfoService awardAmountInfoService;
    private UnitService unitService;
    private AwardHierarchyService awardHierarchyService;
    private BusinessObjectService businessObjectService;
    private BudgetRateAndBaseService budgetRateAndBaseService;
    private RolodexService rolodexService;
    private DocumentService documentService;
    private ParameterService parameterService;
    private ObjectFactory objectFactory;
    private AwardBudgetService AwardBudgetService;

    private ErrorReporter errorReporter;

    public SapIntegrationServiceImpl() {
        this.objectFactory = new ObjectFactory();
    }

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

    protected ParameterService getParameterService() {
        return parameterService;
    }

    public void afterPropertiesSet() throws Exception {
        if (awardAmountInfoService == null) {
            throw new IllegalStateException("The awardAmountInfoService was not injected.");
        }
        if (unitService == null) {
            throw new IllegalStateException("The unitService was not injected.");
        }
        if (awardHierarchyService == null) {
            throw new IllegalStateException("The awardHierarchyService was not injected.");
        }
        if (businessObjectService == null) {
            throw new IllegalStateException("The businessObjectService was not injected.");
        }
        if (budgetRateAndBaseService == null) {
            throw new IllegalStateException("The budgetRateAndBaseService was not injected.");
        }
        if (rolodexService == null) {
            throw new IllegalStateException("The rolodexService was not injected.");
        }

    }

    public String getTransmitXml(SapTransmission transmission) {
        ZGMKCRMINTERFACE sapInterface = constructSapInterface(transmission, new ArrayList<Long>());
        StringWriter stringWriter = new StringWriter();
        try {
            JAXBContext context = JAXBContext.newInstance(ZGMKCRMINTERFACE.class);
            Marshaller marshaller = context.createMarshaller();

            marshaller.marshal(sapInterface, stringWriter);
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
        //        LOG.info(stringWriter.toString()); //TODO mkousheh delete once in production
        return stringWriter.toString();
    }

    public SapTransmissionResponse transmit(SapTransmission transmission) {
        ValidationResults results = validate(transmission);
        if (!results.calculateSuccess()) {
            return SapTransmissionResponse.validationFailure("Failed to validate transmission successfully.");
        }

        return executeSapService(transmission);
    }

    protected ZGMKCRMINTERFACE constructSapInterface(SapTransmission transmission,
            List<Long> interfacedSponsoredProgramIds) {
        ZGMGRANTSTRUCTURE grant = constructGrant(transmission.getAward());

        ZBAPI0035SPONSOREDOBJECTST sponsoredObjects = constructSponsoredObjects(transmission);
        grant.setSPONSOREDOBJECTS(sponsoredObjects);

        SPONSOR sponsors = objectFactory.createZGMKCRMINTERFACESPONSOR();
        BILLINGPLAN billingPlans = objectFactory.createZGMKCRMINTERFACEBILLINGPLAN();

        ZGMSPONSORSTRUCTURE sponsor = constructSponsor(transmission.getAward());
        sponsors.getItem().add(sponsor);

        // if a billing plan exists on the parent award, send it as a sponsor
        AwardSponsorContact billingPartner = determineBillingPartner(transmission.getAward());
        if (billingPartner != null) {
            ZGMSPONSORSTRUCTURE billingPartnerSponsor = constructSponsorFromBillingPartner(transmission.getAward(),
                    billingPartner);
            sponsors.getItem().add(billingPartnerSponsor);
        }

        ZGMBILLINGPLANSTRUCTURE billingPlan = constructBillingPlan(transmission.getAward());
        if (billingPlan != null) {
            billingPlans.getItem().add(billingPlan);
        }

        SPONSOREDPROGRAMS sponsoredPrograms = objectFactory.createZGMKCRMINTERFACESPONSOREDPROGRAMS();
        for (Award childAward : transmission.getChildAwards()) {
            if (determineCostShareMemoMatch(childAward) == null) {
                if (!"group".equalsIgnoreCase(((AwardExtension) childAward.getExtension()).getChildType())) {
                    ZGMSPPROGRAMSTRUCTURE sponsoredProgram = constructSponsoredProgram(childAward, false);
                    sponsoredPrograms.getItem().add(sponsoredProgram);
                    interfacedSponsoredProgramIds.add(childAward.getAwardId());
                }

                ZGMSPONSORSTRUCTURE childAwardSponsor = constructSponsor(childAward);
                sponsors.getItem().add(childAwardSponsor);
                if (childAward.getPrimeSponsor() != null) {
                    ZGMSPONSORSTRUCTURE childAwardPrimeSponsor = constructSponsor(childAward,
                            childAward.getPrimeSponsor());
                    sponsors.getItem().add(childAwardPrimeSponsor);
                }

                for (AwardApprovedSubaward awardApprovedSubaward : childAward.getAwardApprovedSubawards()) {
                    ZGMSPONSORSTRUCTURE subAwardOrgSponsor = constructSponsorFromOrganization(childAward,
                            awardApprovedSubaward.getOrganization());
                    sponsors.getItem().add(subAwardOrgSponsor);
                }

                ZGMBILLINGPLANSTRUCTURE childBillingPlan = constructBillingPlan(childAward);
                if (childBillingPlan != null) {
                    billingPlans.getItem().add(childBillingPlan);
                }
            }
        }

        ZGMSPPROGRAMSTRUCTURE costSharingSponsoredProgram = processCostSharing(transmission.getAward());
        if (costSharingSponsoredProgram != null) {
            sponsoredPrograms.getItem().add(costSharingSponsoredProgram);
            interfacedSponsoredProgramIds.add(transmission.getAward().getAwardId());
        }

        SPONSOREDPROGRAMSGRP sponsoredProgramsGrp = constructSponsoredProgramGroups(transmission.getAward());

        ZGMKCRMINTERFACE kcrmInterface = objectFactory.createZGMKCRMINTERFACE();
        kcrmInterface.setBILLINGPLAN(billingPlans);
        kcrmInterface.setSPONSOR(sponsors);
        kcrmInterface.setGRANT(grant);
        kcrmInterface.setSPONSOREDPROGRAMS(sponsoredPrograms);
        kcrmInterface.setSPONSOREDPROGRAMSGRP(sponsoredProgramsGrp);

        return kcrmInterface;
    }

    protected SapTransmissionResponse executeSapService(SapTransmission transmission) {

        List<Long> interfacedSponsoredProgramIds = new ArrayList<Long>();
        ZGMKCRMINTERFACE kcrmInterface = constructSapInterface(transmission, interfacedSponsoredProgramIds);

        // execute the service

        StringWriter sendWriter = new StringWriter();
        StringWriter receiveWriter = new StringWriter();
        SIKCRMPROCESSOUTBOUND sapService = newWebServicePort(new PrintWriter(sendWriter),
                new PrintWriter(receiveWriter));

        LOG.info("Outbound Message: " + getTransmitXml(transmission)); //TODO mkousheh delete once in production

        ZGMKCRMINTERFACEResponse response = null;
        try {
            response = sapService.siKCRMPROCESSOUTBOUND(kcrmInterface);
        } catch (Fault fault) {
            Throwable nestedCause = fault.getCause();
            if (nestedCause instanceof SocketTimeoutException) {
                LOG.error("A SocketTimeoutException was thrown from service invocation.", fault);
                return SapTransmissionResponse.transmissionFailure(nestedCause.getMessage(), null,
                        sendWriter.toString(), receiveWriter.toString());
            }

            // BU Customization ID: N/A mkousheh N/A - N/A
            // errorReporter.reportError("A SocketTimeoutException was thrown from service invocation.", DATE_FORMAT_PATTERN);
            throw fault;
        }

        // BU Customization ID: N/A mukadder 20130429 - ENHC0010154 - Issue 55 - KC_SAP Interface to display warning message
        List<String> warningMessages = processWarningMessages(response);

        String failureMessage = processResponseMessages(response);
        if (failureMessage != null) {
            return SapTransmissionResponse.transmissionFailure(failureMessage, warningMessages,
                    sendWriter.toString(), receiveWriter.toString());
        }

        Map<Long, String> sponsoredProgramIds = null;

        SPONSOREDPROGRAMSMESSAGES sponsoredProgramsMessages = response.getSPONSOREDPROGRAMSMESSAGES();
        try {
            sponsoredProgramIds = extractSponsoredProgramIds(interfacedSponsoredProgramIds, transmission,
                    sponsoredProgramsMessages);
        } catch (IllegalStateException e) {
            throw new IllegalStateException(e.getMessage() + "/n/n" + receiveWriter.toString());
        }

        // BU Customization ID: N/A mukadder 20130306 - Handle SAP Walker number
        Map<Long, String> walkerIds = null;
        SPXWALKT walkerMessages = response.getSPXWALKT();
        try {
            walkerIds = extractWalkerIds(interfacedSponsoredProgramIds, transmission, walkerMessages);
        } catch (IllegalStateException e) {
            throw new IllegalStateException(e.getMessage() + "/n/n" + receiveWriter.toString());
        }

        // BU Customization ID: N/A mukadder 20130429 - ENHC0010154 - Issue 55 - KC_SAP Interface to display warning message
        return SapTransmissionResponse.success(sponsoredProgramIds, walkerIds, warningMessages,
                sendWriter.toString(), receiveWriter.toString());
    }

    /**
     * Extract Walker numbers from the return message
     */
    private Map<Long, String> extractWalkerIds(List<Long> interfacedSponsoredProgramIds,
            SapTransmission transmission, SPXWALKT walkerMessages) {
        Map<Long, String> walkerIds = new HashMap<Long, String>();
        if (walkerMessages != null && walkerMessages.getItem() != null) {
            if (walkerMessages.getItem().size() != interfacedSponsoredProgramIds.size()) {
                throw new IllegalStateException(
                        "The number of sponsored program messages returned should be equal to the number of sponsored programs interfaced.  "
                                + "Instead " + interfacedSponsoredProgramIds.size()
                                + " sponsored programs were interfaced and " + walkerMessages.getItem().size()
                                + " sponsored program messages were returned.");
            }
            for (int index = 0; index < walkerMessages.getItem().size(); index++) {
                ZFIKCRMSPXWALK walkerMessage = walkerMessages.getItem().get(index);
                Long awardId = interfacedSponsoredProgramIds.get(index);
                walkerIds.put(awardId, walkerMessage.getFNDUNTDPTPRESUF());
            }
        }
        return walkerIds;
    }

    /**
     * Constructs a new web service "port" and returns it.
     */
    protected SIKCRMPROCESSOUTBOUND newWebServicePort(PrintWriter sendWriter, PrintWriter receiveWriter) {
        String sapWebServiceUrlValue = ConfigContext.getCurrentContextConfig()
                .getProperty(SAP_SERVICE_WSDL_URL_PARAM);
        if (StringUtils.isBlank(sapWebServiceUrlValue)) {
            throw new IllegalStateException("The " + SAP_SERVICE_WSDL_URL_PARAM
                    + " configuration parameter needs to be set in order to invoke the service.");
        }
        try {
            URL sapWebServiceUrl = new URL(sapWebServiceUrlValue);
            // TODO could probably cache the created ZFIGMKCRMINTERFACE_Service
            SIKCRMPROCESSOUTBOUND sapService = new SIKCRMPROCESSOUTBOUNDService(sapWebServiceUrl)
                    .getSIKCRMPROCESSOUTBOUNDPort();
            configureServiceEndpoint(sapService, sendWriter, receiveWriter);
            return sapService;
        } catch (MalformedURLException e) {
            throw new RuntimeException(
                    "Failed to initialize SAP web service url because it was invalid, value was: "
                            + sapWebServiceUrlValue,
                    e);
        }
    }

    /**
     * Configures the service endpoint by configuring basic http authentication on the service as well as setting a custom value for the
     * service endpoint if one has been configured.
     */
    protected void configureServiceEndpoint(SIKCRMPROCESSOUTBOUND serviceEndpoint, PrintWriter sendWriter,
            PrintWriter receiveWriter) {

        Client client = ClientProxy.getClient(serviceEndpoint);
        client.getOutInterceptors().add(new LoggingOutInterceptor(sendWriter));
        client.getInInterceptors().add(new LoggingInInterceptor(receiveWriter));

        String connectionTimeout = ConfigContext.getCurrentContextConfig()
                .getProperty(SAP_SERVICE_CONNECTION_TIMEOUT_PARAM);
        String receiveTimeout = ConfigContext.getCurrentContextConfig()
                .getProperty(SAP_SERVICE_RECEIVE_TIMEOUT_PARAM);
        if (!StringUtils.isBlank(connectionTimeout) || !StringUtils.isBlank(receiveTimeout)) {
            Conduit conduit = client.getConduit();
            if (conduit instanceof HTTPConduit) {
                HTTPConduit httpConduit = (HTTPConduit) conduit;
                HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
                if (!StringUtils.isBlank(connectionTimeout)) {
                    httpClientPolicy.setConnectionTimeout(Long.parseLong(connectionTimeout));
                }
                if (!StringUtils.isBlank(receiveTimeout)) {
                    httpClientPolicy.setReceiveTimeout(Long.parseLong(receiveTimeout));
                }
                httpConduit.setClient(httpClientPolicy);
            }
        }

        if (!(serviceEndpoint instanceof BindingProvider)) {
            throw new IllegalArgumentException(
                    "The given service endpoint should be an instance of BindingProvider but was not.");
        }
        BindingProvider provider = (BindingProvider) serviceEndpoint;
        Map<String, Object> requestContext = provider.getRequestContext();

        String username = ConfigContext.getCurrentContextConfig().getProperty(SAP_SERVICE_USERNAME_PARAM);
        String password = ConfigContext.getCurrentContextConfig().getProperty(SAP_SERVICE_PASSWORD_PARAM);

        if (StringUtils.isBlank(username)) {
            throw new IllegalStateException(
                    "No username was configured for the SAP service, please ensure that the following configuration parameter is set: "
                            + SAP_SERVICE_USERNAME_PARAM);
        }
        if (StringUtils.isBlank(password)) {
            throw new IllegalStateException(
                    "No passwrod was configured for the SAP service, please ensure that the following configuration parameter is set: "
                            + SAP_SERVICE_PASSWORD_PARAM);
        }

        requestContext.put(BindingProvider.USERNAME_PROPERTY, username);
        requestContext.put(BindingProvider.PASSWORD_PROPERTY, password);

        // next check if a custom endpoint url has been configured
        String endpointUrl = ConfigContext.getCurrentContextConfig().getProperty(SAP_SERVICE_URL_PARAM);
        if (!StringUtils.isBlank(endpointUrl)) {
            requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointUrl);
        }

    }

    /**
     * In the case of warning, returns a non-null string that contains the warning message.
     * BU Customization ID: N/A mukadder 20130429 - ENHC0010154 - Issue 55 - KC_SAP Interface to display warning message
     */
    private List<String> processWarningMessages(ZGMKCRMINTERFACEResponse response) {
        List<String> warningMessages = new ArrayList<String>();
        if (response != null) {
            RETURN returnMessages = response.getRETURN();
            if (returnMessages != null & returnMessages.getItem() != null) {
                for (BAPIRET2 returnMessage : returnMessages.getItem()) {
                    if (WARNING_MESSAGE_TYPE.equals(returnMessage.getTYPE())) {
                        warningMessages.add(returnMessage.getMESSAGE());
                    }
                }
            }
        }
        return warningMessages;
    }

    /**
     * In the case of failure, returns a non-null string that contains the failure message.
     */
    private String processResponseMessages(ZGMKCRMINTERFACEResponse response) {

        boolean isSuccess = true;
        boolean hasSuccessMessage = false;
        StringBuilder successMessage = new StringBuilder();
        StringBuilder failureMessage = new StringBuilder();

        successMessage.append("SAP integration success messages:\n");
        failureMessage.append("SAP integration failure messages:\n");

        if (response != null) {

            GRANTMESSAGES grantMessages = response.getGRANTMESSAGES();
            SPONSORMESSAGES sponsorMessages = response.getSPONSORMESSAGES();
            SPONSOREDPROGRAMSMESSAGES sponsoredProgramsMessages = response.getSPONSOREDPROGRAMSMESSAGES();

            if (grantMessages != null && grantMessages.getItem() != null) {
                for (ZGRANTMESSAGES grantMessage : grantMessages.getItem()) {
                    if (ERROR_MESSAGE_TYPE.equals(grantMessage.getTYPE())) {
                        isSuccess = false;
                        failureMessage.append("    [Grant Failure Message: " + grantMessage.getGRANTNBR() + "] - "
                                + grantMessage.getMESSAGE() + "\n");
                    } else {
                        hasSuccessMessage = true;
                        successMessage.append("    [Grant Success Message: " + grantMessage.getGRANTNBR() + "] - "
                                + grantMessage.getMESSAGE() + "\n");
                    }
                }
            }

            if (sponsorMessages != null && sponsorMessages.getItem() != null) {
                for (ZSPONSORMESSAGES sponsorMessage : sponsorMessages.getItem()) {
                    if (ERROR_MESSAGE_TYPE.equals(sponsorMessage.getTYPE())) {
                        isSuccess = false;
                        failureMessage.append("    [Sponsor Failure Message: " + sponsorMessage.getSPONSOR()
                                + "] - " + sponsorMessage.getMESSAGE() + "\n");
                    } else {
                        hasSuccessMessage = true;
                        successMessage.append("    [Sponsor Success Message: " + sponsorMessage.getSPONSOR()
                                + "] - " + sponsorMessage.getMESSAGE() + "\n");
                    }
                }
            }

            if (sponsoredProgramsMessages != null && sponsoredProgramsMessages.getItem() != null) {
                for (ZSPPROGRAMMESSAGES sponsoredProgramsMessage : sponsoredProgramsMessages.getItem()) {
                    if (ERROR_MESSAGE_TYPE.equals(sponsoredProgramsMessage.getTYPE())) {
                        isSuccess = false;
                        failureMessage.append("    [Sponsored Program Failure Message: "
                                + sponsoredProgramsMessage.getSPONSOREDPROG() + "] - "
                                + sponsoredProgramsMessage.getMESSAGE() + "\n");
                    } else {
                        hasSuccessMessage = true;
                        successMessage.append("    [Sponsored Program Success Message: "
                                + sponsoredProgramsMessage.getSPONSOREDPROG() + "] - "
                                + sponsoredProgramsMessage.getMESSAGE() + "\n");
                    }
                }
            }

        }

        if (hasSuccessMessage) {
            LOG.info(successMessage.toString());
        }
        if (!isSuccess) {
            LOG.info(failureMessage.toString());
            return failureMessage.toString();
        }
        return null;
    }

    /**
     * Implements 1.7.1 and 1.7.2 of the functional specification.
     * <p/>
     * This data maps 1:1 from the Parent Award that is being interfaced.
     */
    protected ZGMGRANTSTRUCTURE constructGrant(Award award) {

        // Specification Section 1.7.1

        ZGMGRANTSTRUCTURE grant = objectFactory.createZGMGRANTSTRUCTURE();

        CustomAwardDataHelper helper = new CustomAwardDataHelper(award);

        if (helper.getLastTransmissionDate() == null) {
            grant.setGRANTUPDATE(INTERFACE_NEW);
        } else {
            grant.setGRANTUPDATE(INTERFACE_UPDATE);
        }

        ZBAPI0035HEADER grantHeader = objectFactory.createZBAPI0035HEADER();
        ZBAPI0035HEADERADD grantHeaderAdd = objectFactory.createZBAPI0035HEADERADD();
        ZFIGRANTDATA grantData = objectFactory.createZFIGRANTDATA();

        grant.setHEADER(grantHeader);
        grant.setHEADERADD(grantHeaderAdd);
        grant.setEXTENSIONIN(grantData);

        // Position 1
        grantHeader.setGRANTNBR(deriveGrantNumber(award.getAwardNumber()));

        // BU Customization ID: N/A mkousheh 20110620 - Send Parent Transaction Type
        grantHeader.setPARENTTRANSACTIONTYPE(award.getAwardTransactionType().getDescription());

        // Position 2
        grantHeader.setGRANTTYPE(convertAccountTypeToGrantType(award.getAccountTypeCode(), award));

        // Position 4
        grantHeader.setSPONSOR(award.getSponsorCode());

        // Position 5 - pad with zeros to 10 chars
        grantHeaderAdd.setAUTHGROUP(StringUtils.rightPad(award.getLeadUnitNumber(), 10, "0"));

        // Position 6
        grantHeaderAdd.setAWARDTYPE(convertAwardTypeCodeToAwardType(award.getAwardTypeCode(), award));

        AwardAmountInfo awardAmountInfo = getAwardAmountInfoService()
                .fetchLastAwardAmountInfoForAwardVersionAndFinalizedTandMDocumentNumber(award);

        // Position 8
        grantHeaderAdd.setGRANTTOTAL(awardAmountInfo.getAmountObligatedToDate().bigDecimalValue());

        // Position 9
        grantHeader.setVALIDFROM(dateToString(org.kuali.coeus.sys.framework.util.DateUtils
                .convertToSqlDate(awardAmountInfo.getCurrentFundEffectiveDate())));

        // BU Customization ID: N/A mkousheh 20110620 - Set Valid From Budget to 3 months if null
        if (awardAmountInfo.getCurrentFundEffectiveDate() != null) {
            java.util.Date tmpDate = org.apache.commons.lang3.time.DateUtils

                    .addMonths(awardAmountInfo.getCurrentFundEffectiveDate(), -3);
            grantHeader.setVALIDFROMBUDGET(
                    dateToString(org.kuali.coeus.sys.framework.util.DateUtils.convertToSqlDate(tmpDate)));
        }

        // Position 10
        grantHeader.setVALIDTO(dateToString(org.kuali.coeus.sys.framework.util.DateUtils
                .convertToSqlDate(awardAmountInfo.getObligationExpirationDate())));
        // BU Customization ID: N/A mkousheh 20110620 - Set Valid To Budget to 1 year if null
        if (awardAmountInfo.getObligationExpirationDate() != null) {
            java.util.Date tmpDate2 = org.apache.commons.lang3.time.DateUtils
                    .addYears(awardAmountInfo.getObligationExpirationDate(), 1);
            grantHeader.setVALIDTOBUDGET(
                    dateToString(org.kuali.coeus.sys.framework.util.DateUtils.convertToSqlDate(tmpDate2)));
        }

        // Position 11
        grantHeader.setEXTREFERENCE(award.getSponsorAwardNumber());
        grantHeader.setINTREFERENCE(award.getAwardNumber());

        // Position 12
        grantHeaderAdd.setCFDANBR(award.getCfdaNumber());

        // Position 13
        if (award.getBasisOfPaymentCode() == null) {
            logAwardInfo(award, "awardBasisOfPayment is null, not sending billingRule on grant");
        } else {
            grantHeaderAdd.setBILLINGRULE(convertBasisOfPaymentToBillingRule(award.getBasisOfPaymentCode(), award));
        }

        // Position 14
        grantHeaderAdd
                .setLETTEROFCREDIT(convertMethodOfPaymentToLetterOfCredit(award.getMethodOfPaymentCode(), award));

        // BUKC-0124: Send new custom field FAIN to SAP via interface (ENHC0012816)
        grantHeaderAdd.setFUNDINGORIGIN(((AwardExtension) award.getExtension()).getFain());

        // Position 16 and 17
        for (AwardReportTerm awardReportTermItem : award.getAwardReportTermItems()) {
            // skip Report Codes 27 and 24
            if (!awardReportTermItem.getReportCode().equals("27")
                    && !awardReportTermItem.getReportCode().equals("24")) {
                if (PAYMENT_INVOICES_REPORT_CLASS_CODE.equals(awardReportTermItem.getReportClassCode())) {
                    grantData.setZZINVOICEFREQ(
                            convertFrequencyCodeToInvoiceFrequency(awardReportTermItem.getFrequencyCode()));
                    grantData.setZZINVOICEFORM(convertReportCodeToInvoiceForm(awardReportTermItem.getReportCode()));
                    break;
                }
            }
        }

        // Position 18
        String advancePayment = determineAdvancePayment(award);
        if (advancePayment != null) {
            grantData.setZZADVPYMNTIND(advancePayment);
        }

        // Position 19
        grantData.setZZFUNDCENTER(StringUtils.rightPad(award.getLeadUnitNumber(), 10, "0"));

        // Position 20
        grantData.setZZAWARDTITLE(award.getTitle());

        // Position 22
        grantData.setZZINTEARNED(helper.getInterestEarned());

        // Position 23
        if (helper.isArra()) {
            grantData.setZZLDCODE(ARRA_FUNDING_INFORMATION);
        }

        // Position 24
        grantData.setZZMJRPRJCT("yes".equalsIgnoreCase(helper.getMajorProject()) ? "x" : "");

        //grantData.setZZINVOICEFREQ(""); // NOT ON AWARD
        //grantData.setZZINVOICEFORM(""); // NOT ON AWARD

        // Position 25
        String propertyOwnerTitle = determinePropertyOwnerTitle(award);
        if (propertyOwnerTitle != null) {
            grantData.setZZPRPRTYOWNR(propertyOwnerTitle);
        }

        // Position 26
        String costShareMemoMatch = determineCostShareMemoMatch(award);
        if (costShareMemoMatch != null) {
            grantData.setZZCOSTSHARE(costShareMemoMatch);
        }

        // Position 27
        grantData.setZZAVCTOLERANCE(convertAvcIndicatorToAvcTolerance(helper.getAvcIndicator()));

        // Position 28
        grantData.setZZNSFCTGRY(award.getNsfCode());

        // Position 29
        grantData.setZZA133CLSTR(helper.getA133Cluster());

        // Position 31
        // BUKC-0088: Transmitting Project Start Date as AwardEffectiveDate and not BeginDate (fields switched after 3.0.1 release) - (DFCT0011210)
        grantData.setZZPROJBEGDA(dateToString(award.getAwardEffectiveDate()));
        // grantData.setZZPROJBEGDA(dateToString(award.getBeginDate()));

        // Position 32
        grantData.setZZPROJENDDA(dateToString(awardAmountInfo.getFinalExpirationDate()));

        grantData.setZZAWARDNO(award.getAwardNumber());

        // Position 33
        grantData.setZZSPONSOR(award.getPrimeSponsorCode());

        // Position 34
        grantHeader.setUSERSTATUS(convertStatusCodeToResponsibility(award.getStatusCode(), award));

        // Position 35
        grantData.setZZINTEARNED(convertInterestEarnedCode(helper.getInterestEarned()));

        // Position 36
        if (getMostRecentAwardReportTerm(award) != null
                && getMostRecentAwardReportTerm(award).getAwardReportTermRecipients().size() > 0) {
            grantData.setZZBILLPARTNER(getMostRecentAwardReportTerm(award).getAwardReportTermRecipients().get(0)
                    .getRolodexId().toString());
        }

        // Specification Section 1.7.2
        ZGMFACREDITT faCredit = objectFactory.createZGMFACREDITT();
        grant.setFACREDIT(faCredit);
        List<ZGMFACREDIT> faCredits = faCredit.getItem();
        for (AwardPerson awardPerson : award.getProjectPersons()) {
            for (AwardPersonUnit awardPersonUnit : awardPerson.getUnits()) {
                AwardPersonUnitCreditSplit creditSplit = getMostRecentCreditSplit(awardPersonUnit);
                ZGMFACREDIT credit = objectFactory.createZGMFACREDIT();
                credit.setGRANTNBR(award.getAwardNumber());
                credit.setDEPT(StringUtils.rightPad(awardPersonUnit.getUnitNumber(), 10, "0"));
                if (creditSplit != null) {
                    credit.setPERCENTAGE(creditSplit.getCredit().bigDecimalValue());
                }
                faCredits.add(credit);
            }
        }

        // Specification Section 1.7.3

        ZBAPI0035RESPONSIBLET grantPersons = objectFactory.createZBAPI0035RESPONSIBLET();
        grant.setRESPONSIBILITY(grantPersons);
        for (AwardPerson awardPerson : award.getProjectPersons()) {
            ZBAPI0035RESPONSIBLE grantPerson = objectFactory.createZBAPI0035RESPONSIBLE();
            grantPersons.getItem().add(grantPerson);
            grantPerson.setUSERID(awardPerson.getPersonId());
            grantPerson.setUSERNAME(awardPerson.getFullName());
            // BUKC-0062: SAP Interface - Handle multiple PIs for NIH awards when transmit to SAP
            if (awardPerson.isMultiplePi()) {
                grantPerson.setRESPONSIBILITY("PI");
            } else {
                grantPerson.setRESPONSIBILITY(
                        convertProjectRoleToResponsibility(awardPerson.getContactRoleCode(), award));

            }
        }

        for (AwardUnitContact awardContact : award.getAwardUnitContacts()) {
            if ("2".equals(awardContact.getUnitAdministratorTypeCode())) {
                ZBAPI0035RESPONSIBLE grantPerson = objectFactory.createZBAPI0035RESPONSIBLE();
                grantPersons.getItem().add(grantPerson);
                grantPerson.setUSERID(awardContact.getPersonId());
                grantPerson.setUSERNAME(awardContact.getFullName());
                grantPerson.setRESPONSIBILITY("DA");
            }
            if ("6".equals(awardContact.getUnitAdministratorTypeCode())) {
                ZBAPI0035RESPONSIBLE grantPerson = objectFactory.createZBAPI0035RESPONSIBLE();
                grantPersons.getItem().add(grantPerson);
                grantPerson.setUSERID(awardContact.getPersonId());
                grantPerson.setUSERNAME(awardContact.getPerson().getFullName());
                grantPerson.setRESPONSIBILITY("OAV");
            }

            // BUKC-0140: Add new Admin Type (Clinical Trial Admin)
            if ("8".equals(awardContact.getUnitAdministratorTypeCode())) {
                ZBAPI0035RESPONSIBLE grantPerson = objectFactory.createZBAPI0035RESPONSIBLE();
                grantPersons.getItem().add(grantPerson);
                grantPerson.setUSERID(awardContact.getPersonId());
                grantPerson.setUSERNAME(awardContact.getPerson().getFullName());
                grantPerson.setRESPONSIBILITY("CTAD");
            }

        }

        // BU Customization ID: N/A mkousheh 20110706 - Get OSP and PAFO Admin from Unit Admin per lead unit
        // (above code is not accurate since Data are not stored on award-person
        for (UnitAdministrator unitAdministrator : award.getLeadUnit().getUnitAdministrators()) {
            if ("3".equals(unitAdministrator.getUnitAdministratorTypeCode())) {
                ZBAPI0035RESPONSIBLE grantPerson = objectFactory.createZBAPI0035RESPONSIBLE();
                grantPersons.getItem().add(grantPerson);
                grantPerson.setUSERID(unitAdministrator.getPersonId());
                grantPerson.setUSERNAME(unitAdministrator.getPerson().getFullName());
                grantPerson.setRESPONSIBILITY("OSP");
            }
            if ("4".equals(unitAdministrator.getUnitAdministratorTypeCode())) {
                ZBAPI0035RESPONSIBLE grantPerson = objectFactory.createZBAPI0035RESPONSIBLE();
                grantPersons.getItem().add(grantPerson);
                grantPerson.setUSERID(unitAdministrator.getPersonId());
                grantPerson.setUSERNAME(unitAdministrator.getPerson().getFullName());
                grantPerson.setRESPONSIBILITY("PAFO");
            }

        }

        return grant;
    }

    /**
     * To derive grant number from award number, we take the first 6 digits.
     * <p/>
     * For example, if award number is 123456-00001 then grant number will be 123456.
     *
     * @param awardNumber the award number
     * @return the derived grant number
     * @throws IllegalArgumentException if the award number is not in a valid format
     */
    private String deriveGrantNumber(String awardNumber) {
        if (StringUtils.isBlank(awardNumber) || awardNumber.length() < 6) {
            throw new IllegalArgumentException(
                    "Grant number could not be derived from the given award number, it was either blank or less than 6 digits.  Award number was: "
                            + awardNumber);
        }
        return awardNumber.substring(0, 6);
    }

    /**
     * Converts the given frequency code from KC to an SAP invoice frequency according to the following mapping:
     * <p/>
     * When Frequency_Code = 1, Invoice_Frequency = "M
     * When Frequency_Code = 2, Invoice_Frequency = "Q"
     * All other values for Frequency_Code, Invoice_Frequency = "O"
     *
     * @param frequencyCode the frequencyCode to convert
     * @return the resulting invoice frequency
     * @throws IllegalArgumentException if the given frequencyCode is blank or null
     */
    private String convertFrequencyCodeToInvoiceFrequency(String frequencyCode) {
        if (StringUtils.isBlank(frequencyCode)) {
            throw new IllegalArgumentException(
                    "The given frequencyCode was null or blank, could not convert to an invoice frequency.");
        }
        if ("1".equals(frequencyCode)) {
            return "M";
        } else if ("2".equals(frequencyCode)) {
            return "Q";
        } else {
            return "O";
        }
    }

    /**
     * Converts the given report code from KC to an SAP invoice form according to the following mapping:
     * <p/>
     * When Report_Code = 1, Invoice_Form = "ZFI_GM_SF425"
     * When Report_Code = 3, Invoice_Form = "ZFI_GM_SF270"
     * When Report_Code = 25, Invoice_Form = "ZFI_GM_SF1034-1035"
     * When Report_Code = 26, Invoice_Form = "ZFI_GM_BU-Standard"
     * When Report_Code = 28, Invoice_Form = "Manual"
     */
    private String convertReportCodeToInvoiceForm(String reportCode) {
        String invoiceForm = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.REPORT_CODE_TO_INVOICE_FORM_MAPPING, reportCode);
        if (StringUtils.isEmpty(invoiceForm)) {
            throw new IllegalArgumentException(
                    "Could not resolve an invoice form from the given report code, value was: " + reportCode);
        }
        return invoiceForm;
    }

    /**
     * Determines the Advance_Payment value using the following algorithm:
     * <p/>
     * When a row exists on AWARD_REPORT_TERMS where AWARD_REPORT_TERMS.REPORT_CLASS_CODE = 8 and
     * AWARD_REPORT_TERMS.REPORT_CODE = 27  Send an "X" value for Advance_Payment.  Otherwise don"t send a value.
     *
     * @param award the award to determine the advance payment for
     * @return the value of the advance payment ("X") or null if no advance payment data should be sent
     */
    private String determineAdvancePayment(Award award) {
        List<AwardReportTerm> awardReportTerms = award.getAwardReportTermItems();
        for (AwardReportTerm awardReportTerm : awardReportTerms) {
            if (PAYMENT_INVOICES_REPORT_CLASS_CODE.equals(awardReportTerm.getReportClassCode())
                    && "27".equals(awardReportTerm.getReportCode())) {
                return "X";
            }
        }
        return null;
    }

    /**
     * Determines the Property_Owner_Title for the given Award using the following mapping:
     * <p/>
     * If an Award has AWARD_SPONSOR_TERM_ID = 420; Property_Owner_Title = "BU"
     * If an Award has AWARD_SPONSOR_TERM_ID = 421; Property_Owner_Title = "FD"
     * If an Award has AWARD_SPONSOR_TERM_ID = 422; Property_Owner_Title = "OI"
     * Otherwise, don"t send anything
     *
     * @param award the award to determine the property owner title for
     * @return the property owner title or null if one cannot be determined
     */
    private String determinePropertyOwnerTitle(Award award) {
        AwardSponsorTerm sponsorTerm = getMostRecentAwardSponsorTerm(award);
        if (sponsorTerm != null) {
            if (new Long(420).equals(sponsorTerm.getSponsorTermId())) {
                return "BU";
            } else if (new Long(421).equals(sponsorTerm.getSponsorTermId())) {
                return "FD";
            } else if (new Long(422).equals(sponsorTerm.getSponsorTermId())) {
                return "OI";
            }
        }
        return null;
    }

    /**
     * Determines the Cost_Share_Memo_Match value using the following algorithm:
     * <p/>
     * If an Award has a row on the AWARD_COST_SHARE where Cost_Share_Type_Code = 3, then Cost_Share_Memo_Match = "X".
     * Otherwise don't send a value.
     *
     * @param award the award to determine the cost share memo match for
     * @return the value of the cost share memo match ("X") or null if no cost share memo match data should be sent
     */
    public String determineCostShareMemoMatch(Award award) {
        List<AwardCostShare> awardCostShares = award.getAwardCostShares();
        for (AwardCostShare awardCostShare : awardCostShares) {
            if (new Integer(3).equals(awardCostShare.getCostShareTypeCode())) {
                return "X";
            }
        }
        return null;
    }

    /**
     * Takes the given Award Type  and converts it to an Award Type tolerance.
     *
     * @param awardTypeCode the awardTypeCode to convert
     * @return the avc tolerance value for the given indicator
     */
    private String convertAwardTypeCodeToAwardType(int awardTypeCode, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.AWARD_TYPE_MAPPING, Integer.toString(awardTypeCode));
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert award type, given award type value was not understood: " + awardTypeCode
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    /**
     * Takes the given AVC indicator and converts it to an AVC tolerance.
     *
     * @param avcIndicator the avcIndicator to convert
     * @return the avc tolerance value for the given indicator
     */
    private String convertAvcIndicatorToAvcTolerance(String avcIndicator) {
        if (StringUtils.isBlank(avcIndicator)) {
            throw new IllegalArgumentException("The avc indicator was blank or null.");
        }
        if ("100%".equals(avcIndicator)) {
            return "Z001";
        } else if ("125%".equals(avcIndicator)) {
            return "Z002";
        } else if ("0".equals(avcIndicator)) {
            return "Z999";
        }
        throw new IllegalArgumentException(
                "Could not resolve avc tolerance from the given avc indicator, value was: " + avcIndicator);
    }

    /**
     * Takes the given Interest Earned Code and converts it to an Interest Earned Code tolerance.
     *
     * @param interestEarnedCode
     * @return the interest earned code tolerance value for the given code
     */
    private String convertInterestEarnedCode(String interestEarnedCode) {
        if ("Not Applicable".equals(interestEarnedCode)) {
            return "N";
        } else if ("Return to sponsor".equals(interestEarnedCode)) {
            return "S";
        } else if ("Return to award".equals(interestEarnedCode)) {
            return "A";
        }
        throw new IllegalArgumentException(
                "Could not convert the given interest earned code, value was: " + interestEarnedCode);
    }

    /**
     * Determines the Billing Partner ID value if found, otherwise use the Rolodex ID
     *
     * @param award the award to determine the billing partner match for
     * @return the value of tje billing partner id
     */
    private Integer determineBillingPartnerId(Award award) {
        AwardSponsorContact billingPartner = determineBillingPartner(award);
        if (billingPartner != null) {
            return billingPartner.getRolodexId();
        }
        return null;
    }

    /**
     * @param award award the award to determine the billing partner match for
     * @return an object of the billing contact if found
     */
    private AwardSponsorContact determineBillingPartner(Award award) {
        for (AwardSponsorContact contact : award.getSponsorContacts()) {
            if ("1".equals(contact.getContactRoleCode())) {
                return contact;
            }
        }
        return null;
    }

    /**
     * Implements section 1.7.7 of the functional specification to create sponsored term data.
     * It does this for child awards as well as their parent.
     * <p/>
     * <p>Note that if an award has multiple sponsored terms, they all should be sent (not only
     * the most recent).
     */
    protected ZBAPI0035SPONSOREDOBJECTST constructSponsoredObjects(SapTransmission transmission) {
        ZBAPI0035SPONSOREDOBJECTST sapSponsoredObjects = objectFactory.createZBAPI0035SPONSOREDOBJECTST();
        Set<SponsoredObject> parentSponsoredObjects = constructSponsoredObjects(null, transmission.getAward());
        if (parentSponsoredObjects != null && isCostSharing(transmission.getAward())) {
            loadSapSponsoredObjects(sapSponsoredObjects, parentSponsoredObjects);
        }
        for (Award award : transmission.getChildAwards()) {
            if (!"group".equalsIgnoreCase(((AwardExtension) award.getExtension()).getChildType())) {
                Set<SponsoredObject> sponsoredObjects = constructSponsoredObjects(parentSponsoredObjects, award);
                loadSapSponsoredObjects(sapSponsoredObjects, sponsoredObjects);
            }
        }
        return sapSponsoredObjects;
    }

    protected Set<SponsoredObject> constructSponsoredObjects(Set<SponsoredObject> parentSponsoredObjects,
            Award award) {
        Set<SponsoredObject> sponsoredObjects = new HashSet<SponsoredObject>();
        List<AwardSponsorTerm> awardSponsorTerms = award.getAwardSponsorTerms();
        String sponsoredProgram = null;
        if (StringUtils.isEmpty(award.getAccountNumber())) {
            sponsoredProgram = award.getAwardNumber();
        } else {
            sponsoredProgram = award.getAccountNumber();
        }
        if (awardSponsorTerms != null) {
            for (AwardSponsorTerm awardSponsorTerm : awardSponsorTerms) {
                String sponsoredClass = convertSponsorTermToSponsorClass(awardSponsorTerm.getSponsorTermId(),
                        award);
                if (!StringUtils.isBlank(sponsoredProgram) && !StringUtils.isBlank(sponsoredClass)) {
                    sponsoredObjects.add(new SponsoredObject(sponsoredProgram, sponsoredClass));
                }
            }
        }
        // copy in the entries from the parent, using the child sponsored program
        if (parentSponsoredObjects != null) {
            for (SponsoredObject parentSponsoredObject : parentSponsoredObjects) {
                sponsoredObjects.add(new SponsoredObject(sponsoredProgram, parentSponsoredObject.sponsoredClass));
            }
        }
        return sponsoredObjects;
    }

    protected void loadSapSponsoredObjects(ZBAPI0035SPONSOREDOBJECTST sapSponsoredObjects,
            Set<SponsoredObject> sponsoredObjects) {
        for (SponsoredObject sponsoredObject : sponsoredObjects) {
            ZBAPI0035SPONSOREDOBJECTS sapSponsoredObject = objectFactory.createZBAPI0035SPONSOREDOBJECTS();
            sapSponsoredObject.setSPONSOREDPROG(sponsoredObject.sponsoredProgram);
            sapSponsoredObject.setSPONSOREDCLASS(sponsoredObject.sponsoredClass);
            sapSponsoredObjects.getItem().add(sapSponsoredObject);
        }
    }

    /**
     * This class is just a wrapper around sponsored program and sponsored class.  Implements hashcode and equals
     * method which allows us to use it inside a Set and prevent duplicate entries from happenign.
     */
    private static class SponsoredObject {
        private final String sponsoredProgram;
        private final String sponsoredClass;

        SponsoredObject(String sponsoredProgram, String sponsoredClass) {
            if (StringUtils.isBlank(sponsoredProgram) || StringUtils.isBlank(sponsoredClass)) {
                throw new IllegalArgumentException("both sponsored program and sponsored class must be non-null!");
            }
            this.sponsoredProgram = sponsoredProgram;
            this.sponsoredClass = sponsoredClass;
        }

        @Override
        public boolean equals(Object object) {
            if (object instanceof SponsoredObject) {
                SponsoredObject sponsoredObject = (SponsoredObject) object;
                return sponsoredObject.sponsoredProgram.equals(sponsoredProgram)
                        && sponsoredObject.sponsoredClass.equals(sponsoredClass);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return sponsoredProgram.hashCode();
        }

    }

    /**
     * Implements section 1.7.4, 1.7.5, and 1.7.6 of the functional specification.
     *
     * @param award       the award for which to generate a sponsored program
     * @param costSharing indicates if this is a cost sharing program or not, if true then this method produces a
     *                    sponsored program structure which compiles with specification section 1.7.4, if false then it will generate
     *                    one which complies with specification section 1.7.5
     * @param parentAward true if the given award is a parent award, false otherwise
     * @return
     */
    protected ZGMSPPROGRAMSTRUCTURE constructSponsoredProgram(Award award, boolean costSharing) {

        CustomAwardDataHelper helper = new CustomAwardDataHelper(award);
        AwardBudgetExt abvoe = getLastBudgetVersion(award.getAwardDocument());
        boolean awardBudgetVersionToBePosted = false;
        if (abvoe != null) {
            String budgetStatus = abvoe.getAwardBudgetStatusCode();
            awardBudgetVersionToBePosted = Constants.BUDGET_STATUS_CODE_TO_BE_POSTED.equalsIgnoreCase(budgetStatus);
        }

        ZGMSPPROGRAMSTRUCTURE sponsoredProgramStructure = objectFactory.createZGMSPPROGRAMSTRUCTURE();

        if (helper.getLastTransmissionDate() == null) {
            sponsoredProgramStructure.setSPPROGRAMUPDATE(INTERFACE_NEW);
        } else {
            sponsoredProgramStructure.setSPPROGRAMUPDATE(INTERFACE_UPDATE);
        }

        sponsoredProgramStructure.setSPPROGRAMNUMBER(award.getAccountNumber());

        // Handle data which is shared between 1.7.4 and 1.7.5

        // Position 1
        if (StringUtils.isEmpty(award.getAccountNumber())) {
            sponsoredProgramStructure.setSPONSOREDPROG(award.getAwardNumber());
        } else {
            sponsoredProgramStructure.setSPONSOREDPROG(award.getAccountNumber());
        }

        AwardAmountInfo awardAmountInfo = getAwardAmountInfoService()
                .fetchLastAwardAmountInfoForAwardVersionAndFinalizedTandMDocumentNumber(award);

        // Position 3
        sponsoredProgramStructure.setZZVALIDFROM(dateToString(awardAmountInfo.getCurrentFundEffectiveDate()));

        // Position 4
        sponsoredProgramStructure.setZZVALIDTO(dateToString(awardAmountInfo.getObligationExpirationDate()));

        // Position 6
        sponsoredProgramStructure.setBUSINESSAREA(convertLeadUnitToBusinessArea(award.getLeadUnitNumber(), award));

        // Position 7
        GMSPPROGRAMFMBTTT programFmbtTt = objectFactory.createGMSPPROGRAMFMBTTT();
        GMSPPROGRAMFMBT programFmbt = objectFactory.createGMSPPROGRAMFMBT();
        programFmbtTt.getItem().add(programFmbt);
        sponsoredProgramStructure.setTGMSPPROGRAMFMBT(programFmbtTt);

        programFmbt.setSPONSOREDPROG(award.getAccountNumber());
        programFmbt.setFUNCTIONALAREA(convertActivityTypeToFunctionalArea(award.getActivityTypeCode(), award));

        // Position 8
        // If this is a cost sharing sponsored program then it is sourced from the parent award, but they don't have
        // budgets so the F and A rate would not be able to be calculated.
        // also, parent awards cant have budgets, so check to see if their are children as well
        AwardHierarchy hierarchy = getAwardHierarchyService().loadAwardHierarchy(award.getAwardNumber());
        String overheadKey = "";
        if (!awardBudgetVersionToBePosted) {

            // BU Customization ID: N/A mkousheh 20120504 - Get the latest child that matches that was successful transmitted
            AwardTransmissionChild lastTransmittedChild = getLatestChildAwardTransmitted(
                    hierarchy.getRoot().getAward(), award.getAwardNumber(), true);
            if (lastTransmittedChild != null) {
                overheadKey = lastTransmittedChild.getOverheadKey();
            }

            // BU Customization ID: N/A mkousheh 20120504 - Commented out the following code and replaced with the above -
            // BUG (it gets latest transmitted then matches the child, sometimes no matching child in the last transmission
            // AwardTransmission lastTransmission = getLatestAwardTransmission(hierarchy.getRoot().getAward(), true);
            // if  (lastTransmission != null) {
            //     AwardTransmissionChild lastChildTransmission = findMatchingTransmissionChild(lastTransmission.getTransmissionChildren(), award.getAwardNumber());
            //     if (lastChildTransmission != null) {
            //         overheadKey = lastChildTransmission.getOverheadKey();
            //     }
            // }

            // BU Customization ID: N/A mkousheh N/A - Switching this to use value saved on last transmission child -
            //            BigDecimal fAndARate = calculateApplicableFandARate(award);
            //            if(fAndARate != null) {
            //                sponsoredProgramStructure.setOVERHEADKEY(StringUtils.leftPad(fAndARate.toPlainString(), 6, "0"));
            //            }
        } else if (!costSharing && !hierarchy.hasChildren() && awardBudgetVersionToBePosted) {
            BigDecimal fAndARate = calculateApplicableFandARate(award);
            if (fAndARate != null && (((AwardExtension) award.getExtension()).getSteppedUpRate() == null
                    || "NA".equalsIgnoreCase(((AwardExtension) award.getExtension()).getSteppedUpRate()))) {
                overheadKey = StringUtils.leftPad(fAndARate.toPlainString(), 6, "0");
            } else {
                // get the system parameter for these values and lookup the mapping value based on the date
                overheadKey = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                        BUConstants.FEDERAL_RATE_DATE_OVERHEAD_KEY_FIELD_MAPPINGS,
                        ((AwardExtension) award.getExtension()).getSteppedUpRate());
                if (fAndARate == null || StringUtils.isBlank(overheadKey)) {
                    overheadKey = ("000.00");
                }
            }
        }
        sponsoredProgramStructure.setOVERHEADKEY(overheadKey);
        GlobalVariables.getUserSession().addObject("overheadKey-".concat(award.getAwardNumber()),
                (Object) overheadKey);
        LOG.info("Writing overheadKey to session: " + overheadKey);

        // Position 9
        programFmbt.setFUNDSCENTER(StringUtils.rightPad(award.getLeadUnitNumber(), 10, "0"));

        // Position 11
        sponsoredProgramStructure.setZZREFERENCE(award.getAwardNumber());

        // Position 12
        sponsoredProgramStructure.setZZAWARDNO(award.getSponsorAwardNumber());

        // Position 13
        sponsoredProgramStructure.setZZDOCNO(award.getDocumentFundingId());

        AwardBudgetExt budget = null;
        if (!costSharing) {
            budget = award.getAwardDocument().getBudgetVersionOverview();
            if (budget == null) {
                throw new IllegalArgumentException(
                        "Award with number " + award.getAwardNumber() + " does not have a budget.");
            }
        }

        // BU Customization ID: N/A mkousheh 20110706 - In case of CostSharing check if updated
        boolean costSharesHaveChanged = false;
        if (costSharing) {
            AwardCostShare tmpAwardCostShare = getMostRecentAwardCostShare(award);
            if (tmpAwardCostShare != null
                    && ((AwardExtension) award.getExtension()).getLastTransmissionDate() != null
                    && tmpAwardCostShare.getUpdateTimestamp() != null && ((AwardExtension) award.getExtension())
                            .getLastTransmissionDate().before(tmpAwardCostShare.getUpdateTimestamp())) {
                costSharesHaveChanged = true;
            }
        }

        //        if (!costSharesHaveChanged) {
        // Position 17
        if (awardBudgetVersionToBePosted && budget != null && budget.getTotalDirectCost() != null) {
            sponsoredProgramStructure.setBUDGETTDC(budget.getTotalDirectCost().bigDecimalValue());
        } else {
            sponsoredProgramStructure.setBUDGETTDC(ScaleTwoDecimal.ZERO.bigDecimalValue());
        }

        // Position 18
        if (awardBudgetVersionToBePosted && budget != null && budget.getTotalIndirectCost() != null) {
            sponsoredProgramStructure.setBUDGETFA(budget.getTotalIndirectCost().bigDecimalValue());
        } else {
            sponsoredProgramStructure.setBUDGETFA(ScaleTwoDecimal.ZERO.bigDecimalValue());
        }
        //        }

        // BU Customization ID: N/A mkousheh 20110712 - If Parent Award Transaction is NoCostExtention or Administrative Changes Set Budget to zero
        if (hierarchy.getRoot().getAward().getAwardTransactionTypeCode() != null) {
            if (hierarchy.getRoot().getAward().getAwardTransactionTypeCode().equals(10)
                    || hierarchy.getRoot().getAward().getAwardTransactionTypeCode().equals(6)) {
                sponsoredProgramStructure.setBUDGETTDC(ScaleTwoDecimal.ZERO.bigDecimalValue());
                sponsoredProgramStructure.setBUDGETFA(ScaleTwoDecimal.ZERO.bigDecimalValue());
            }
        }

        // Position 19
        boolean fringeNotAllowed = helper.isFringeNotAllowed();
        sponsoredProgramStructure
                .setFRINGECODE(FringeCodeMapping.mapToSapFringeCode(fringeNotAllowed, award.getAccountTypeCode()));

        /**
         *  Data which is only part of 1.7.4
         */
        if (!costSharing && !hierarchy.hasChildren()) {

            // Position 10
            sponsoredProgramStructure.setZZORDCAT(convertChildType(helper.getChildType(), award));

            // Position 14
            String offCampus = null;
            if (!awardBudgetVersionToBePosted) {

                // BU Customization ID: N/A mkousheh 20120504 - Get the latest child that matches that was successful transmitted
                //                AwardTransmission lastTransmission = getLatestAwardTransmission(hierarchy.getRoot().getAward(), true);
                //                if  (lastTransmission != null) {
                //                    AwardTransmissionChild lastChildTransmission = findMatchingTransmissionChild(lastTransmission.getTransmissionChildren(), award.getAwardNumber());
                //                    if (lastChildTransmission != null) {
                //                        offCampus = lastChildTransmission.getOffCampus();
                //                    }
                //                }
                AwardTransmissionChild lastTransmittedChild = getLatestChildAwardTransmitted(
                        hierarchy.getRoot().getAward(), award.getAwardNumber(), true);
                if (lastTransmittedChild != null) {
                    offCampus = lastTransmittedChild.getOffCampus();
                }

            } else if (budget != null) {
                offCampus = budget.getOnOffCampusFlag();
            }
            sponsoredProgramStructure.setZZOFFCAMPUS(offCampus);
            GlobalVariables.getUserSession().addObject("offCampus-".concat(award.getAwardNumber()),
                    (Object) offCampus);
            LOG.info("Writing offCampus to session: " + offCampus);

            // Position 16
            String baseCode = null;
            if (!awardBudgetVersionToBePosted) {

                // BU Customization ID: N/A mkousheh 20120504 - Get the latest child that matches that was successful transmitted
                //                AwardTransmission lastTransmission = getLatestAwardTransmission(hierarchy.getRoot().getAward(), true);
                //                if  (lastTransmission != null) {
                //                    AwardTransmissionChild lastChildTransmission = findMatchingTransmissionChild(lastTransmission.getTransmissionChildren(), award.getAwardNumber());
                //                    if (lastChildTransmission != null) {
                //                        baseCode = lastChildTransmission.getBaseCode();
                //                    }
                //                }

                AwardTransmissionChild lastTransmittedChild = getLatestChildAwardTransmitted(
                        hierarchy.getRoot().getAward(), award.getAwardNumber(), true);
                if (lastTransmittedChild != null) {
                    baseCode = lastTransmittedChild.getBaseCode();
                }

            } else if (budget != null) { // AAP removed per whit -  && awardBudgetVersionToBePosted
                baseCode = budget.getOhRateClassCode();
            }
            sponsoredProgramStructure.setBASECODE(baseCode);
            GlobalVariables.getUserSession().addObject("baseCode-".concat(award.getAwardNumber()),
                    (Object) baseCode);
            LOG.info("Writing baseCode to session: " + baseCode);

            // Position 20
            sponsoredProgramStructure.setDESCRIPTION(helper.getChildDescription());

            // Position 21
            // BU Customization ID: N/A mkousheh 20110816 - SUBRecipient no longer needed
            // ZFIGMSUBRECIPIENTTAB awardApprovedSubawards = objectFactory.createZFIGMSUBRECIPIENTTAB();
            // sponsoredProgramStructure.setTSUBRECIPIENT(awardApprovedSubawards);
            // List<AwardApprovedSubaward> subawards = award.getAwardApprovedSubawards();
            // for (AwardApprovedSubaward subaward : subawards) {
            //      GMSUBRECIPIENT awardApprovedSubaward = objectFactory.createGMSUBRECIPIENT();
            //      awardApprovedSubawards.getItem().add(awardApprovedSubaward);
            //      awardApprovedSubaward.setSUBRECIPIENT(subaward.getOrganizationId());
            //      awardApprovedSubaward.setSUBNAME(subaward.getOrganizationName());
            //      awardApprovedSubaward.setSUBAMOUNT(subaward.getAmount().bigDecimalValue());
            // }

            // AwardApprovedSubaward awardApprovedSubaward = getMostRecentAwardApprovedSubaward(award);
            // if (awardApprovedSubaward != null) {
            //      ZGMSUBRECIPIENT subRecipent = new ZGMSUBRECIPIENT();
            //      subRecipent.setSUBRECIPIENT(awardApprovedSubaward.getOrganizationId());
            //      subRecipent.setSUBNAME(awardApprovedSubaward.getOrganizationName());
            //      subRecipent.setSUBAMOUNT(awardApprovedSubaward.getAmount().bigDecimalValue());
            //      sponsoredProgramStructure.setZZSUBRECIPIENT(subRecipent);
            //      sponsoredProgramStructure.setZZSUBRECIPIENT(awardApprovedSubaward.getOrganizationId());
            // }

            // Position 22
            sponsoredProgramStructure.setKCRMSTATUS(StringUtils.leftPad(award.getStatusCode().toString(), 2, "0"));

            // Position 23
            sponsoredProgramStructure.setSPONSOR(award.getSponsorCode());

            // Position 24
            sponsoredProgramStructure.setPRIMESPONSOR(award.getPrimeSponsorCode());

            /**
             * Specification section 1.7.6
             */

            ZFIGMSPRESPONSIBLETABKCRM sponsoredProgramPersons = objectFactory.createZFIGMSPRESPONSIBLETABKCRM();
            sponsoredProgramStructure.setTRESPONSIBLE(sponsoredProgramPersons);
            List<AwardPerson> persons = award.getProjectPersons();
            for (AwardPerson person : persons) {
                ZGMSPRESPONSIBLEKCRM sponsoredProgramPerson = objectFactory.createZGMSPRESPONSIBLEKCRM();
                sponsoredProgramPersons.getItem().add(sponsoredProgramPerson);
                sponsoredProgramPerson.setSPONSOREDPROG(award.getAccountNumber());
                sponsoredProgramPerson.setOBJECTID(person.getPersonId());
                sponsoredProgramPerson.setOBJECTNAME(person.getFullName());

                // BUKC-0062: SAP Interface - Handle multiple PIs for NIH awards when transmit to SAP
                if (person.isMultiplePi()) {
                    sponsoredProgramPerson.setRESPONSIBILITY("PI");
                } else {
                    sponsoredProgramPerson.setRESPONSIBILITY(
                            convertProjectRoleToResponsibility(person.getContactRoleCode(), award));

                }
            }

            // BU Customization ID: N/A mkousheh 20110828 - Add DAs to SponsoredPrograms
            for (AwardUnitContact awardContact : award.getAwardUnitContacts()) {
                if ("2".equals(awardContact.getUnitAdministratorTypeCode())) {
                    ZGMSPRESPONSIBLEKCRM sponsoredProgramPerson = objectFactory.createZGMSPRESPONSIBLEKCRM();
                    sponsoredProgramPersons.getItem().add(sponsoredProgramPerson);
                    sponsoredProgramPerson.setOBJECTID(awardContact.getPersonId());
                    sponsoredProgramPerson.setOBJECTNAME(awardContact.getFullName());
                    sponsoredProgramPerson.setRESPONSIBILITY("DA");
                }
                if ("6".equals(awardContact.getUnitAdministratorTypeCode())) {
                    ZGMSPRESPONSIBLEKCRM sponsoredProgramPerson = objectFactory.createZGMSPRESPONSIBLEKCRM();
                    sponsoredProgramPersons.getItem().add(sponsoredProgramPerson);
                    sponsoredProgramPerson.setOBJECTID(awardContact.getPersonId());
                    sponsoredProgramPerson.setOBJECTNAME(awardContact.getPerson().getFullName());
                    sponsoredProgramPerson.setRESPONSIBILITY("OAV");
                }

                // BUKC-0140: Add new Admin Type (Clinical Trial Admin)
                if ("8".equals(awardContact.getUnitAdministratorTypeCode())) {
                    ZGMSPRESPONSIBLEKCRM sponsoredProgramPerson = objectFactory.createZGMSPRESPONSIBLEKCRM();
                    sponsoredProgramPersons.getItem().add(sponsoredProgramPerson);
                    sponsoredProgramPerson.setOBJECTID(awardContact.getPersonId());
                    sponsoredProgramPerson.setOBJECTNAME(awardContact.getPerson().getFullName());
                    sponsoredProgramPerson.setRESPONSIBILITY("CTAD");
                }
            }
        }

        /**
         *  Data which is only part of 1.7.5
         */
        ScaleTwoDecimal costSharCommitmentMinusMemoMatch = getTotalCostShareCommitmentAmountMinusMemoMatch(award);

        if (costSharing && costSharCommitmentMinusMemoMatch != null
                && costSharCommitmentMinusMemoMatch.isPositive()) {

            // Position 2
            // Sponsored_Program_type - according to Whit - this does not need to be sent

            // Position 10
            sponsoredProgramStructure.setZZORDCAT("CS1");

            // Position 14
            sponsoredProgramStructure.setZZOFFCAMPUS("Y");

            // Position 21
            sponsoredProgramStructure.setKCRMSTATUS(award.getStatusCode().toString());

            sponsoredProgramStructure.setBUDGETTDC(costSharCommitmentMinusMemoMatch.bigDecimalValue()); // getTotalDirectCost()

            // BU Customization ID: N/A mkousheh 20120731 - Remove the work around as SAP team fixed the issue on their side
            /*
            // Temporary fix for Cost Sharing.  When we send an update for a Cost Sharing Sponsored Program the Budget_TDC and Budget_FA should default to 0.00
            AwardTransmission lastTransmission = getLatestAwardTransmission(hierarchy.getRoot().getAward(), true);
            if (lastTransmission != null) {
            AwardTransmissionChild lastChildTransmission = findMatchingTransmissionChild(lastTransmission.getTransmissionChildren(), award.getAwardNumber());
            if (isCostSharing(award) && helper.getLastTransmissionDate() != null) {
                sponsoredProgramStructure.setBUDGETTDC(ScaleTwoDecimal.ZERO.bigDecimalValue());
                sponsoredProgramStructure.setBUDGETFA(ScaleTwoDecimal.ZERO.bigDecimalValue());
                
            }
            }
            // end temporary fix
                
             */

            // BU Customization ID: N/A mkousheh 20120808 - Set 000.00 for overhead rate when cost sharing
            sponsoredProgramStructure.setOVERHEADKEY("000.00");

            AwardCostShare awardCostShare = getMostRecentAwardCostShare(award);
            if (awardCostShare != null && awardCostShare.getDestination() != null) {
                sponsoredProgramStructure.setSPONSOREDPROG(awardCostShare.getDestination());
            } else {
                sponsoredProgramStructure.setSPONSOREDPROG(award.getAwardNumber());
            }

        }

        return sponsoredProgramStructure;
    }

    private ScaleTwoDecimal getTotalCostShareCommitmentAmountMinusMemoMatch(Award award) {
        List<AwardCostShare> awardCostSharesMinusMemoMatch = new ArrayList<AwardCostShare>();

        for (AwardCostShare awardCostShare : award.getAwardCostShares()) {
            if (!new Integer(3).equals(awardCostShare.getCostShareTypeCode())) {
                awardCostSharesMinusMemoMatch.add(awardCostShare);
            }
        }

        return getTotalAmount(awardCostSharesMinusMemoMatch);
    }

    ScaleTwoDecimal getTotalAmount(List<? extends ValuableItem> valuableItems) {
        ScaleTwoDecimal returnVal = new ScaleTwoDecimal(0.00);
        for (ValuableItem item : valuableItems) {
            ScaleTwoDecimal amount = item.getAmount() != null ? item.getAmount() : new ScaleTwoDecimal(0.00);
            returnVal = returnVal.add(amount);
        }
        return returnVal;
    }

    /**
     * Calculates the applicable F and A rate for the given Award using the current system time as the effective date.
     *
     * @param award the award for whic to calculate the rate
     * @return the value of the rate as a BigDecimal
     * @throws IllegalStateException if the rate could not be found for the current date/time
     */
    private BigDecimal calculateApplicableFandARate(Award award) {
        ScaleTwoDecimal fAndARate = getBudgetRateAndBaseService().calculateApplicableFandARate(award);
        if (fAndARate == null) {
            return null;
        }
        return fAndARate.bigDecimalValue();
    }

    /**
     * Converts the given child type to a format that SAP can understand according to the following mapping:
     * <p/>
     * If "Standard; Child_Type = ST1
     * If "Fabrication", Child_Type =  P01
     * If "Participant Support Costs", Child_Type = PS1
     * If "Subaward", Child_Type = SA1
     *
     * @param childType the child type to convert
     * @return the converted child type value
     */
    private String convertChildType(String childType, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.CHILD_TYPE_MAPPING, childType);
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert child type, given child type value was not understood: " + childType
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    /**
     * Implements section 1.7.5 of the specification.
     * <p/>
     * A sponsored program for cost sharing will only get created if the award which is passed in supports cost sharing.
     *
     * @return the sponsored program information for the cost sharing award, or null if the award does not support cost sharing
     */
    protected ZGMSPPROGRAMSTRUCTURE processCostSharing(Award award) {
        ScaleTwoDecimal costSharCommitmentMinusMemoMatch = getTotalCostShareCommitmentAmountMinusMemoMatch(award);
        if (isCostSharing(award) && costSharCommitmentMinusMemoMatch.isPositive()) {
            return constructSponsoredProgram(award, true);
        }
        return null;
    }

    /**
     * Cost Sharing Exists when data for an Award exists on Award_Cost_Share and the Commitment Amount > 0
     */
    protected boolean isCostSharing(Award award) {
        AwardCostShare awardCostShare = getMostRecentAwardCostShare(award);
        if (awardCostShare != null) {
            if (awardCostShare.getCommitmentAmount() != null && awardCostShare.getCommitmentAmount().isPositive()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Implements section 1.7.9 of functional specification which relates to Billing Plans.
     */
    protected ZGMBILLINGPLANSTRUCTURE constructBillingPlan(Award award) {

        AwardPaymentSchedule schedule = getMostRecentAwardPaymentSchedule(award);

        if (schedule == null) {
            return null;
        }

        ZGMBILLINGPLANSTRUCTURE billingPlan = objectFactory.createZGMBILLINGPLANSTRUCTURE();
        billingPlan.setBILLINGDATE(dateToString(schedule.getDueDate()));
        billingPlan.setBILLINGVALUE(schedule.getAmount().bigDecimalValue());

        return billingPlan;
    }

    /**
     * Implements section 1.8 of functional spec related to Sponsor data mapping.
     */
    protected ZGMSPONSORSTRUCTURE constructSponsor(Award award) {

        Sponsor sponsor = award.getSponsor();
        return constructSponsor(award, sponsor);
    }

    /**
     * Implements section 1.8 of functional spec related to Sponsor data mapping.
     */
    protected ZGMSPONSORSTRUCTURE constructSponsor(Award award, Sponsor sponsor) {

        Rolodex rolodex = sponsor.getRolodex();

        ZGMSPONSORSTRUCTURE sponsorStructure = objectFactory.createZGMSPONSORSTRUCTURE();

        // Position 1
        sponsorStructure.setSPONSOR(sponsor.getSponsorCode());

        // Position 2
        sponsorStructure.setSPONSORTYPE(convertSponsorCodeToSponsorType(sponsor.getSponsorTypeCode(), award));

        // Position 3
        sponsorStructure.setSPONSORNAME(sponsor.getSponsorName());

        // Position 4
        sponsorStructure.setCONTACTPERSON(rolodex.getFirstName() + " " + rolodex.getLastName());

        // Position 5
        sponsorStructure.setADDRESSLINE1(rolodex.getAddressLine1());

        // Position 6
        sponsorStructure.setADDRESSLINE2(rolodex.getAddressLine2());

        // Position 7
        sponsorStructure.setCITY(rolodex.getCity());

        // Position 8
        sponsorStructure.setSTATE(rolodex.getState());

        // Position 9
        sponsorStructure.setPOSTCODE1(rolodex.getPostalCode());

        // Position 10
        sponsorStructure.setCOUNTRY(rolodex.getCountryCode());

        // Position 11
        sponsorStructure.setTELNUMBER(rolodex.getPhoneNumber());

        // Position 12
        sponsorStructure.setFAXNUMBER(rolodex.getFaxNumber());

        // Position 13
        sponsorStructure.setFUND(sponsor.getDodacNumber());

        // BU Customization ID: N/A mkousheh 20110712 - Special case where a Prime Sponsor present then use
        // DODAC from Prime Sponsor
        if (award.getPrimeSponsor() != null) {
            sponsorStructure.setFUND(award.getPrimeSponsor().getDodacNumber());
        }

        return sponsorStructure;
    }

    /**
     * Construct a sponsor structure for an Organization from a SubAward
     */
    protected ZGMSPONSORSTRUCTURE constructSponsorFromOrganization(Award award, Organization organization) {

        Sponsor sponsor = businessObjectService.findBySinglePrimaryKey(Sponsor.class,
                organization.getOrganizationId());
        if (sponsor == null) {
            throw new IllegalStateException("Failed to locate a Sponsor record for the given organization with id: "
                    + organization.getOrganizationId());
        }

        Rolodex rolodex = organization.getRolodex();

        ZGMSPONSORSTRUCTURE sponsorStructure = objectFactory.createZGMSPONSORSTRUCTURE();

        // Position 1
        sponsorStructure.setSPONSOR(organization.getOrganizationId());

        // Position 2

        sponsorStructure.setSPONSORTYPE(convertSponsorCodeToSponsorType(sponsor.getSponsorTypeCode(), award));

        // Position 3
        sponsorStructure.setSPONSORNAME(organization.getOrganizationName());

        // Position 4
        sponsorStructure.setCONTACTPERSON(rolodex.getFirstName() + " " + rolodex.getLastName());

        // Position 5
        sponsorStructure.setADDRESSLINE1(rolodex.getAddressLine1());

        // Position 6
        sponsorStructure.setADDRESSLINE2(rolodex.getAddressLine2());

        // Position 7
        sponsorStructure.setCITY(rolodex.getCity());

        // Position 8
        sponsorStructure.setSTATE(rolodex.getState());

        // Position 9
        sponsorStructure.setPOSTCODE1(rolodex.getPostalCode());

        // Position 10
        sponsorStructure.setCOUNTRY(rolodex.getCountryCode());

        // Position 11
        sponsorStructure.setTELNUMBER(rolodex.getPhoneNumber());

        // Position 12
        sponsorStructure.setFAXNUMBER(rolodex.getFaxNumber());

        // Position 13
        sponsorStructure.setFUND(organization.getDodacNumber());

        return sponsorStructure;
    }

    /**
     * Construct a sponsor structure from an AwardSponsorContact representing the billing partner
     */
    protected ZGMSPONSORSTRUCTURE constructSponsorFromBillingPartner(Award award,
            AwardSponsorContact billingPartner) {

        RolodexContract rolodex = billingPartner.getRolodex();

        if (rolodex == null) {
            if (billingPartner.getRolodexId() != null) {
                rolodex = getRolodexService().getRolodex(billingPartner.getRolodexId());
            }
            if (rolodex == null) {
                throw new IllegalStateException(
                        "Billing partner does not have a rolodex entry.  Award contact id of billing partner is: "
                                + billingPartner.getAwardContactId() + ", rolodex id is: "
                                + billingPartner.getRolodexId());
            }
        }

        ZGMSPONSORSTRUCTURE sponsorStructure = objectFactory.createZGMSPONSORSTRUCTURE();

        // Position 1
        sponsorStructure.setSPONSOR(rolodex.getRolodexId().toString());

        // Position 2
        sponsorStructure.setSPONSORTYPE(DEFAULT_SPONSOR_TYPE_FOR_BILLING_PARTNER);

        // Position 3
        sponsorStructure.setSPONSORNAME(billingPartner.getContactOrganizationName());

        // Position 4
        sponsorStructure.setCONTACTPERSON(rolodex.getFirstName() + " " + rolodex.getLastName());

        // Position 5
        sponsorStructure.setADDRESSLINE1(rolodex.getAddressLine1());

        // Position 6
        sponsorStructure.setADDRESSLINE2(rolodex.getAddressLine2());

        // Position 7
        sponsorStructure.setCITY(rolodex.getCity());

        // Position 8
        sponsorStructure.setSTATE(rolodex.getState());

        // Position 9
        sponsorStructure.setPOSTCODE1(rolodex.getPostalCode());

        // Position 10
        sponsorStructure.setCOUNTRY(rolodex.getCountryCode());

        // Position 11
        sponsorStructure.setTELNUMBER(rolodex.getPhoneNumber());

        // Position 12
        sponsorStructure.setFAXNUMBER(rolodex.getFaxNumber());

        // Position 13
        // TODO should a fund be passed?
        //sponsorStructure.setFund(?);

        return sponsorStructure;
    }

    private <T extends SequenceAssociate<?>> T getMostRecentSequenceAssociate(List<T> sequenceAssociates) {
        Collections.sort(sequenceAssociates, new Comparator<SequenceAssociate<?>>() {
            public int compare(SequenceAssociate<?> sequence1, SequenceAssociate<?> sequence2) {
                return sequence1.getSequenceNumber().compareTo(sequence2.getSequenceNumber());
            }
        });
        return sequenceAssociates.get(sequenceAssociates.size() - 1);
    }

    private AwardApprovedSubaward getMostRecentAwardApprovedSubaward(Award award) {
        List<AwardApprovedSubaward> awardApprovedSubawards = award.getAwardApprovedSubawards();
        if (awardApprovedSubawards == null || awardApprovedSubawards.isEmpty()) {
            return null;
        }
        return getMostRecentSequenceAssociate(awardApprovedSubawards);
    }

    private AwardPersonUnitCreditSplit getMostRecentCreditSplit(AwardPersonUnit awardPersonUnit) {
        List<AwardPersonUnitCreditSplit> creditSplits = awardPersonUnit.getCreditSplits();
        if (creditSplits.isEmpty()) {
            // credit split panel not being used any more
            //            throw new IllegalStateException("Person unit has no credit splits.");
            return null;
        }
        return getMostRecentSequenceAssociate(creditSplits);
    }

    private AwardReportTerm getMostRecentAwardReportTerm(Award award) {
        List<AwardReportTerm> reportTerms = award.getAwardReportTermItems();
        if (reportTerms.isEmpty()) {
            throw new IllegalStateException("Award has no report terms.");
        }
        return getMostRecentSequenceAssociate(reportTerms);
    }

    private AwardPaymentSchedule getMostRecentAwardPaymentSchedule(Award award) {
        List<AwardPaymentSchedule> schedules = award.getPaymentScheduleItems();
        if (schedules == null || schedules.isEmpty()) {
            return null;
        }
        return getMostRecentSequenceAssociate(schedules);
    }

    private AwardCostShare getMostRecentAwardCostShare(Award award) {
        List<AwardCostShare> costShares = award.getAwardCostShares();
        if (costShares.isEmpty()) {
            return null;
        }
        return getMostRecentSequenceAssociate(costShares);
    }

    private AwardSponsorTerm getMostRecentAwardSponsorTerm(Award award) {
        List<AwardSponsorTerm> awardSponsorTerms = award.getAwardSponsorTerms();
        if (awardSponsorTerms.isEmpty()) {
            return null;
        }
        return getMostRecentSequenceAssociate(awardSponsorTerms);
    }

    /**
     * Implement section 1.7.8 regarding sending of the sponsored program group
     */
    private SPONSOREDPROGRAMSGRP constructSponsoredProgramGroups(Award award) {

        SPONSOREDPROGRAMSGRP programGroups = objectFactory.createZGMKCRMINTERFACESPONSOREDPROGRAMSGRP();
        AwardHierarchy hierarchy = getAwardHierarchyService().loadAwardHierarchy(award.getAwardNumber());
        for (AwardHierarchy childHierarchy : hierarchy.getChildren()) {
            if ("Group".equalsIgnoreCase(((AwardExtension) award.getExtension()).getChildType())) {
                //                ZGMSPPROGRAMGRPSTRUCTURE programGroup = objectFactory.createZGMSPPROGRAMGRPSTRUCTURE();
                //                programGroup.setPARENTAWARD(childHierarchy.getParentAwardNumber());
                //                programGroup.setCHILDAWARD(childHierarchy.getAwardNumber());
                //                programGroup.setTITLE(childHierarchy.getAward().getTitle());
                //                programGroup.setSPONSOREDPROG(childHierarchy.getAward().getAccountNumber());
                //                programGroups.getItem().add(programGroup);
                generateProgramGroupChildren(programGroups, childHierarchy);
            }
        }

        return programGroups;
    }

    private void generateProgramGroupChildren(SPONSOREDPROGRAMSGRP programGroups, AwardHierarchy awardHierarchy) {
        for (AwardHierarchy childHierarchy : awardHierarchy.getChildren()) {
            ZGMSPPROGRAMGRPSTRUCTURE childProgramGroup = objectFactory.createZGMSPPROGRAMGRPSTRUCTURE();
            childProgramGroup.setPARENTAWARD(childHierarchy.getParentAwardNumber());
            childProgramGroup.setCHILDAWARD(childHierarchy.getAwardNumber());
            childProgramGroup.setTITLE(childHierarchy.getAward().getTitle());
            childProgramGroup.setSPONSOREDPROG(childHierarchy.getAward().getAccountNumber());
            programGroups.getItem().add(childProgramGroup);
            if ("Group"
                    .equalsIgnoreCase(((AwardExtension) (childHierarchy.getAward().getExtension())).getChildType())
                    && childHierarchy.hasChildren()) {
                generateProgramGroupChildren(programGroups, childHierarchy);
            }
        }
    }

    /**
     * Extracts sponsored program ids from the given sponsored programs message responses.
     * <p/>
     * This allows us to implement section 1.6 of the functional specification and deal with the cases where an award is being assigned a
     * new sponsored program id from SAP.
     * <p/>
     * TODO for now assume one-to-one mapping between sponsored program message and passed in sponsored programs?
     *
     * @param transmission              the original award transmission data
     * @param sponsoredProgramsMessages the sponsored programs message responses from the SAP service
     * @return a populated Map containing the award id as the key and it's corresponding sponsored program id as the value
     */
    private Map<Long, String> extractSponsoredProgramIds(List<Long> interfacedSponsoredProgramIds,
            SapTransmission transmission, SPONSOREDPROGRAMSMESSAGES sponsoredProgramsMessages) {
        Map<Long, String> sponsoredProgramIds = new HashMap<Long, String>();
        if (sponsoredProgramsMessages != null && sponsoredProgramsMessages.getItem() != null) {
            if (sponsoredProgramsMessages.getItem().size() != interfacedSponsoredProgramIds.size()) {
                throw new IllegalStateException(
                        "The number of sponsored program messages returned should be equal to the number of sponsored programs interfaced.  "
                                + "Instead " + interfacedSponsoredProgramIds.size()
                                + " sponsored programs were interfaced and "
                                + sponsoredProgramsMessages.getItem().size()
                                + " sponsored program messages were returned.");
            }
            for (int index = 0; index < sponsoredProgramsMessages.getItem().size(); index++) {
                ZSPPROGRAMMESSAGES sponsoredProgramMessage = sponsoredProgramsMessages.getItem().get(index);
                Long awardId = interfacedSponsoredProgramIds.get(index);
                sponsoredProgramIds.put(awardId, sponsoredProgramMessage.getSPONSOREDPROG());
            }
        }
        return sponsoredProgramIds;
    }

    /**
     * Run set of BU custom validation before transmitting award to SAP
     *
     * @param transmission in progress
     * @return Validation results object
     */
    public ValidationResults validate(SapTransmission transmission) {
        ValidationResults validationResults = new ValidationResults();

        // transmission should have both a parent award and at least one child
        if (transmission.getAward() == null) {
            ErrorMessageKeys.NO_PARENT_AWARD.populate(validationResults);
        } else {
            checkPrimaryAwardForChanges(transmission.getAward(), validationResults);
            for (Award childAward : transmission.getChildAwards()) {
                checkChildAwardForChanges(transmission.getAward(), childAward, validationResults);
            }

            CustomAwardDataHelper helper = new CustomAwardDataHelper(transmission.getAward());
            if (helper.getLastTransmissionDate() == null) {
                if (transmission.getChildAwards() == null || transmission.getChildAwards().isEmpty()) {
                    ErrorMessageKeys.NO_CHILDREN_AWARD.populate(validationResults);
                }
            }

            // BU Customization ID: N/A mkousheh 20120713 - Add validation to prevent transmission if Parent transaction
            // type is "No Cost Extension" or "Administrative Change" and the selected child's budget status is "to be posted"
            if (transmission.getAward().getAwardTransactionTypeCode()
                    .equals(AWARD_TRANSACTION_TYPE_NO_COST_EXTENSION)
                    || transmission.getAward().getAwardTransactionTypeCode()
                            .equals(AWARD_TRANSACTION_TYPE_ADMINISTRATION_CHANGE)) {
                for (Award childAward : transmission.getChildAwards()) {

                    AwardBudgetExt abvoe = getLastBudgetVersion(childAward.getAwardDocument());
                    boolean awardBudgetVersionToBePosted = false;
                    if (abvoe != null) {
                        String budgetStatus = abvoe.getAwardBudgetStatusCode();
                        awardBudgetVersionToBePosted = Constants.BUDGET_STATUS_CODE_TO_BE_POSTED
                                .equalsIgnoreCase(budgetStatus);
                    }

                    if (awardBudgetVersionToBePosted) {
                        ErrorMessageKeys.PARENT_TRANSACTION_TYPE_NOCOST_EXT_OR_ADMIN_CHANGE.populate(
                                transmission.getAward().getAwardId(), validationResults,
                                transmission.getAward().getAwardTransactionType().getDescription(),
                                childAward.getAwardNumber(),
                                transmission.getAward().getSequenceNumber().toString());
                        break;
                    }
                }
            }

            if (transmission.getAward().getAccountTypeCode() == 0) {
                ErrorMessageKeys.NO_PARENT_ACCOUNT_TYPE.populate(validationResults);
            }
            if (transmission.getAward().getAwardAmountInfos().size() > 0) {
                String timeAndMoneyDocumentNumber = transmission.getAward().getLastAwardAmountInfo()
                        .getTimeAndMoneyDocumentNumber();

                try {
                    if (StringUtils.isNotBlank(timeAndMoneyDocumentNumber)) {
                        TimeAndMoneyDocument timeAndMoneyDocument = (TimeAndMoneyDocument) getDocumentService()
                                .getByDocumentHeaderId(timeAndMoneyDocumentNumber);
                        if (timeAndMoneyDocument != null
                                && timeAndMoneyDocument.getDocumentHeader().hasWorkflowDocument()
                                && !timeAndMoneyDocument.getDocumentHeader().getWorkflowDocument().isFinal()) {
                            ErrorMessageKeys.TIME_MONEY_NOT_FINAL.populate(validationResults);
                        }
                    }
                } catch (WorkflowException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }

        // only proceed with full validation if no validation errors were raised in previous step
        if (validationResults.isEmpty()) {
            validateAward(validationResults, transmission.getAward(), true);
            for (Award childAward : transmission.getChildAwards()) {
                validateAward(validationResults, childAward, false);
            }
        }

        return validationResults;
    }

    /**
     * Find the previous transmission data
     *
     * @param rootAward        - parent award object
     * @param getLastSuccesful retuns whether there was a successful transmission or not
     * @return last transmission object
     */
    private AwardTransmission getLatestAwardTransmission(Award rootAward, boolean getLastSuccesful) {
        AwardTransmission latestTransmission = null;

        if (getLastSuccesful) {
            for (AwardTransmission awardTransmission : ((AwardExtension) rootAward.getExtension())
                    .getAwardTransmissions()) {
                if (awardTransmission.isSuccess()) {
                    latestTransmission = awardTransmission;
                }
            }
        } else {
            if (((AwardExtension) rootAward.getExtension()).getAwardTransmissions() != null
                    && ((AwardExtension) rootAward.getExtension()).getAwardTransmissions().size() > 0)
                latestTransmission = ((AwardExtension) rootAward.getExtension()).getAwardTransmissions()
                        .get(((AwardExtension) rootAward.getExtension()).getAwardTransmissions().size() - 1);
        }

        return latestTransmission;
    }

    // BU Customization ID: N/A mkousheh 20120505 - Return the latest award child that was successfully transmitted
    private AwardTransmissionChild getLatestChildAwardTransmitted(Award rootAward, String awardNumber,
            boolean getLastSuccesful) {
        AwardTransmission latestTransmissionWithMatchChild = null;

        List<AwardTransmission> awardTransmissionReversed = ((AwardExtension) rootAward.getExtension())
                .getAwardTransmissions();

        // reverse award transmission list
        Collections.sort(awardTransmissionReversed, new Comparator<AwardTransmission>() {
            public int compare(AwardTransmission awardTransmission1, AwardTransmission awardTransmission2) {
                return ((Long) awardTransmission1.getTransmissionId())
                        .compareTo((Long) awardTransmission2.getTransmissionId());
            }
        });
        Collections.reverse(awardTransmissionReversed);

        if (getLastSuccesful) {
            for (AwardTransmission awardTransmission : (List<AwardTransmission>) awardTransmissionReversed) {
                //for (AwardTransmission awardTransmission : rootAward.getAwardTransmissions()) {

                if (awardTransmission.isSuccess()) {
                    for (AwardTransmissionChild awardTransmissionChild : awardTransmission
                            .getTransmissionChildren()) {
                        if (awardNumber.equals(awardTransmissionChild.getAwardNumber())) {
                            return awardTransmissionChild;
                        }
                    }
                }
            }
        } else {
            if (((AwardExtension) rootAward.getExtension()).getAwardTransmissions() != null
                    && ((AwardExtension) rootAward.getExtension()).getAwardTransmissions().size() > 0) {
                // latestTransmission = rootAward.getAwardTransmissions().get(rootAward.getAwardTransmissions().size() - 1);
                for (AwardTransmissionChild awardTransmissionChild : ((AwardExtension) rootAward.getExtension())
                        .getAwardTransmissions()
                        .get(((AwardExtension) rootAward.getExtension()).getAwardTransmissions().size() - 1)
                        .getTransmissionChildren()) {
                    if (awardNumber.equals(awardTransmissionChild.getAwardNumber())) {
                        return awardTransmissionChild;
                    }
                }
            }
        }
        return null;
    }

    private void checkPrimaryAwardForChanges(Award award, ValidationResults validationResults) {
        AwardTransmission lastTransmission = getLatestAwardTransmission(award, true);

        if (award != null && lastTransmission != null && lastTransmission.isSuccess()) {
            if (award.getAccountTypeCode() != null && lastTransmission.getAccountTypeCode() != null
                    && award.getAccountTypeCode().compareTo(lastTransmission.getAccountTypeCode()) != 0) {
                ErrorMessageKeys.PARENT_ACCOUNT_TYPE_CANNOT_CHANGE.populate(award.getAwardId(), validationResults,
                        award.getAwardNumber(), lastTransmission.getAccountTypeCode().toString(),
                        award.getAccountTypeCode().toString());
            }

            // BU Customization ID: N/A mkousheh 20121130 - Issue 82 only prevent changing Payment
            // Basis Cost reimbursement (Resource Related Billing) (1) to Milestone (4) or vice-versa
            if ((RRB_BASIS_OF_PAYMENT.equals(award.getBasisOfPaymentCode())
                    && MILESTONE_BASIS_OF_PAYMENT.equals(lastTransmission.getBasisOfPaymentCode()))
                    || (MILESTONE_BASIS_OF_PAYMENT.equals(award.getBasisOfPaymentCode())
                            && RRB_BASIS_OF_PAYMENT.equals(lastTransmission.getBasisOfPaymentCode()))) {
                ErrorMessageKeys.PARENT_PAYMENT_BASIS_CANNOT_CHANGE.populate(award.getAwardId(), validationResults,
                        award.getAwardNumber(), lastTransmission.getBasisOfPaymentCode(),
                        award.getBasisOfPaymentCode());
            }

            /*
                        if (!MANUAL_BASIS_OF_PAYMENT.equalsIgnoreCase(lastTransmission.getBasisOfPaymentCode())
                && !StringUtils.equals(award.getBasisOfPaymentCode(), lastTransmission.getBasisOfPaymentCode())) {
            ErrorMessageKeys.PARENT_PAYMENT_BASIS_CANNOT_CHANGE.populate(award.getAwardId(), validationResults, award.getAwardNumber(), lastTransmission.getBasisOfPaymentCode(), award.getBasisOfPaymentCode());
                        }
            */

            if (DHHS_LOC_MATHOD_OF_PAYMENT.equalsIgnoreCase(lastTransmission.getMethodOfPaymentCode())
                    && !StringUtils.equals(award.getMethodOfPaymentCode(),
                            lastTransmission.getMethodOfPaymentCode())) {
                ErrorMessageKeys.PARENT_PAYMENT_METHOD_CANNOT_CHANGE.populate(award.getAwardId(), validationResults,
                        award.getAwardNumber(), lastTransmission.getMethodOfPaymentCode(),
                        award.getMethodOfPaymentCode());
            }
            if (!StringUtils.equals(award.getSponsorCode(), lastTransmission.getSponsorCode())) {
                ErrorMessageKeys.PARENT_SPONSOR_CODE_CANNOT_CHANGE.populate(award.getAwardId(), validationResults,
                        award.getAwardNumber(), lastTransmission.getSponsorCode(), award.getSponsorCode());
            }
        }
    }

    private AwardTransmissionChild findMatchingTransmissionChild(List<AwardTransmissionChild> transmissionChildren,
            String awardNumber) {
        for (AwardTransmissionChild awardTransmissionChild : transmissionChildren) {
            if (awardNumber.equals(awardTransmissionChild.getAwardNumber())) {
                return awardTransmissionChild;
            }
        }

        return null;
    }

    private void checkChildAwardForChanges(Award primaryAward, Award award, ValidationResults validationResults) {
        // BU Customization ID: N/A mkousheh 20120504 - Return the latest award child that was successfully transmitted
        AwardTransmissionChild lastTransmittedChild = getLatestChildAwardTransmitted(primaryAward,
                award.getAwardNumber(), true);
        if (award != null && lastTransmittedChild != null) {
            if (!"group".equalsIgnoreCase(((AwardExtension) award.getExtension()).getChildType())
                    && !StringUtils.equals(((AwardExtension) award.getExtension()).getChildType(),
                            lastTransmittedChild.getChildType())) {
                ErrorMessageKeys.CHILD_CHILD_TYPE_CANNOT_CHANGE.populate(award.getAwardId(), validationResults,
                        award.getAwardNumber(), lastTransmittedChild.getChildType(),
                        ((AwardExtension) award.getExtension()).getChildType());
            }
            if (!StringUtils.equals(award.getLeadUnitNumber(), lastTransmittedChild.getLeadUnitNumber())) {
                ErrorMessageKeys.CHILD_LEAD_UNIT_CANNOT_CHANGE.populate(award.getAwardId(), validationResults,
                        award.getAwardNumber(), lastTransmittedChild.getLeadUnitNumber(),
                        award.getLeadUnitNumber());
            }
        }
    }

    private void validateAward(ValidationResults results, Award award, boolean parentAward) {
        // BU Customization ID: N/A mkousheh N/A - RSN:8311755 (remove validation)
        // BUKC-0028: Enable validation to identify child has no budget
        validateChildHasBudget(results, award, parentAward);

        validateAwardIsFinal(results, award);
        validateAwardDFAFSNumber(results, award);
        validateChildAwardType(results, award, parentAward);
        validateAccountType(results, award);
        validateDollarAmmount(results, award);

        // BU Customization ID: N/A mkousheh N/A - RSN:8311755
        // validateLeadUnit(results, award);

        validateProjectDates(results, award);

        // BUKC-0127: Remove validation for cost-share on child award (ENHC0012984)
        //validateCostSharingOnChildAward(results, award, parentAward);

        validateGroupAwardHasChildren(results, award);

        // BU Customization ID: N/A mkousheh N/A - RSN:8311717
        // validateFederalAward(results, award);

        validateNIHAward(results, award);
        validateDollarObligation(results, award);
        //validatePrimeSponsorOnSubAward(results, award);
    }

    private void validateAwardIsFinal(ValidationResults results, Award award) {
        AwardDocument awardDocument;
        VersionHistoryService vhs = (VersionHistoryService) KcServiceLocator
                .getService(VersionHistoryService.class);
        try {
            VersionHistory versionHistory = vhs.findPendingVersion(Award.class, award.getAwardNumber(),
                    new Integer(award.getSequenceNumber() + 1).toString());
            if (versionHistory != null) {
                Award activeAward = (Award) versionHistory.getSequenceOwner();
                if (activeAward != null) {
                    awardDocument = (AwardDocument) getDocumentService()
                            .getByDocumentHeaderId(activeAward.getAwardDocument().getDocumentNumber());
                    if (awardDocument.getDocumentHeader().hasWorkflowDocument()) {
                        if (!awardDocument.getDocumentHeader().getWorkflowDocument().isFinal()) {
                            ErrorMessageKeys.AWARD_NOT_FINAL.populate(award.getAwardId(), results,
                                    activeAward.getAwardNumber());
                        }
                    } else {
                        ErrorMessageKeys.AWARD_DOCUMENT_NULL.populate(award.getAwardId(), results,
                                activeAward.getAwardNumber());
                    }
                } else {
                    // show error that there is no active award?
                    ErrorMessageKeys.AWARD_NOT_FINAL.populate(award.getAwardId(), results,
                            activeAward.getAwardNumber());
                }
            } else if (award.getAwardDocument().getDocumentHeader().hasWorkflowDocument()
                    && !award.getAwardDocument().getDocumentHeader().getWorkflowDocument().isFinal()) {
                ErrorMessageKeys.AWARD_NOT_FINAL.populate(award.getAwardId(), results, award.getAwardNumber());
            }
        } catch (WorkflowException e) {
            e.printStackTrace();
        }
    }

    private void validateAwardDFAFSNumber(ValidationResults results, Award award) {
        /*
        Integer methodOfPaymentCode = 0;
        try {
        methodOfPaymentCode = Integer.parseInt(award.getMethodOfPaymentCode());
        } finally {
        // do nothing
        }
        */
        String dfafsNumber = award.getDocumentFundingId();

        // BUKC-0041: Parameterize LOC (Line of Credit) method of payment to accommodate the new LOC codes added (Issue 62)
        if (isLocMethodOfPayment(award.getMethodOfPaymentCode()) && StringUtils.isBlank(dfafsNumber)) {
            // if (methodOfPaymentCode.compareTo(27) <= 0 && StringUtils.isBlank(dfafsNumber)) {
            ErrorMessageKeys.AWARD_DOCUMENT_NO_DOCUMENT_NUMBER.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }
    }

    private boolean isLocMethodOfPayment(String methodOfPaymentCode) {
        String locMethodOfPaymentList = parameterService.getParameterValueAsString(AwardDocument.class,
                BUConstants.BU_LOC_METHOD_OF_PAYMENT_CD);
        String[] termIds = locMethodOfPaymentList.split(",");

        return Arrays.asList(termIds).contains(methodOfPaymentCode);
    }

    /**
     * Child awards must have a budget.
     */
    private void validateChildHasBudget(ValidationResults results, Award award, boolean parentAward) {
        if (!parentAward) {
            AwardBudgetExt budget = award.getAwardDocument().getBudgetVersionOverview();
            if (budget.getBudgetId() == null) {
                ErrorMessageKeys.NO_BUDGET_ON_CHILD_AWARD.populate(award.getAwardId(), results,
                        award.getAwardNumber());
            }
        }
    }

    private void validateChildAwardType(ValidationResults results, Award award, boolean isParentAward) {
        if (!isParentAward && StringUtils.isBlank(((AwardExtension) award.getExtension()).getChildType())) {
            ErrorMessageKeys.NO_CHILD_TYPE_ON_CHILD_AWARD.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }
    }

    /**
     * Validates the following:
     * <p/>
     * <ol>
     * <li>Account Type is NULL; Account Type should always be populated</li>
     * <li>Account Type is Federal, and a non-Federal Sponsor is attached; Federal Account Types should only have Federal Sponsors.</li>
     * <li>Account Type is Non-Federal, and a Federal Sponsor is attached; Federal Account Types should only have Federal Sponsors.</li>
     * </ol>
     */
    private void validateAccountType(ValidationResults results, Award award) {
        if (award.getAccountTypeCode() == null) {
            ErrorMessageKeys.ACCOUNT_TYPE_NULL.populate(award.getAwardId(), results, award.getAwardNumber());
        } else {
            if (isFederalAccount(award) && isNonFederalSponsor(award)) {
                ErrorMessageKeys.FEDERAL_ACCOUNT_NON_FEDERAL_SPONSOR.populate(award.getAwardId(), results,
                        award.getAwardNumber());
            } else if (isNonFederalAccount(award) && isFederalSponsor(award)) {
                ErrorMessageKeys.NON_FEDERAL_ACCOUNT_FEDERAL_SPONSOR.populate(award.getAwardId(), results,
                        award.getAwardNumber());
            }
        }
    }

    private boolean isFederalAccount(Award award) {
        return award.getAccountTypeCode().equals(FEDERAL_CODE);
    }

    // BUKC-0035: Fix an issue with the code to compare the same data type for Federal/NonFederal issue
    private boolean isFederalSponsor(Award award) {
        // BUKC-0064: If Prime Sponsor present, validate against it rather that the Sponsor
        if (!(award.getPrimeSponsor() == null)) {
            return Integer.parseInt(award.getPrimeSponsor().getSponsorTypeCode()) == FEDERAL_CODE;
        } else {
            return Integer.parseInt(award.getSponsor().getSponsorTypeCode()) == FEDERAL_CODE;
        }
    }

    private boolean isNonFederalAccount(Award award) {
        return award.getAccountTypeCode().equals(NON_FEDERAL_CODE);
    }

    private boolean isNonFederalSponsor(Award award) {
        // BUKC-0029: Fix an issue with Acount Type validation (Federal/Non-Federal validation)
        // return true if Sposor is (non) federal - all should be consider non federal if type <> 1
        // return award.getSponsor().getSponsorTypeCode().equals(NON_FEDERAL_CODE);
        return (!isFederalSponsor(award));
    }

    /**
     * Validates if Dollar Amounts Appear on an Award Record that has Children; Dollar amounts can only appear
     * on child awards (that have no children of their own).
     */
    private void validateDollarAmmount(ValidationResults results, Award award) {
        AwardHierarchy awardHierarchy = award.getAwardHierarchyService().loadAwardHierarchy(award.getAwardNumber());
        if (awardHierarchy == null) {
            ErrorMessageKeys.AWARD_HIERARCHY_NULL.populate(award.getAwardId(), results, award.getAwardNumber());

        }

        // BU Customization ID: N/A mkousheh N/A - Based on Eddie's request
        /*
                } else if (awardHierarchy.hasChildren() && award.calculateObligatedDistributedAmountTotal().isGreaterThan(ScaleTwoDecimal.ZERO)) {
        ErrorMessageKeys.AWARD_DOLLAR_AMOUNT_ON_CHILD.populate(award.getAwardId(), results, award.getAwardNumber());
                }
        */
    }

    /**
     * Determines if the Lead Unit on the Award is invalid; The Lead Unit can only be populated with a
     * Child Node in the Unit Hierarchy.
     */
    private void validateLeadUnit(ValidationResults results, Award award) {
        List<Unit> subUnits = unitService.getSubUnits(award.getLeadUnitNumber());
        if (subUnits != null && !subUnits.isEmpty()) {
            ErrorMessageKeys.INVALID_LEAD_UNIT.populate(award.getAwardId(), results, award.getAwardNumber());
        }
    }

    /**
     * Validates the following:
     * <p/>
     * <ol>
     * <li>Sub-Award does not have a Prime Sponsor Listed; When the Award Type is listed as a Sub-award it needs to have a Prime Sponsor attached</li>
     * -    *   <li>Multiple Subawards are recorded on a Single Award; Only one subaward should be listed on an award.</li>
     * <li>Subawards are recorded on a Parent Award; Subawards can only be listed on child awards.</li>
     * </ol>
     */
    private void validateSubAwards(ValidationResults results, Award award, boolean parentAward) {

        if (parentAward && isSubAward(award) && StringUtils.isBlank(award.getPrimeSponsorCode())) {
            ErrorMessageKeys.SUB_AWARD_NO_PRIME_SPONSOR.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }

        // BU Customization ID: N/A mkousheh N/A - Per RSN:8311717
        // List<AwardApprovedSubaward> subAwards = award.getAwardApprovedSubawards();
        // if (subAwards != null && subAwards.size() > 1) {
        //    ErrorMessageKeys.MULTIPLE_SUBAWARDS.populate(award.getAwardId(), results, award.getAwardNumber());
        // }
        // if (parentAward && subAwards != null && !subAwards.isEmpty()) {
        //   ErrorMessageKeys.SUBAWARD_ON_PARENT.populate(award.getAwardId(), results, award.getAwardNumber());
        // }

    }

    /**
     * Determines if the given Award is a Sub-award.  This is based on the
     * description of the AwardType on the Award.
     *
     * @param award the Award to check to determine if it is a sub-award or not
     * @return true if the given award is a sub-award, false if not
     */
    private boolean isSubAward(Award award) {
        return AWARD_TYPE_CODE_SUBAWARD.equals(award.getAwardType().getCode());
    }

    /**
     * Validates the following:
     * <p/>
     * <ol>
     * <li>Obligation End Date is greater than Project End Date; Obligation End Date should always be equal to or less than the Project End Date.</li>
     * </ol>
     */
    private void validateProjectDates(ValidationResults results, Award award) {

        AwardHierarchyNode awardHierarchyNode = getAwardHierarchyNode(award);

        java.util.Date projectStartDate = zeroOutTime(award.getAwardEffectiveDate());
        java.util.Date projectEndDate = zeroOutTime(award.getProjectEndDate());
        java.util.Date obligationStartDate = zeroOutTime(awardHierarchyNode.getCurrentFundEffectiveDate());
        java.util.Date obligationEndDate = zeroOutTime(award.getObligationExpirationDate());

        if (projectStartDate == null) {
            ErrorMessageKeys.PROJECT_START_DATE_NULL.populate(award.getAwardId(), results, award.getAwardNumber());
        }

        if (obligationStartDate == null) {
            ErrorMessageKeys.OBLIGATION_START_DATE_NULL.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }

        if (projectEndDate == null) {
            ErrorMessageKeys.PROJECT_START_END_NULL.populate(award.getAwardId(), results, award.getAwardNumber());
        }

        if (obligationEndDate == null) {
            ErrorMessageKeys.OBLIGATION_END_DATE_NULL.populate(award.getAwardId(), results, award.getAwardNumber());
        }

    }

    /**
     * Performs the (somewhat convoluted) logic to extract an AwardHierarchyNode from an Award.  This code was largely reverse engineered
     * by looking at {@link AwardHierarchyUIServiceImpl}.
     *
     * @param award the award to extract the AwardHierarchyNode from
     * @return the award hierarchy node for the given award
     */
    private AwardHierarchyNode getAwardHierarchyNode(Award award) {
        Map<String, AwardHierarchyNode> awardHierarchyNodes = new HashMap<String, AwardHierarchyNode>();
        Map<String, AwardHierarchy> awardHierarchyItems = getAwardHierarchyService()
                .getAwardHierarchy(award.getAwardNumber(), new ArrayList<String>());
        getAwardHierarchyService().populateAwardHierarchyNodes(awardHierarchyItems, awardHierarchyNodes,
                award.getAwardNumber(), award.getSequenceNumber().toString());
        return awardHierarchyNodes.get(award.getAwardNumber());
    }

    /**
     * Takes the given Date and returns a copy of the Date which has all of the time portions of the date (hours, minutes, seconds, etc.) set to zero.
     *
     * @param givenDate the date to zero out
     * @return a copy of the given date with the temporal components zeroed out
     */
    private java.util.Date zeroOutTime(java.util.Date givenDate) {
        if (givenDate != null) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(givenDate);
            calendar.set(Calendar.HOUR_OF_DAY, 0);
            calendar.set(Calendar.HOUR, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar.getTime();
        } else {
            return null;
        }
    }

    /**
     * Validates if Cost Sharing Appears on a Child Award; Cost Sharing can only be represented at the Parent..
     *
     * @param results
     * @param award
     */
    private void validateCostSharingOnChildAward(ValidationResults results, Award award, boolean parentAward) {
        if (!parentAward && isCostSharing(award)) {
            ErrorMessageKeys.COST_SHARING_ON_CHILD_AWARD.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }
    }

    private void validateGroupAwardHasChildren(ValidationResults results, Award award) {
        AwardHierarchy hierarchy = getAwardHierarchyService().loadAwardHierarchy(award.getAwardNumber());

        if ("Group".equalsIgnoreCase(((AwardExtension) award.getExtension()).getChildType())
                && !hierarchy.hasChildren()) {
            ErrorMessageKeys.GROUP_AWARD_HAS_NO_CHILDREN.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }
    }

    /**
     * Validates if Federal Award does not have a CFDA Number; When the Account Type is Federal the CFDA Number is required.
     *
     * @param results
     * @param award
     */
    private void validateFederalAward(ValidationResults results, Award award) {
        if (isFederalAccount(award) && StringUtils.isBlank(award.getCfdaNumber())) {
            ErrorMessageKeys.CFDA_NUMBER_REQUIRED_FEDERAL_ACCOUNT.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }
    }

    /**
     * Validates if NIH Award does not have a Document Number; When an NIH sponsor is listed the Document Number should not be Null.
     *
     * @param results
     * @param award
     */
    private void validateNIHAward(ValidationResults results, Award award) {

        boolean isNih = award.getSponsorName().startsWith("HHS/NIH");
        if (isNih && StringUtils.isBlank(award.getDocumentFundingId())) {
            ErrorMessageKeys.NIH_AWARD_NO_DOCUMENT_NUMBER.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }

    }

    /**
     * This is a validation that was originally spec'd.
     * This Validation should trigger if Award.Award_Type = 6 and Award.Prime_Sponsor_Code is NULL
     *
     * @param results
     * @param award
     */
    private void validatePrimeSponsorOnSubAward(ValidationResults results, Award award) {
        if (AWARD_TYPE_CODE_SUBAWARD == award.getAwardTypeCode()
                && StringUtils.isBlank(award.getPrimeSponsorCode())) {
            ErrorMessageKeys.SUB_AWARD_WITH_NO_PRIME_SPONSOR.populate(award.getAwardId(), results,
                    award.getAwardNumber());
        }
    }

    /**
     * This is a validation that was originally spec'd.
     * This validation should trigger if the parent award has an amount > 0 the
     * Award_Amount_info.Obli_Distributable_amount
     *
     * @param results
     * @param award
     */
    private void validateDollarObligation(ValidationResults results, Award award) {
        AwardAmountInfo awardAmountInfo = getAwardAmountInfoService()
                .fetchLastAwardAmountInfoForAwardVersionAndFinalizedTandMDocumentNumber(award);
        AwardHierarchy hierarchy = getAwardHierarchyService().loadAwardHierarchy(award.getAwardNumber());
        if (hierarchy.hasChildren() && awardAmountInfo.getObliDistributableAmount().isPositive()) {
            //            ErrorMessageKeys.DOLLARS_OBLIGATED_ON_AWARD_WITH_NO_CHILDREN.populate(award.getAwardId(), results, award.getAwardNumber());
            LOG.error("Transmision Error: DOLLARS_OBLIGATED_ON_AWARD_WITH_NO_CHILDREN");
        }
    }

    /**
     * An enumeration which defines all of the error messages that can be produced by validation of an
     * SAP award transmission.
     */
    private static enum ErrorMessageKeys {

        AWARD_DOCUMENT_NULL("error.award.sapintegration.awardDocumentNull"), AWARD_NOT_FINAL(
                "error.award.sapintegration.awardMustBeFinal"), TIME_MONEY_NOT_FINAL(
                        "error.award.sapintegration.timeAndMoneyDocumentMustBeFinal"), NO_PARENT_ACCOUNT_TYPE(
                                "error.award.sapintegration.noParentAccountType"), NO_PARENT_AWARD(
                                        "error.award.sapintegration.noParent"), NO_CHILDREN_AWARD(
                                                "error.award.sapintegration.noChildren"), NO_CHILD_TYPE_ON_CHILD_AWARD(
                                                        "error.award.sapintegration.noChildType"), NO_BUDGET_ON_CHILD_AWARD(
                                                                "error.award.sapintegration.noBudgetOnChild"), ACCOUNT_TYPE_NULL(
                                                                        "error.award.sapintegration.nullAccountType"), AWARD_HIERARCHY_NULL(
                                                                                "error.award.sapintegration.nullHierarchy"), AWARD_DOLLAR_AMOUNT_ON_CHILD(
                                                                                        "error.award.sapintegration.dollarAmountOnChild"), FEDERAL_ACCOUNT_NON_FEDERAL_SPONSOR(
                                                                                                "error.award.sapintegration.federalAccount.nonFederalSponsor"), NON_FEDERAL_ACCOUNT_FEDERAL_SPONSOR(
                                                                                                        "error.award.sapintegration.nonFederalAccount.federalSponsor"), MULTIPLE_SUBAWARDS(
                                                                                                                "error.award.sapintegration.subaward.multiple"), SUBAWARD_ON_PARENT(
                                                                                                                        "error.award.sapintegration.subawardOnParent"), SUB_AWARD_NO_PRIME_SPONSOR(
                                                                                                                                "error.award.sapintegration.subaward.noPrimeSponsor"), INVALID_LEAD_UNIT(
                                                                                                                                        "error.award.sapintegration.invalid.leadUnit"), PROJECT_START_DATE_NULL(
                                                                                                                                                "error.award.sapintegration.projectStartDate.required"), PROJECT_START_END_NULL(
                                                                                                                                                        "error.award.sapintegration.projectEndDate.required"), OBLIGATION_START_DATE_NULL(
                                                                                                                                                                "error.award.sapintegration.obligationStartDate.required"), OBLIGATION_END_DATE_NULL(
                                                                                                                                                                        "error.award.sapintegration.obligationEndDate.required"), OBLIGATION_END_DATE_AFTER_PROJECT_END_DATE(
                                                                                                                                                                                "error.award.sapintegration.obligationEndDate.after.projectEndDate"), COST_SHARING_ON_CHILD_AWARD(
                                                                                                                                                                                        "error.award.sapintegration.costSharing.onChildAward"), CFDA_NUMBER_REQUIRED_FEDERAL_ACCOUNT(
                                                                                                                                                                                                "error.award.sapintegration.cfdaNumber.requiredOn.federalAccount"), NIH_AWARD_NO_DOCUMENT_NUMBER(
                                                                                                                                                                                                        "error.award.sapintegration.nihAward.noDocumentNumber"), DOLLARS_OBLIGATED_ON_AWARD_WITH_NO_CHILDREN(
                                                                                                                                                                                                                "error.award.sapintegration.dollarsObligatedOnAwardWithChildren"), SUB_AWARD_WITH_NO_PRIME_SPONSOR(
                                                                                                                                                                                                                        "error.award.sapintegration.subAwardWithNoPrimeSponsor"), PARENT_ACCOUNT_TYPE_CANNOT_CHANGE(
                                                                                                                                                                                                                                "error.award.sapintegration.parentAward.accountTypeChanged"), PARENT_SPONSOR_CODE_CANNOT_CHANGE(
                                                                                                                                                                                                                                        "error.award.sapintegration.parentAward.sponsorCodeChanged"), PARENT_PAYMENT_METHOD_CANNOT_CHANGE(
                                                                                                                                                                                                                                                "error.award.sapintegration.parentAward.paymentMethodChanged"), PARENT_PAYMENT_BASIS_CANNOT_CHANGE(
                                                                                                                                                                                                                                                        "error.award.sapintegration.parentAward.paymentBasisChanged"), CHILD_CHILD_TYPE_CANNOT_CHANGE(
                                                                                                                                                                                                                                                                "error.award.sapintegration.childAward.childTypeChanged"), CHILD_LEAD_UNIT_CANNOT_CHANGE(
                                                                                                                                                                                                                                                                        "error.award.sapintegration.childAward.leadUnitChanged"), GROUP_AWARD_HAS_NO_CHILDREN(
                                                                                                                                                                                                                                                                                "error.award.sapintegration.groupAward.hasNoChildren"), AWARD_DOCUMENT_NO_DOCUMENT_NUMBER(
                                                                                                                                                                                                                                                                                        "error.award.sapintegration.awardDocument.noDocumentNumber"), PARENT_TRANSACTION_TYPE_NOCOST_EXT_OR_ADMIN_CHANGE(
                                                                                                                                                                                                                                                                                                "error.award.sapintegration.parentAward.transactionType.noCostExt_Or_administrativeChange");

        private String errorMessageKey;

        private ErrorMessageKeys(String errorMessageKey) {
            this.errorMessageKey = errorMessageKey;
        }

        //       private void populate(Long awardId, ValidationResults validationResults, String parentAwardTransactionType, String childAwardNumber) {
        //           ValidationError validationError = new ValidationError(errorMessageKey, Arrays.asList(parentAwardTransactionType, childAwardNumber));
        //           validationResults.addAwardValidationError(awardId, validationError);
        //        }

        //        private void populate(Long awardId, ValidationResults validationResults, String awardNumber, String oldValue, String newValue) {
        //            ValidationError validationError = new ValidationError(errorMessageKey, Arrays.asList(awardNumber, oldValue, newValue));
        //            validationResults.addAwardValidationError(awardId, validationError);
        //        }

        private void populate(Long awardId, ValidationResults validationResults, String param1, String param2,
                String param3) {
            ValidationError validationError = new ValidationError(errorMessageKey,
                    Arrays.asList(param1, param2, param3));
            validationResults.addAwardValidationError(awardId, validationError);
        }

        private void populate(Long awardId, ValidationResults validationResults, String awardNumber) {
            ValidationError validationError = new ValidationError(errorMessageKey,
                    new ArrayList<String>(Arrays.asList(awardNumber)));
            validationResults.addAwardValidationError(awardId, validationError);
        }

        private void populate(ValidationResults validationResults) {
            ValidationError validationError = new ValidationError(errorMessageKey, new ArrayList<String>());
            validationResults.addGlobalValidationError(validationError);
        }

    }

    private String dateToString(Date date) {
        if (date == null) {
            return null;
        }
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_PATTERN);
        return dateFormat.format(date);
    }

    /**
     * Maps the given KC sponsor's account type code to SAP grant type code as per "Mapping Table M1" in the specification.
     */
    private String convertAccountTypeToGrantType(int accountType, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.ACCOUNT_TYPE_MAPPING, Integer.toString(accountType));
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert account type, given account type value was not understood: " + accountType
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    /**
     * Defines the mapping from KC account types to SAP fringe codes.
     */
    private static enum FringeCodeMapping {
        FEDERAL("FD"), NON_FEDERAL("NF"), FRINGE_NOT_ALLOWED("NA");

        private final String code;

        private FringeCodeMapping(String code) {
            this.code = code;
        }

        private static String mapToSapFringeCode(boolean isFringeNotAllowed, Integer accountTypeCode) {
            if (isFringeNotAllowed) {
                return FRINGE_NOT_ALLOWED.code;
            } else if (FEDERAL_CODE.equals(accountTypeCode)) {
                return FEDERAL.code;
            } else if (NON_FEDERAL_CODE.equals(accountTypeCode)) {
                return NON_FEDERAL.code;
            }
            return null;
        }
    }

    /**
     * Defines the mapping from KC activity types to SAP functional areas as per "Mapping Table M7" in the specification.
     */
    private String convertActivityTypeToFunctionalArea(String activityType, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.ACTIVITY_TYPE_MAPPING, activityType);
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert activity type, given activity type value was not understood: " + activityType
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    /**
     * Defines the mapping from KC Basis of Payment code to SAP Billing Rule as per "Mapping Table M3" in the specification.
     */
    private String convertBasisOfPaymentToBillingRule(String basisOfPaymentCode, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.BASIS_OF_PAYMENT_MAPPING, basisOfPaymentCode);
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert basis of payment code, given basis of payment value was not understood: "
                            + basisOfPaymentCode + " awardNumber was: " + award.getAwardNumber() + "\n"
                            + award.toString());
        }
        return returnValue;
    }

    /**
     * Defines the mapping from KC Method of Payment code to SAP Letter of Credit as per "Mapping Table M4" in the specification.
     */
    private String convertMethodOfPaymentToLetterOfCredit(String methodOfPaymentCode, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.METHOD_OF_PAYMENT_MAPPING, methodOfPaymentCode);
        if (StringUtils.isBlank(returnValue)) {
            returnValue = null;
        }
        return returnValue;
    }

    /**
     * Defines the mapping from KC project role to SAP responsibility as per "Mapping Table M6" in the specification.
     */
    private String convertProjectRoleToResponsibility(String projectRole, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.PROJECT_ROLE_MAPPING, projectRole);
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert project role, given project role value was not understood: " + projectRole
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    /**
     * Defines the mapping from KC status code to SAP status code as per "Mapping Table M5" in the specification.
     */
    private String convertStatusCodeToResponsibility(int statusCode, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.STATUS_CODE_MAPPING, Integer.toString(statusCode));
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert status code, given status code value was not understood: " + statusCode
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    /**
     * Defines the mapping from KC sponsor type codes to SAP sponsor type codes.
     */
    private String convertSponsorCodeToSponsorType(String sponsorCode, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.SPONSOR_CODE_MAPPING, sponsorCode);
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert sponsor code, given sponsor code value was not understood: " + sponsorCode
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    /**
     * Defines the mapping from Sponsor Terms in KCRM to Sponsor Class in SAP as per functional specification mapping table M8.
     */
    private String convertSponsorTermToSponsorClass(long sponsorTerm, Award award) {
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.SPONSOR_TERM_MAPPING, Long.toString(sponsorTerm));
        if (StringUtils.isEmpty(returnValue)) {
            LOG.info("[awardId: " + award.getAwardId() + ", awardNumber: " + award.getAwardNumber() + "] - "
                    + "Failed to convert sponsor term, given sponsor term value was not understood: "
                    + sponsorTerm);
        }
        return returnValue;
    }

    private String convertLeadUnitToBusinessArea(String leadUnitNumber, Award award) {
        String firstDigit = StringUtils.left(leadUnitNumber, 1);
        String returnValue = getParameterService().getSubParameterValueAsString(AwardDocument.class,
                BUConstants.LEAD_UNIT_MAPPING, firstDigit);
        if (StringUtils.isEmpty(returnValue)) {
            throw new IllegalArgumentException(
                    "Failed to convert lead unit, given lead unit value was not understood: " + leadUnitNumber
                            + " awardNumber was: " + award.getAwardNumber() + "\n" + award.toString());
        }
        return returnValue;
    }

    private void logAwardInfo(Award award, String message) {
        if (LOG.isInfoEnabled()) {
            LOG.info("[awardId: " + award.getAwardId() + ", awardNumber: " + award.getAwardNumber() + "] - "
                    + message);
        }
    }

    public void setAwardAmountInfoService(AwardAmountInfoService awardAmountInfoService) {
        this.awardAmountInfoService = awardAmountInfoService;
    }

    public void setUnitService(UnitService unitService) {
        this.unitService = unitService;
    }

    public void setAwardHierarchyService(AwardHierarchyService awardHierarchyService) {
        this.awardHierarchyService = awardHierarchyService;
    }

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

    public void setBudgetRateAndBaseService(BudgetRateAndBaseService budgetRateAndBaseService) {
        this.budgetRateAndBaseService = budgetRateAndBaseService;
    }

    public void setRolodexService(RolodexService rolodexService) {
        this.rolodexService = rolodexService;
    }

    protected AwardAmountInfoService getAwardAmountInfoService() {
        return awardAmountInfoService;
    }

    protected UnitService getUnitService() {
        return unitService;
    }

    protected AwardHierarchyService getAwardHierarchyService() {
        return awardHierarchyService;
    }

    protected BusinessObjectService getBusinessObjectService() {
        return businessObjectService;
    }

    protected BudgetRateAndBaseService getBudgetRateAndBaseService() {
        return budgetRateAndBaseService;
    }

    protected RolodexService getRolodexService() {
        return rolodexService;
    }

    public DocumentService getDocumentService() {
        return documentService;
    }

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

    protected AwardBudgetService getAwardBudgetService() {
        return KcServiceLocator.getService(AwardBudgetService.class);
    }

    /**
     * Based on the same method in the foundation's AwardBudgetService method
     * to get the last budget version on the award
     *
     * @param award on progress to be transmitted
     * @return last budget version on award
     */
    protected AwardBudgetExt getLastBudgetVersion(AwardDocument awardDocument) {
        List<AwardBudgetExt> awardBudgetDocumentVersions = awardDocument.getAward().getBudgets();
        AwardBudgetExt budgetVersionOverview = null;
        int versionSize = awardBudgetDocumentVersions.size();
        if (versionSize > 0) {
            budgetVersionOverview = awardBudgetDocumentVersions.get(versionSize - 1).getFirstBudget();
        }
        return budgetVersionOverview;
    }
}

class AwardTransmissionComparator implements Comparator<AwardTransmission>, Serializable {

    private static final long serialVersionUID = 8230902362851330642L;

    public int compare(AwardTransmission awardTransmission1, AwardTransmission awardTransmission2) {
        return ((Long) awardTransmission1.getTransmissionId())
                .compareTo((Long) awardTransmission2.getTransmissionId());
    }
}