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

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.gl.service.impl.StringHelper;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.kns.util.KNSGlobalVariables;
import org.kuali.kfs.krad.bo.Attachment;
import org.kuali.kfs.krad.bo.DocumentHeader;
import org.kuali.kfs.krad.bo.Note;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.exception.ValidationException;
import org.kuali.kfs.krad.service.AttachmentService;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.service.KualiRuleService;
import org.kuali.kfs.krad.service.MailService;
import org.kuali.kfs.krad.service.NoteService;
import org.kuali.kfs.krad.util.ErrorMessage;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.KRADPropertyConstants;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapKeyConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.batch.ElectronicInvoiceInputFileType;
import org.kuali.kfs.module.purap.batch.ElectronicInvoiceStep;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoice;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceItem;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceLoad;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceLoadSummary;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceOrder;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReason;
import org.kuali.kfs.module.purap.businessobject.ElectronicInvoiceRejectReasonType;
import org.kuali.kfs.module.purap.businessobject.ItemType;
import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.dataaccess.ElectronicInvoicingDao;
import org.kuali.kfs.module.purap.document.ElectronicInvoiceRejectDocument;
import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
import org.kuali.kfs.module.purap.document.RequisitionDocument;
import org.kuali.kfs.module.purap.document.service.AccountsPayableService;
import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
import org.kuali.kfs.module.purap.document.service.RequisitionService;
import org.kuali.kfs.module.purap.document.validation.event.AttributedCalculateAccountsPayableEvent;
import org.kuali.kfs.module.purap.document.validation.event.AttributedPaymentRequestForEInvoiceEvent;
import org.kuali.kfs.module.purap.exception.CxmlParseException;
import org.kuali.kfs.module.purap.exception.PurError;
import org.kuali.kfs.module.purap.service.ElectronicInvoiceHelperService;
import org.kuali.kfs.module.purap.service.ElectronicInvoiceMatchingService;
import org.kuali.kfs.module.purap.util.ElectronicInvoiceUtils;
import org.kuali.kfs.module.purap.util.ExpiredOrClosedAccountEntry;
import org.kuali.kfs.sys.batch.InitiateDirectoryBase;
import org.kuali.kfs.sys.batch.service.BatchInputFileService;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.exception.ParseException;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.kfs.vnd.businessobject.VendorDetail;
import org.kuali.kfs.vnd.document.service.VendorService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AutoPopulatingList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * This is a helper service to parse electronic invoice file, match it with a PO and create PREQs based on the eInvoice. Also, it
 * provides helper methods to the reject document to match it with a PO and create PREQ.
 */

public class ElectronicInvoiceHelperServiceImpl extends InitiateDirectoryBase implements ElectronicInvoiceHelperService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ElectronicInvoiceHelperServiceImpl.class);

    protected final String UNKNOWN_DUNS_IDENTIFIER = "Unknown";
    protected final String INVOICE_FILE_MIME_TYPE = "text/xml";

    private StringBuffer emailTextErrorList;

    protected ElectronicInvoiceInputFileType electronicInvoiceInputFileType;
    protected MailService mailService;
    protected ElectronicInvoiceMatchingService matchingService;
    protected ElectronicInvoicingDao electronicInvoicingDao;
    protected BatchInputFileService batchInputFileService;
    protected VendorService vendorService;
    protected PurchaseOrderService purchaseOrderService;
    protected PaymentRequestService paymentRequestService;
    protected ConfigurationService kualiConfigurationService;
    protected DateTimeService dateTimeService;
    protected ParameterService parameterService;

    @Override
    @NonTransactional
    public ElectronicInvoiceLoad loadElectronicInvoices() {

        //add a step to check for directory paths
        prepareDirectories(getRequiredDirectoryNames());

        String rejectDirName = getRejectDirName();
        String acceptDirName = getAcceptDirName();
        emailTextErrorList = new StringBuffer();

        boolean moveFiles = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.FILE_MOVE_AFTER_LOAD_IND);

        int failedCnt = 0;

        if (LOG.isInfoEnabled()) {
            LOG.info("Invoice Base Directory - " + electronicInvoiceInputFileType.getDirectoryPath());
            LOG.info("Invoice Accept Directory - " + acceptDirName);
            LOG.info("Invoice Reject Directory - " + rejectDirName);
            LOG.info("Is moving files allowed - " + moveFiles);
        }

        if (StringUtils.isBlank(rejectDirName)) {
            throw new RuntimeException("Reject directory name should not be empty");
        }

        if (StringUtils.isBlank(acceptDirName)) {
            throw new RuntimeException("Accept directory name should not be empty");
        }

        File[] filesToBeProcessed = getFilesToBeProcessed();
        ElectronicInvoiceLoad eInvoiceLoad = new ElectronicInvoiceLoad();

        if (filesToBeProcessed == null ||
            filesToBeProcessed.length == 0) {

            StringBuffer mailText = new StringBuffer();

            mailText.append("\n\n");
            mailText.append(PurapConstants.ElectronicInvoice.NO_FILES_PROCESSED_EMAIL_MESSAGE);
            mailText.append("\n\n");

            sendSummary(mailText);
            return eInvoiceLoad;
        }

        try {
            /**
             * Create, if not there
             */
            FileUtils.forceMkdir(new File(acceptDirName));
            FileUtils.forceMkdir(new File(rejectDirName));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        if (LOG.isInfoEnabled()) {
            LOG.info(filesToBeProcessed.length + " file(s) available for processing");
        }

        StringBuilder emailMsg = new StringBuilder();

        for (int i = 0; i < filesToBeProcessed.length; i++) {

            File xmlFile = filesToBeProcessed[i];
            LOG.info("Processing " + xmlFile.getName() + "....");

            byte[] modifiedXML = null;
            //process only if file exists and not empty
            if (xmlFile.length() != 0L) {
                modifiedXML = addNamespaceDefinition(eInvoiceLoad, xmlFile);
            }

            boolean isRejected = false;

            if (modifiedXML == null) {//Not able to parse the xml
                isRejected = true;
            } else {
                try {
                    isRejected = processElectronicInvoice(eInvoiceLoad, xmlFile, modifiedXML);
                } catch (Exception e) {
                    String msg = xmlFile.getName() + "\n";
                    LOG.error(msg);

                    //since getMessage() is empty we'll compose the stack trace and nicely format it.
                    StackTraceElement[] elements = e.getStackTrace();
                    StringBuffer trace = new StringBuffer();
                    trace.append(e.getClass().getName());
                    if (e.getMessage() != null) {
                        trace.append(": ");
                        trace.append(e.getMessage());
                    }
                    trace.append("\n");
                    for (int j = 0; j < elements.length; ++j) {
                        StackTraceElement element = elements[j];

                        trace.append("    at ");
                        trace.append(describeStackTraceElement(element));
                        trace.append("\n");
                    }

                    LOG.error(trace);
                    emailMsg.append(msg);
                    msg += "\n--------------------------------------------------------------------------------------\n" + trace;
                    logProcessElectronicInvoiceError(msg);
                    failedCnt++;

                    /**
                     * Clear the error map, so that subsequent EIRT routing isn't prevented since rice
                     * is throwing a ValidationException if the error map is not empty before routing the doc.
                     */
                    GlobalVariables.getMessageMap().clearErrorMessages();

                    //Do not execute rest of code below
                    continue;
                }
            }

            /**
             * If there is a single order has rejects and the remainings are accepted in a invoice file,
             * then the entire file has been moved to the reject dir.
             */
            if (isRejected) {
                if (LOG.isInfoEnabled()) {
                    LOG.info(xmlFile.getName() + " has been rejected");
                }
                if (moveFiles) {
                    if (LOG.isInfoEnabled()) {
                        LOG.info(xmlFile.getName() + " has been marked to move to " + rejectDirName);
                    }
                    eInvoiceLoad.addRejectFileToMove(xmlFile, rejectDirName);
                }
            } else {
                if (LOG.isInfoEnabled()) {
                    LOG.info(xmlFile.getName() + " has been accepted");
                }
                if (moveFiles) {
                    if (!moveFile(xmlFile, acceptDirName)) {
                        String msg = xmlFile.getName() + " unable to move";
                        LOG.error(msg);
                        throw new PurError(msg);
                    }
                }
            }

            if (!moveFiles) {
                String fullPath = FilenameUtils.getFullPath(xmlFile.getAbsolutePath());
                String fileName = FilenameUtils.getBaseName(xmlFile.getAbsolutePath());
                File processedFile = new File(fullPath + File.separator + fileName + ".processed");
                try {
                    FileUtils.touch(processedFile);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            //  delete the .done file
            deleteDoneFile(xmlFile);
        }

        emailTextErrorList.append("\nFAILED FILES\n");
        emailTextErrorList.append("-----------------------------------------------------------\n\n");
        emailTextErrorList.append(emailMsg);
        emailTextErrorList.append("\nTOTAL COUNT\n");
        emailTextErrorList.append("===========================\n");
        emailTextErrorList.append("      " + failedCnt + " FAILED\n");
        emailTextErrorList.append("===========================\n");

        StringBuffer summaryText = saveLoadSummary(eInvoiceLoad);

        StringBuffer finalText = new StringBuffer();
        finalText.append(summaryText);
        finalText.append("\n");
        finalText.append(emailTextErrorList);
        sendSummary(finalText);

        LOG.info("Processing completed");

        return eInvoiceLoad;

    }

    protected File[] getFilesToBeProcessed() {
        File[] filesToBeProcessed;
        String baseDirName = getBaseDirName();
        File baseDir = new File(baseDirName);
        if (!baseDir.exists()) {
            throw new RuntimeException("Base dir [" + baseDirName + "] doesn't exists in the system");
        }
        filesToBeProcessed = baseDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                String fullPath = FilenameUtils.getFullPath(file.getAbsolutePath());
                String fileName = FilenameUtils.getBaseName(file.getAbsolutePath());
                File processedFile = new File(fullPath + File.separator + fileName + ".processed");
                return (!file.isDirectory() &&
                    file.getName().endsWith(electronicInvoiceInputFileType.getFileExtension()) &&
                    !processedFile.exists());
            }
        });

        return filesToBeProcessed;
    }

    protected void logProcessElectronicInvoiceError(String msg) {
        File file = new File(electronicInvoiceInputFileType.getReportPath() + "/" +
            electronicInvoiceInputFileType.getReportPrefix() + "_" +
            dateTimeService.toDateTimeStringForFilename(dateTimeService.getCurrentDate()) + "." +
            electronicInvoiceInputFileType.getReportExtension());
        BufferedWriter writer = null;

        try {
            writer = new BufferedWriter(new PrintWriter(file));
            writer.write(msg);
            writer.newLine();
        } catch (FileNotFoundException e) {
            LOG.error(file + " not found " + " " + e.getMessage());
            throw new RuntimeException(file + " not found " + e.getMessage(), e);
        } catch (IOException e) {
            LOG.error("Error writing to BufferedWriter " + e.getMessage());
            throw new RuntimeException("Error writing to BufferedWriter " + e.getMessage(), e);
        } finally {
            try {
                writer.flush();
                writer.close();
            } catch (Exception e) {
            }
        }
    }

    /**
     * @param element
     * @return String describing the given StackTraceElement
     */
    private static String describeStackTraceElement(StackTraceElement element) {
        StringBuffer description = new StringBuffer();
        if (element == null) {
            description.append("invalid (null) element");
        }
        description.append(element.getClassName());
        description.append(".");
        description.append(element.getMethodName());
        description.append("(");
        description.append(element.getFileName());
        description.append(":");
        description.append(element.getLineNumber());
        description.append(")");

        return description.toString();
    }

    protected byte[] addNamespaceDefinition(ElectronicInvoiceLoad eInvoiceLoad,
                                            File invoiceFile) {


        boolean result = true;

        LOG.info("addNamespaceDefinition: Adding namespace definition, DocumentBuilderFactory.newInstance()");

        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        builderFactory.setValidating(false); // It's not needed to validate here
        builderFactory.setIgnoringElementContentWhitespace(true);

        DocumentBuilder builder = null;
        try {
            LOG.info("addNamespaceDefinition: builderFactory.newDocumentBuilder()");
            builder = builderFactory.newDocumentBuilder();  // Create the parser
        } catch (ParserConfigurationException e) {
            LOG.error("addNamespaceDefinition: Error getting document builder - " + e.getMessage());
            throw new RuntimeException(e);
        }

        Document xmlDoc = null;

        try {
            LOG.info("addNamespaceDefinition: builder.parse");
            xmlDoc = builder.parse(invoiceFile);
        } catch (Exception e) {
            LOG.info("addNamespaceDefinition: Error parsing the file - " + e.getMessage());
            rejectElectronicInvoiceFile(eInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, e.getMessage(), PurapConstants.ElectronicInvoice.FILE_FORMAT_INVALID);
            return null;
        }
        LOG.info("addNamespaceDefinition: xmlDoc.getDocumentElement()");

        Node node = xmlDoc.getDocumentElement();
        Element element = (Element) node;

        String xmlnsValue = element.getAttribute("xmlns");
        String xmlnsXsiValue = element.getAttribute("xmlns:xsi");

        LOG.info("addNamespaceDefinition: getInvoiceFile");
        File namespaceAddedFile = getInvoiceFile(invoiceFile.getName());

        if (StringUtils.equals(xmlnsValue, "http://www.kuali.org/kfs/purap/electronicInvoice") &&
            StringUtils.equals(xmlnsXsiValue, "http://www.w3.org/2001/XMLSchema-instance")) {
            LOG.info("addNamespaceDefinition: xmlns and xmlns:xsi attributes already exists in the invoice xml");
        } else {
            element.setAttribute("xmlns", "http://www.kuali.org/kfs/purap/electronicInvoice");
            element.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        }

        OutputFormat outputFormat = new OutputFormat(xmlDoc);
        outputFormat.setOmitDocumentType(true);

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        XMLSerializer serializer = new XMLSerializer(out, outputFormat);
        try {
            serializer.asDOMSerializer();
            LOG.info("addNamespaceDefinition: serializer.serialize");
            serializer.serialize(xmlDoc.getDocumentElement());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        LOG.info("addNamespaceDefinition: Namespace validation completed");

        return out.toByteArray();

    }

    /**
     * This method processes a single electronic invoice file
     *
     * @param eInvoiceLoad the load summary to be modified
     * @return boolean where true means there has been some type of reject
     */

    @Transactional
    protected boolean processElectronicInvoice(ElectronicInvoiceLoad eInvoiceLoad,
                                               File invoiceFile,
                                               byte[] xmlAsBytes) {

        ElectronicInvoice eInvoice = null;

        try {
            eInvoice = loadElectronicInvoice(xmlAsBytes);
        } catch (CxmlParseException e) {
            LOG.info("Error loading file - " + e.getMessage());
            rejectElectronicInvoiceFile(eInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, e.getMessage(), PurapConstants.ElectronicInvoice.FILE_FORMAT_INVALID);
            return true;
        }

        eInvoice.setFileName(invoiceFile.getName());

        boolean isCompleteFailure = checkForCompleteFailure(eInvoiceLoad, eInvoice, invoiceFile);

        if (isCompleteFailure) {
            return true;
        }

        setVendorDUNSNumber(eInvoice);
        setVendorDetails(eInvoice);

        Map itemTypeMappings = getItemTypeMappings(eInvoice.getVendorHeaderID(), eInvoice.getVendorDetailID());
        Map kualiItemTypes = getKualiItemTypes();

        if (LOG.isInfoEnabled()) {
            if (itemTypeMappings != null && itemTypeMappings.size() > 0) {
                LOG.info("Item mappings found");
            }
        }

        boolean validateHeader = true;

        for (ElectronicInvoiceOrder order : eInvoice.getInvoiceDetailOrders()) {

            String poID = order.getOrderReferenceOrderID();
            PurchaseOrderDocument po = null;

            if (NumberUtils.isDigits(StringUtils.defaultString(poID))) {
                po = purchaseOrderService.getCurrentPurchaseOrder(new Integer(poID));
                if (po != null) {
                    order.setInvoicePurchaseOrderID(poID);
                    order.setPurchaseOrderID(po.getPurapDocumentIdentifier());
                    order.setPurchaseOrderCampusCode(po.getDeliveryCampusCode());

                    if (LOG.isInfoEnabled()) {
                        LOG.info("PO matching Document found");
                    }
                }
            }

            ElectronicInvoiceOrderHolder orderHolder = new ElectronicInvoiceOrderHolder(eInvoice, order, po, itemTypeMappings, kualiItemTypes, validateHeader);
            matchingService.doMatchingProcess(orderHolder);

            if (orderHolder.isInvoiceRejected()) {

                ElectronicInvoiceRejectDocument rejectDocument = createRejectDocument(eInvoice, order, eInvoiceLoad);

                if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null) {
                    rejectDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
                }

                String dunsNumber = StringUtils.isEmpty(eInvoice.getDunsNumber()) ?
                    UNKNOWN_DUNS_IDENTIFIER :
                    eInvoice.getDunsNumber();

                ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, dunsNumber);
                loadSummary.addFailedInvoiceOrder(rejectDocument.getTotalAmount(), eInvoice);
                eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);

            } else {

                PaymentRequestDocument preqDoc = createPaymentRequest(orderHolder);

                if (orderHolder.isInvoiceRejected()) {
                    /**
                     * This is required. If there is anything in the error map, then it's not possible to route the doc since the rice
                     * is throwing error if errormap is not empty before routing the doc.
                     */
                    GlobalVariables.getMessageMap().clearErrorMessages();

                    ElectronicInvoiceRejectDocument rejectDocument = createRejectDocument(eInvoice, order, eInvoiceLoad);

                    if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null) {
                        rejectDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
                    }

                    ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, eInvoice.getDunsNumber());
                    loadSummary.addFailedInvoiceOrder(rejectDocument.getTotalAmount(), eInvoice);
                    eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);

                } else {
                    ElectronicInvoiceLoadSummary loadSummary = getOrCreateLoadSummary(eInvoiceLoad, eInvoice.getDunsNumber());
                    loadSummary.addSuccessfulInvoiceOrder(preqDoc.getTotalDollarAmount(), eInvoice);
                    eInvoiceLoad.insertInvoiceLoadSummary(loadSummary);
                }

            }

            validateHeader = false;
        }

        return eInvoice.isFileRejected();
    }

    protected void setVendorDUNSNumber(ElectronicInvoice eInvoice) {

        String dunsNumber = null;

        if (StringUtils.equals(eInvoice.getCxmlHeader().getFromDomain(), "DUNS")) {
            dunsNumber = eInvoice.getCxmlHeader().getFromIdentity();
        } else if (StringUtils.equals(eInvoice.getCxmlHeader().getSenderDomain(), "DUNS")) {
            dunsNumber = eInvoice.getCxmlHeader().getSenderIdentity();
        }

        if (StringUtils.isNotEmpty((dunsNumber))) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Setting Vendor DUNS number - " + dunsNumber);
            }
            eInvoice.setDunsNumber(dunsNumber);
        }

    }

    protected void setVendorDetails(ElectronicInvoice eInvoice) {

        if (StringUtils.isNotEmpty(eInvoice.getDunsNumber())) {

            VendorDetail vendorDetail = vendorService.getVendorByDunsNumber(eInvoice.getDunsNumber());

            if (vendorDetail != null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Vendor match found - " + vendorDetail.getVendorNumber());
                }
                eInvoice.setVendorHeaderID(vendorDetail.getVendorHeaderGeneratedIdentifier());
                eInvoice.setVendorDetailID(vendorDetail.getVendorDetailAssignedIdentifier());
                eInvoice.setVendorName(vendorDetail.getVendorName());
            } else {
                eInvoice.setVendorHeaderID(null);
                eInvoice.setVendorDetailID(null);
                eInvoice.setVendorName(null);
            }
        }
    }

    protected void validateVendorDetails(ElectronicInvoiceRejectDocument rejectDocument) {

        boolean vendorFound = false;

        if (StringUtils.isNotEmpty(rejectDocument.getVendorDunsNumber())) {

            VendorDetail vendorDetail = vendorService.getVendorByDunsNumber(rejectDocument.getVendorDunsNumber());

            if (vendorDetail != null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Vendor [" + vendorDetail.getVendorNumber() + "] match found for the DUNS - " + rejectDocument.getVendorDunsNumber());
                }
                rejectDocument.setVendorHeaderGeneratedIdentifier(vendorDetail.getVendorHeaderGeneratedIdentifier());
                rejectDocument.setVendorDetailAssignedIdentifier(vendorDetail.getVendorDetailAssignedIdentifier());
                rejectDocument.setVendorDetail(vendorDetail);
                vendorFound = true;
            }
        }

        if (!vendorFound) {
            rejectDocument.setVendorHeaderGeneratedIdentifier(null);
            rejectDocument.setVendorDetailAssignedIdentifier(null);
            rejectDocument.setVendorDetail(null);
        }

        String newDocumentDesc = generateRejectDocumentDescription(rejectDocument);
        rejectDocument.getDocumentHeader().setDocumentDescription(newDocumentDesc);
    }

    protected Map getItemTypeMappings(Integer vendorHeaderId,
                                      Integer vendorDetailId) {

        Map itemTypeMappings = null;

        if (vendorHeaderId != null && vendorDetailId != null) {
            String vendorNumber = getVendorNumber(vendorHeaderId, vendorDetailId);
            itemTypeMappings = electronicInvoicingDao.getItemMappingMap(vendorHeaderId, vendorDetailId);
        }

        if (itemTypeMappings == null || itemTypeMappings.isEmpty()) {
            itemTypeMappings = electronicInvoicingDao.getDefaultItemMappingMap();
        }

        return itemTypeMappings;
    }

    protected String getVendorNumber(Integer vendorHeaderId,
                                     Integer vendorDetailId) {

        if (vendorHeaderId != null && vendorDetailId != null) {
            VendorDetail forVendorNo = new VendorDetail();
            forVendorNo.setVendorHeaderGeneratedIdentifier(vendorHeaderId);
            forVendorNo.setVendorDetailAssignedIdentifier(vendorDetailId);
            return forVendorNo.getVendorNumber();
        } else {
            return null;
        }
    }

    protected Map<String, ItemType> getKualiItemTypes() {

        Collection<ItemType> collection = SpringContext.getBean(BusinessObjectService.class).findAll(ItemType.class);
        Map kualiItemTypes = new HashMap<String, ItemType>();

        if (collection == null || collection.size() == 0) {
            throw new RuntimeException("Kauli Item types not available");
        } else {
            if (collection != null) {
                ItemType[] itemTypes = new ItemType[collection.size()];
                collection.toArray(itemTypes);
                for (int i = 0; i < itemTypes.length; i++) {
                    kualiItemTypes.put(itemTypes[i].getItemTypeCode(), itemTypes[i]);
                }
            }
        }

        return kualiItemTypes;
    }

    protected boolean checkForCompleteFailure(ElectronicInvoiceLoad electronicInvoiceLoad,
                                              ElectronicInvoice electronicInvoice,
                                              File invoiceFile) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Checking for complete failure...");
        }

        if (electronicInvoice.getInvoiceDetailRequestHeader().isHeaderInvoiceIndicator()) {
            rejectElectronicInvoiceFile(electronicInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, PurapConstants.ElectronicInvoice.HEADER_INVOICE_IND_ON);
            return true;
        }

        if (electronicInvoice.getInvoiceDetailOrders().size() < 1) {
            rejectElectronicInvoiceFile(electronicInvoiceLoad, UNKNOWN_DUNS_IDENTIFIER, invoiceFile, PurapConstants.ElectronicInvoice.INVOICE_ORDERS_NOT_FOUND);
            return true;
        }

        //it says - Future Release - Enter valid location for Customer Number from E-Invoice
        //mappingService.getInvoiceCustomerNumber() doesnt have any implementation
//        electronicInvoice.setCustomerNumber(mappingService.getInvoiceCustomerNumber(electronicInvoice));

        for (Iterator orderIter = electronicInvoice.getInvoiceDetailOrders().iterator(); orderIter.hasNext(); ) {
            ElectronicInvoiceOrder invoiceOrder = (ElectronicInvoiceOrder) orderIter.next();
            for (Iterator itemIter = invoiceOrder.getInvoiceItems().iterator(); itemIter.hasNext(); ) {
                ElectronicInvoiceItem invoiceItem = (ElectronicInvoiceItem) itemIter.next();
                if (invoiceItem != null) {
                    invoiceItem.setCatalogNumber(invoiceItem.getReferenceItemIDSupplierPartID());
                }
            }
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("No Complete failure");
        }

        return false;

    }

    protected ElectronicInvoiceRejectReasonType getRejectReasonType(String rejectReasonTypeCode) {
        return matchingService.getElectronicInvoiceRejectReasonType(rejectReasonTypeCode);
    }

    protected void rejectElectronicInvoiceFile(ElectronicInvoiceLoad eInvoiceLoad,
                                               String fileDunsNumber,
                                               File filename,
                                               String rejectReasonTypeCode) {

        rejectElectronicInvoiceFile(eInvoiceLoad, fileDunsNumber, filename, null, rejectReasonTypeCode);
    }

    protected void rejectElectronicInvoiceFile(ElectronicInvoiceLoad eInvoiceLoad,
                                               String fileDunsNumber,
                                               File invoiceFile,
                                               String extraDescription,
                                               String rejectReasonTypeCode) {
        if (LOG.isInfoEnabled()) {
            LOG.info("Rejecting the entire invoice file - " + invoiceFile.getName());
        }

        ElectronicInvoiceLoadSummary eInvoiceLoadSummary = getOrCreateLoadSummary(eInvoiceLoad, fileDunsNumber);
        eInvoiceLoadSummary.addFailedInvoiceOrder();
        eInvoiceLoad.insertInvoiceLoadSummary(eInvoiceLoadSummary);

        ElectronicInvoiceRejectDocument eInvoiceRejectDocument = null;
        try {
            eInvoiceRejectDocument = (ElectronicInvoiceRejectDocument) SpringContext.getBean(DocumentService.class).getNewDocument("EIRT");

            eInvoiceRejectDocument.setInvoiceProcessTimestamp(SpringContext.getBean(DateTimeService.class).getCurrentTimestamp());
            eInvoiceRejectDocument.setVendorDunsNumber(fileDunsNumber);
            eInvoiceRejectDocument.setDocumentCreationInProgress(true);

            if (invoiceFile != null) {
                eInvoiceRejectDocument.setInvoiceFileName(invoiceFile.getName());
            }

            List<ElectronicInvoiceRejectReason> list = new ArrayList<ElectronicInvoiceRejectReason>(1);

            String message = "Complete failure document has been created for the Invoice with Filename '" + invoiceFile.getName() + "' due to the following error:\n";
            emailTextErrorList.append(message);

            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(rejectReasonTypeCode, extraDescription, invoiceFile.getName());
            list.add(rejectReason);

            emailTextErrorList.append("    - " + rejectReason.getInvoiceRejectReasonDescription());
            emailTextErrorList.append("\n\n");

            eInvoiceRejectDocument.setInvoiceRejectReasons(list);
            eInvoiceRejectDocument.getDocumentHeader().setDocumentDescription("Complete failure");

            // KFSCNTRB-1369: Need to Save document
            SpringContext.getBean(DocumentService.class).saveDocument(eInvoiceRejectDocument);

            String noteText = "Invoice file";
            attachInvoiceXMLWithRejectDoc(eInvoiceRejectDocument, invoiceFile, noteText);

            eInvoiceLoad.addInvoiceReject(eInvoiceRejectDocument);

        } catch (WorkflowException e) {
            throw new RuntimeException(e);
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("Complete failure document has been created (DocNo:" + eInvoiceRejectDocument.getDocumentNumber() + ")");
        }
    }

    protected void attachInvoiceXMLWithRejectDoc(ElectronicInvoiceRejectDocument eInvoiceRejectDocument,
                                                 File attachmentFile,
                                                 String noteText) {

        Note note = null;
        try {
            note = SpringContext.getBean(DocumentService.class).createNoteFromDocument(eInvoiceRejectDocument, noteText);
            // KFSCNTRB-1369: Can't add note without remoteObjectIdentifier
            note.setRemoteObjectIdentifier(eInvoiceRejectDocument.getDocumentHeader().getObjectId());
        } catch (Exception e1) {
            throw new RuntimeException("Unable to create note from document: ", e1);
        }

        String attachmentType = null;
        BufferedInputStream fileStream = null;
        try {
            fileStream = new BufferedInputStream(new FileInputStream(attachmentFile));
        } catch (FileNotFoundException e) {
            LOG.error("Exception opening attachment file", e);
        }

        Attachment attachment = null;
        try {
            attachment = SpringContext.getBean(AttachmentService.class).createAttachment(eInvoiceRejectDocument.getNoteTarget(), attachmentFile.getName(), INVOICE_FILE_MIME_TYPE, (int) attachmentFile.length(), fileStream, attachmentType);
        } catch (IOException e) {
            throw new RuntimeException("Unable to create attachment", e);
        } finally {
            if (fileStream != null) {
                try {
                    fileStream.close();
                } catch (IOException e) {
                    LOG.error("Exception closing file", e);
                }
            }
        }

        note.setAttachment(attachment);
        attachment.setNote(note);
        SpringContext.getBean(NoteService.class).save(note);
    }

    @NonTransactional
    public ElectronicInvoiceRejectDocument createRejectDocument(ElectronicInvoice eInvoice,
                                                                ElectronicInvoiceOrder electronicInvoiceOrder,
                                                                ElectronicInvoiceLoad eInvoiceLoad) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Creating reject document [DUNS=" + eInvoice.getDunsNumber() + ",POID=" + electronicInvoiceOrder.getInvoicePurchaseOrderID() + "]");
        }

        ElectronicInvoiceRejectDocument eInvoiceRejectDocument;

        try {

            eInvoiceRejectDocument = (ElectronicInvoiceRejectDocument) SpringContext.getBean(DocumentService.class).getNewDocument("EIRT");

            eInvoiceRejectDocument.setInvoiceProcessTimestamp(SpringContext.getBean(DateTimeService.class).getCurrentTimestamp());
            String rejectdocDesc = generateRejectDocumentDescription(eInvoice, electronicInvoiceOrder);
            eInvoiceRejectDocument.getDocumentHeader().setDocumentDescription(rejectdocDesc);
            eInvoiceRejectDocument.setDocumentCreationInProgress(true);

            eInvoiceRejectDocument.setFileLevelData(eInvoice);
            eInvoiceRejectDocument.setInvoiceOrderLevelData(eInvoice, electronicInvoiceOrder);

            //MSU fix
            SpringContext.getBean(DocumentService.class).saveDocument(eInvoiceRejectDocument);

            String noteText = "Invoice file";
            attachInvoiceXMLWithRejectDoc(eInvoiceRejectDocument, getInvoiceFile(eInvoice.getFileName()), noteText);

            eInvoiceLoad.addInvoiceReject(eInvoiceRejectDocument);

        } catch (WorkflowException e) {
            throw new RuntimeException(e);
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("Reject document has been created (DocNo=" + eInvoiceRejectDocument.getDocumentNumber() + ")");
        }

        emailTextErrorList.append("DUNS Number - " + eInvoice.getDunsNumber() + " " + eInvoice.getVendorName() + ":\n");
        emailTextErrorList.append("An Invoice from file '" + eInvoice.getFileName() + "' has been rejected due to the following error(s):\n");

        int index = 1;
        for (ElectronicInvoiceRejectReason reason : eInvoiceRejectDocument.getInvoiceRejectReasons()) {
            emailTextErrorList.append("    - " + reason.getInvoiceRejectReasonDescription() + "\n");
            addRejectReasonsToNote("Reject Reason " + index + ". " + reason.getInvoiceRejectReasonDescription(), eInvoiceRejectDocument);
            index++;
        }

        emailTextErrorList.append("\n");

        return eInvoiceRejectDocument;
    }

    protected void addRejectReasonsToNote(String rejectReasons, ElectronicInvoiceRejectDocument eInvoiceRejectDocument) {

        try {
            Note note = SpringContext.getBean(DocumentService.class).createNoteFromDocument(eInvoiceRejectDocument, rejectReasons);
            // KFSCNTRB-1369: Can't add note without remoteObjectIdentifier
            note.setRemoteObjectIdentifier(eInvoiceRejectDocument.getDocumentHeader().getObjectId());
            PersistableBusinessObject noteParent = eInvoiceRejectDocument.getNoteTarget();
            SpringContext.getBean(NoteService.class).save(note);
        } catch (Exception e) {
            LOG.error("Error creating reject reason note - " + e.getMessage());
        }
    }


    protected String generateRejectDocumentDescription(ElectronicInvoice eInvoice,
                                                       ElectronicInvoiceOrder electronicInvoiceOrder) {

        String poID = StringUtils.isEmpty(electronicInvoiceOrder.getInvoicePurchaseOrderID()) ?
            "UNKNOWN" :
            electronicInvoiceOrder.getInvoicePurchaseOrderID();

        String vendorName = StringUtils.isEmpty(eInvoice.getVendorName()) ?
            "UNKNOWN" :
            eInvoice.getVendorName();

        String description = "PO: " + poID + " Vendor: " + vendorName;

        return checkDescriptionLengthAndStripIfNeeded(description);
    }

    protected String generateRejectDocumentDescription(ElectronicInvoiceRejectDocument rejectDoc) {

        String poID = StringUtils.isEmpty(rejectDoc.getInvoicePurchaseOrderNumber()) ?
            "UNKNOWN" :
            rejectDoc.getInvoicePurchaseOrderNumber();

        String vendorName = "UNKNOWN";
        if (rejectDoc.getVendorDetail() != null) {
            vendorName = rejectDoc.getVendorDetail().getVendorName();
        }

        String description = "PO: " + poID + " Vendor: " + vendorName;

        return checkDescriptionLengthAndStripIfNeeded(description);
    }

    protected String checkDescriptionLengthAndStripIfNeeded(String description) {

        int noteTextMaxLength = SpringContext.getBean(DataDictionaryService.class).getAttributeMaxLength(DocumentHeader.class, KRADPropertyConstants.DOCUMENT_DESCRIPTION).intValue();

        if (noteTextMaxLength < description.length()) {
            description = description.substring(0, noteTextMaxLength);
        }

        return description;
    }

    @NonTransactional
    public ElectronicInvoiceLoadSummary getOrCreateLoadSummary(ElectronicInvoiceLoad eInvoiceLoad,
                                                               String fileDunsNumber) {
        ElectronicInvoiceLoadSummary eInvoiceLoadSummary;

        if (eInvoiceLoad.getInvoiceLoadSummaries().containsKey(fileDunsNumber)) {
            eInvoiceLoadSummary = (ElectronicInvoiceLoadSummary) eInvoiceLoad.getInvoiceLoadSummaries().get(fileDunsNumber);
        } else {
            eInvoiceLoadSummary = new ElectronicInvoiceLoadSummary(fileDunsNumber);
        }

        return eInvoiceLoadSummary;

    }

    @NonTransactional
    public ElectronicInvoice loadElectronicInvoice(byte[] xmlAsBytes)
        throws CxmlParseException {

        if (LOG.isInfoEnabled()) {
            LOG.info("Loading Invoice File");
        }

        ElectronicInvoice electronicInvoice = null;

        try {
            electronicInvoice = (ElectronicInvoice) batchInputFileService.parse(electronicInvoiceInputFileType, xmlAsBytes);
        } catch (ParseException e) {
            throw new CxmlParseException(e.getMessage());
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("Successfully loaded the Invoice File");
        }

        return electronicInvoice;

    }

    protected StringBuffer saveLoadSummary(ElectronicInvoiceLoad eInvoiceLoad) {

        Map savedLoadSummariesMap = new HashMap();
        StringBuffer summaryMessage = new StringBuffer();

        for (Iterator iter = eInvoiceLoad.getInvoiceLoadSummaries().keySet().iterator(); iter.hasNext(); ) {

            String dunsNumber = (String) iter.next();
            ElectronicInvoiceLoadSummary eInvoiceLoadSummary = (ElectronicInvoiceLoadSummary) eInvoiceLoad.getInvoiceLoadSummaries().get(dunsNumber);

            if (!eInvoiceLoadSummary.isEmpty().booleanValue()) {
                LOG.info("Saving Load Summary for DUNS '" + dunsNumber + "'");

                ElectronicInvoiceLoadSummary currentLoadSummary = saveElectronicInvoiceLoadSummary(eInvoiceLoadSummary);

                summaryMessage.append("DUNS Number - " + eInvoiceLoadSummary.getVendorDescriptor() + ":\n");
                summaryMessage.append("     " + eInvoiceLoadSummary.getInvoiceLoadSuccessCount() + " successfully processed invoices for a total of $ " + eInvoiceLoadSummary.getInvoiceLoadSuccessAmount().doubleValue() + "\n");
                summaryMessage.append("     " + eInvoiceLoadSummary.getInvoiceLoadFailCount() + " rejected invoices for an approximate total of $ " + eInvoiceLoadSummary.getInvoiceLoadFailAmount().doubleValue() + "\n");
                summaryMessage.append("\n\n");

                savedLoadSummariesMap.put(currentLoadSummary.getVendorDunsNumber(), eInvoiceLoadSummary);

            } else {
                LOG.info("Not saving Load Summary for DUNS '" + dunsNumber + "' because empty indicator is '" + eInvoiceLoadSummary.isEmpty().booleanValue() + "'");
            }
        }

        summaryMessage.append("\n\n");

        for (Iterator rejectIter = eInvoiceLoad.getRejectDocuments().iterator(); rejectIter.hasNext(); ) {
            ElectronicInvoiceRejectDocument rejectDoc = (ElectronicInvoiceRejectDocument) rejectIter.next();
            routeRejectDocument(rejectDoc, savedLoadSummariesMap);
        }

        /**
         * Even if there is an exception in the reject doc routing, all the files marked as reject will
         * be moved to the reject dir
         */
        moveFileList(eInvoiceLoad.getRejectFilesToMove());

        return summaryMessage;
    }

    protected void routeRejectDocument(ElectronicInvoiceRejectDocument rejectDoc,
                                       Map savedLoadSummariesMap) {

        LOG.info("Saving Invoice Reject for DUNS '" + rejectDoc.getVendorDunsNumber() + "'");

        if (savedLoadSummariesMap.containsKey(rejectDoc.getVendorDunsNumber())) {
            rejectDoc.setInvoiceLoadSummary((ElectronicInvoiceLoadSummary) savedLoadSummariesMap.get(rejectDoc.getVendorDunsNumber()));
        } else {
            rejectDoc.setInvoiceLoadSummary((ElectronicInvoiceLoadSummary) savedLoadSummariesMap.get(UNKNOWN_DUNS_IDENTIFIER));
        }

        try {
            SpringContext.getBean(DocumentService.class).routeDocument(rejectDoc, "Routed by electronic invoice batch job", null);
        } catch (WorkflowException e) {
            e.printStackTrace();
        }

    }

    protected void sendSummary(StringBuffer message) {

        String fromMailId = SpringContext.getBean(ParameterService.class).getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.DAILY_SUMMARY_REPORT_FROM_EMAIL_ADDRESS);
        List<String> toMailIds = new ArrayList<String>(SpringContext.getBean(ParameterService.class).getParameterValuesAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.DAILY_SUMMARY_REPORT_TO_EMAIL_ADDRESSES));

        LOG.info("From email address parameter value:" + fromMailId);
        LOG.info("To email address parameter value:" + toMailIds);

        if (StringUtils.isBlank(fromMailId) || toMailIds.isEmpty()) {
            LOG.error("From/To mail addresses are empty. Unable to send the message");
        } else {

            MailMessage mailMessage = new MailMessage();

            mailMessage.setFromAddress(fromMailId);
            setMessageToAddressesAndSubject(mailMessage, toMailIds);
            mailMessage.setMessage(message.toString());

            try {
                mailService.sendMessage(mailMessage);
            } catch (Exception e) {
                LOG.error("Invalid email address. Message not sent", e);
            }
        }

    }

    protected MailMessage setMessageToAddressesAndSubject(MailMessage message, List<String> toAddressList) {

        if (!toAddressList.isEmpty()) {
            for (int i = 0; i < toAddressList.size(); i++) {
                if (StringUtils.isNotEmpty(toAddressList.get(i))) {
                    message.addToAddress(toAddressList.get(i).trim());
                }
            }
        }

        String mailTitle = "E-Invoice Load Results for " + ElectronicInvoiceUtils.getDateDisplayText(SpringContext.getBean(DateTimeService.class).getCurrentDate());

        message.setSubject(mailTitle);
        return message;
    }

    /**
     * This method is responsible for the matching process for a reject document
     *
     * @return true if the matching process is succeed
     */
    @Override
    @NonTransactional
    public boolean doMatchingProcess(ElectronicInvoiceRejectDocument rejectDocument) {

        /**
         * This is needed here since if the user changes the DUNS number.
         */
        validateVendorDetails(rejectDocument);

        Map itemTypeMappings = getItemTypeMappings(rejectDocument.getVendorHeaderGeneratedIdentifier(),
            rejectDocument.getVendorDetailAssignedIdentifier());

        Map kualiItemTypes = getKualiItemTypes();

        ElectronicInvoiceOrderHolder rejectDocHolder = new ElectronicInvoiceOrderHolder(rejectDocument, itemTypeMappings, kualiItemTypes);
        matchingService.doMatchingProcess(rejectDocHolder);

        /**
         * Once we're through with the matching process, it's needed to check whether it's possible
         * to create PREQ for the reject doc
         */
        if (!rejectDocHolder.isInvoiceRejected()) {
            validateInvoiceOrderValidForPREQCreation(rejectDocHolder);
        }

        //  determine which of the reject reasons we should suppress based on the parameter
        List<String> ignoreRejectTypes = new ArrayList<String>(parameterService.getParameterValuesAsString(PurapConstants.PURAP_NAMESPACE, "ElectronicInvoiceReject", "SUPPRESS_REJECT_REASON_CODES_ON_EIRT_APPROVAL"));
        List<ElectronicInvoiceRejectReason> rejectReasonsToDelete = new ArrayList<ElectronicInvoiceRejectReason>();
        for (ElectronicInvoiceRejectReason rejectReason : rejectDocument.getInvoiceRejectReasons()) {
            String rejectedReasonTypeCode = rejectReason.getInvoiceRejectReasonTypeCode();
            if (StringUtils.isNotBlank(rejectedReasonTypeCode)) {
                if (ignoreRejectTypes.contains(rejectedReasonTypeCode)) {
                    rejectReasonsToDelete.add(rejectReason);
                }
            }
        }

        //  remove the flagged reject reasons
        if (!rejectReasonsToDelete.isEmpty()) {
            rejectDocument.getInvoiceRejectReasons().removeAll(rejectReasonsToDelete);
        }

        //  if no reject reasons, then clear error messages
        if (rejectDocument.getInvoiceRejectReasons().isEmpty()) {
            GlobalVariables.getMessageMap().clearErrorMessages();
        }

        //  this automatically returns false if there are no reject reasons
        return !rejectDocHolder.isInvoiceRejected();
    }

    @Override
    @NonTransactional
    public boolean createPaymentRequest(ElectronicInvoiceRejectDocument rejectDocument) {

        if (rejectDocument.getInvoiceRejectReasons().size() > 0) {
            throw new RuntimeException("Not possible to create payment request since the reject document contains " + rejectDocument.getInvoiceRejectReasons().size() + " rejects");
        }

        Map itemTypeMappings = getItemTypeMappings(rejectDocument.getVendorHeaderGeneratedIdentifier(),
            rejectDocument.getVendorDetailAssignedIdentifier());

        Map kualiItemTypes = getKualiItemTypes();

        ElectronicInvoiceOrderHolder rejectDocHolder = new ElectronicInvoiceOrderHolder(rejectDocument, itemTypeMappings, kualiItemTypes);

        /**
         * First, create a new payment request document.  Once this document is created, then update the reject document's PREQ_ID field
         * with the payment request document identifier.  This identifier is used to associate the reject document with the payment request.
         */
        PaymentRequestDocument preqDocument = createPaymentRequest(rejectDocHolder);
        rejectDocument.setPaymentRequestIdentifier(preqDocument.getPurapDocumentIdentifier());

        return !rejectDocHolder.isInvoiceRejected();

    }

    protected PaymentRequestDocument createPaymentRequest(ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Creating Payment Request document");
        }

        KNSGlobalVariables.getMessageList().clear();

        validateInvoiceOrderValidForPREQCreation(orderHolder);

        if (LOG.isInfoEnabled()) {
            if (orderHolder.isInvoiceRejected()) {
                LOG.info("Not possible to convert einvoice details into payment request");
            } else {
                LOG.info("Payment request document creation validation succeeded");
            }
        }

        if (orderHolder.isInvoiceRejected()) {
            return null;
        }

        PaymentRequestDocument preqDoc = null;
        try {
            preqDoc = (PaymentRequestDocument) SpringContext.getBean(DocumentService.class).getNewDocument("PREQ");
        } catch (WorkflowException e) {
            String extraDescription = "Error=" + e.getMessage();
            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_WORKLOW_EXCEPTION,
                extraDescription,
                orderHolder.getFileName());
            orderHolder.addInvoiceOrderRejectReason(rejectReason);
            LOG.error("Error creating Payment request document - " + e.getMessage());
            return null;
        }

        PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();
        if (poDoc == null) {
            throw new RuntimeException("Purchase Order document (POId=" + poDoc.getPurapDocumentIdentifier() + ") does not exist in the system");
        }

        preqDoc.getDocumentHeader().setDocumentDescription(generatePREQDocumentDescription(poDoc));
        try {
            preqDoc.updateAndSaveAppDocStatus(PurapConstants.PaymentRequestStatuses.APPDOC_IN_PROCESS);
        } catch (WorkflowException we) {
            throw new RuntimeException("Unable to save route status data for document: " + preqDoc.getDocumentNumber(), we);
        }

        preqDoc.setInvoiceDate(orderHolder.getInvoiceDate());
        preqDoc.setInvoiceNumber(orderHolder.getInvoiceNumber());
        preqDoc.setVendorInvoiceAmount(new KualiDecimal(orderHolder.getInvoiceNetAmount()));
        preqDoc.setAccountsPayableProcessorIdentifier("E-Invoice");
        preqDoc.setVendorCustomerNumber(orderHolder.getCustomerNumber());
        preqDoc.setPaymentRequestElectronicInvoiceIndicator(true);

        if (orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier() != null) {
            preqDoc.setAccountsPayablePurchasingDocumentLinkIdentifier(orderHolder.getAccountsPayablePurchasingDocumentLinkIdentifier());
        }

        //Copied from PaymentRequestServiceImpl.populatePaymentRequest()
        //set bank code to default bank code in the system parameter
        Bank defaultBank = SpringContext.getBean(BankService.class).getDefaultBankByDocType(preqDoc.getClass());
        if (defaultBank != null) {
            preqDoc.setBankCode(defaultBank.getBankCode());
            preqDoc.setBank(defaultBank);
        }

        RequisitionDocument reqDoc = SpringContext.getBean(RequisitionService.class).getRequisitionById(poDoc.getRequisitionIdentifier());
        String reqDocInitiator = reqDoc.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
        try {
            Person user = KimApiServiceLocator.getPersonService().getPerson(reqDocInitiator);

            setProcessingCampus(preqDoc, user.getCampusCode());

        } catch (Exception e) {
            String extraDescription = "Error setting processing campus code - " + e.getMessage();
            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, extraDescription, orderHolder.getFileName());
            orderHolder.addInvoiceOrderRejectReason(rejectReason);
            return null;
        }

        HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = SpringContext.getBean(AccountsPayableService.class).expiredOrClosedAccountsList(poDoc);
        if (expiredOrClosedAccountList == null) {
            expiredOrClosedAccountList = new HashMap();
        }

        if (LOG.isInfoEnabled()) {
            LOG.info(expiredOrClosedAccountList.size() + " accounts has been found as Expired or Closed");
        }

        preqDoc.populatePaymentRequestFromPurchaseOrder(orderHolder.getPurchaseOrderDocument(), expiredOrClosedAccountList);

        populateItemDetails(preqDoc, orderHolder);

        /**
         * Validate totals,paydate
         */
        //PaymentRequestDocumentRule.processCalculateAccountsPayableBusinessRules
        SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedCalculateAccountsPayableEvent(preqDoc));

        SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc, true);

        processItemsForDiscount(preqDoc, orderHolder);

        if (orderHolder.isInvoiceRejected()) {
            return null;
        }

        SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc, false);
        /**
         * PaymentRequestReview
         */
        //PaymentRequestDocumentRule.processRouteDocumentBusinessRules
        SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedPaymentRequestForEInvoiceEvent(preqDoc));

        if (GlobalVariables.getMessageMap().hasErrors()) {
            if (LOG.isInfoEnabled()) {
                LOG.info("***************Error in rules processing - " + GlobalVariables.getMessageMap());
            }
            Map<String, AutoPopulatingList<ErrorMessage>> errorMessages = GlobalVariables.getMessageMap().getErrorMessages();

            String errors = errorMessages.toString();
            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, errors, orderHolder.getFileName());
            orderHolder.addInvoiceOrderRejectReason(rejectReason);
            return null;
        }

        if (KNSGlobalVariables.getMessageList().size() > 0) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Payment request contains " + KNSGlobalVariables.getMessageList().size() + " warning message(s)");
                for (int i = 0; i < KNSGlobalVariables.getMessageList().size(); i++) {
                    LOG.info("Warning " + i + "  - " + KNSGlobalVariables.getMessageList().get(i));
                }
            }
        }

        addShipToNotes(preqDoc, orderHolder);

        String routingAnnotation = null;
        if (!orderHolder.isRejectDocumentHolder()) {
            routingAnnotation = "Routed by electronic invoice batch job";
        }

        try {
            SpringContext.getBean(DocumentService.class).routeDocument(preqDoc, routingAnnotation, null);
        } catch (WorkflowException e) {
            e.printStackTrace();
            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_FAILURE, e.getMessage(), orderHolder.getFileName());
            orderHolder.addInvoiceOrderRejectReason(rejectReason);
            return null;
        } catch (ValidationException e) {
            String extraDescription = GlobalVariables.getMessageMap().toString();
            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_ROUTING_VALIDATION_ERROR, extraDescription, orderHolder.getFileName());
            orderHolder.addInvoiceOrderRejectReason(rejectReason);
            return null;
        }

        return preqDoc;
    }

    /**
     * This method check OVERRIDE_PROCESSING_CAMPUS parameter to set processing campus code.
     * If parameter value is populated, it set the processing campus to the value in parameter, otherwise use requisition initiator's campus code.
     *
     * @param preqDoc
     * @param initiatorCampusCode
     */
    protected void setProcessingCampus(PaymentRequestDocument preqDoc, String initiatorCampusCode) {
        String campusCode = parameterService.getParameterValueAsString(ElectronicInvoiceStep.class, PurapParameterConstants.ElectronicInvoiceParameters.OVERRIDE_PROCESSING_CAMPUS);
        if (!StringHelper.isNullOrEmpty(campusCode)) {
            preqDoc.setProcessingCampusCode(campusCode);
        } else {
            preqDoc.setProcessingCampusCode(initiatorCampusCode);
        }

    }

    protected void addShipToNotes(PaymentRequestDocument preqDoc,
                                  ElectronicInvoiceOrderHolder orderHolder) {

        String shipToAddress = orderHolder.getInvoiceShipToAddressAsString();

        try {
            Note noteObj = SpringContext.getBean(DocumentService.class).createNoteFromDocument(preqDoc, shipToAddress);
            preqDoc.addNote(noteObj);
        } catch (Exception e) {
            LOG.error("Error creating ShipTo notes - " + e.getMessage());
        }
    }

    protected void processItemsForDiscount(PaymentRequestDocument preqDocument,
                                           ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Processing payment request items for discount");
        }

        if (!orderHolder.isItemTypeAvailableInItemMapping(ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT)) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Skipping discount processing since there is no mapping of discount type for this vendor");
            }
            return;
        }

        if (orderHolder.getInvoiceDiscountAmount() == null ||
            orderHolder.getInvoiceDiscountAmount() == BigDecimal.ZERO) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Skipping discount processing since there is no discount amount found in the invoice file");
            }
            return;
        }

        KualiDecimal discountValueToUse = new KualiDecimal(orderHolder.getInvoiceDiscountAmount().negate());
        List<PaymentRequestItem> preqItems = preqDocument.getItems();

        boolean alreadyProcessedInvoiceDiscount = false;
        boolean hasKualiPaymentTermsDiscountItem = false;

        //if e-invoice amount is negative... it is a penalty and we must pay extra
        for (int i = 0; i < preqItems.size(); i++) {

            PaymentRequestItem preqItem = preqItems.get(i);

            hasKualiPaymentTermsDiscountItem = hasKualiPaymentTermsDiscountItem || (StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE, preqItem.getItemTypeCode()));

            if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, orderHolder)) {

                alreadyProcessedInvoiceDiscount = true;

                if (StringUtils.equals(preqItem.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)) {
                    //Item is kuali payment terms discount item... must perform calculation
                    // if discount item exists on PREQ and discount dollar amount exists... use greater amount
                    if (LOG.isInfoEnabled()) {
                        LOG.info("Discount Check - E-Invoice matches PREQ item type '" + preqItem.getItemTypeCode() + "'... now checking for amount");
                    }

                    KualiDecimal preqExtendedPrice = preqItem.getExtendedPrice() == null ? KualiDecimal.ZERO : preqItem.getExtendedPrice();
                    if ((discountValueToUse.compareTo(preqExtendedPrice)) < 0) {
                        if (LOG.isInfoEnabled()) {
                            LOG.info("Discount Check - Using E-Invoice amount (" + discountValueToUse + ") as it is more discount than current payment terms amount " + preqExtendedPrice);
                        }
                        preqItem.setItemUnitPrice(discountValueToUse.bigDecimalValue());
                        preqItem.setExtendedPrice(discountValueToUse);
                    }
                } else {
                    // item is not payment terms discount item... just add value
                    // if discount item exists on PREQ and discount dollar amount exists... use greater amount
                    if (LOG.isInfoEnabled()) {
                        LOG.info("Discount Check - E-Invoice matches PREQ item type '" + preqItem.getItemTypeCode() + "'");
                        LOG.info("Discount Check - Using E-Invoice amount (" + discountValueToUse + ") as it is greater than payment terms amount");
                    }
                    preqItem.addToUnitPrice(discountValueToUse.bigDecimalValue());
                    preqItem.addToExtendedPrice(discountValueToUse);
                }
            }
        }

        /*
         *   If we have not already processed the discount amount then the mapping is pointed
         *   to an item that is not in the PREQ item list
         *
         *   FYI - FILE DISCOUNT AMOUNT CURRENTLY HARD CODED TO GO INTO PAYMENT TERMS DISCOUNT ITEM ONLY... ALL OTHERS WILL FAIL
         */

        if (!alreadyProcessedInvoiceDiscount) {
            String itemTypeRequired = PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE;
            // if we already have a PMT TERMS DISC item but the e-invoice discount wasn't processed... error out
            // if the item mapping for e-invoice discount item is not PMT TERMS DISC item and we haven't processed it... error out

            if (hasKualiPaymentTermsDiscountItem ||
                !orderHolder.isItemTypeAvailableInItemMapping(ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT)) {
                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.PREQ_DISCOUNT_ERROR, null, orderHolder.getFileName());
                orderHolder.addInvoiceOrderRejectReason(rejectReason);
                return;
            } else {
                PaymentRequestItem newItem = new PaymentRequestItem();
                newItem.setItemUnitPrice(discountValueToUse.bigDecimalValue());
                newItem.setItemTypeCode(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE);
                newItem.setExtendedPrice(discountValueToUse);
                newItem.setPurapDocument(preqDocument);
                preqDocument.addItem(newItem);
            }
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("Completed processing payment request items for discount");
        }

    }

    protected void populateItemDetails(PaymentRequestDocument preqDocument, ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Populating invoice order items into the payment request document");
        }

        List<PurApItem> preqItems = preqDocument.getItems();

        //process all preq items and apply amounts from order holder
        for (int i = 0; i < preqItems.size(); i++) {

            PaymentRequestItem preqItem = (PaymentRequestItem) preqItems.get(i);
            processInvoiceItem(preqItem, orderHolder);

            /**
             * This is not needed since if we have default desc from misc item, then preq rules are expecting the account details for this items
             * AccountsPayableItemBase.isConsideredEntered() returns true if there is any item desc available.
             *
             */
//            setItemDefaultDescription(preqItem);
        }

        //Now we'll add any missing mapping items that did not have
        // an existing payment request item
        addMissingMappedItems(preqItems, orderHolder);

        //as part of a clean up, remove any preq items that have zero or null unit/extended price
        removeEmptyItems(preqItems);

        if (LOG.isInfoEnabled()) {
            LOG.info("Successfully populated the invoice order items");
        }

    }

    /**
     * Removes preq items from the list that have null or zero unit and extended price
     *
     * @param preqItems
     */
    protected void removeEmptyItems(List<PurApItem> preqItems) {

        for (int i = preqItems.size() - 1; i >= 0; i--) {
            PurApItem item = preqItems.get(i);

            //if the unit and extended price have null or zero as a combo, remove item
            if (isNullOrZero(item.getItemUnitPrice()) && isNullOrZero(item.getExtendedPrice())) {
                preqItems.remove(i);
            }
        }
    }

    /**
     * Ensures that the mapped items, item type code, exist as a payment request item so they're
     * process correctly within populateItemDetails
     *
     * @param preqItems
     * @param orderHolder
     */
    protected void addMissingMappedItems(List<PurApItem> preqItems, ElectronicInvoiceOrderHolder orderHolder) {

        PurchasingAccountsPayableDocument purapDoc = null;
        Integer purapDocIdentifier = null;

        //grab all the required item types that should be on the payment request
        List requiredItemTypeCodeList = createInvoiceRequiresItemTypeCodeList(orderHolder);

        if (ObjectUtils.isNotNull(requiredItemTypeCodeList) && !requiredItemTypeCodeList.isEmpty()) {

            //loop through existing payment request items and remove ones we already have
            for (int i = 0; i < preqItems.size(); i++) {
                //if the preq item exists in the list already, remove
                if (requiredItemTypeCodeList.contains(preqItems.get(i).getItemTypeCode())) {
                    requiredItemTypeCodeList.remove(preqItems.get(i).getItemTypeCode());
                }

                //utility grab the document identifier and document
                purapDoc = preqItems.get(i).getPurapDocument();
                purapDocIdentifier = preqItems.get(i).getPurapDocumentIdentifier();
            }

            if (ObjectUtils.isNotNull(requiredItemTypeCodeList) && !requiredItemTypeCodeList.isEmpty()) {
                //if we have any left, it means they didn't exist on the payment request
                // and we must add them.
                for (int i = 0; i < requiredItemTypeCodeList.size(); i++) {
                    PaymentRequestItem preqItem = new PaymentRequestItem();
                    preqItem.resetAccount();
                    preqItem.setPurapDocumentIdentifier(purapDocIdentifier);
                    preqItem.setPurapDocument(purapDoc);
                    preqItem.setItemTypeCode((String) requiredItemTypeCodeList.get(i));

                    //process item
                    processInvoiceItem(preqItem, orderHolder);

                    //Add to preq Items if the value is not zero
                    if ((ObjectUtils.isNotNull(preqItem.getItemUnitPrice()) && preqItem.getItemUnitPrice() != BigDecimal.ZERO) &&
                        (ObjectUtils.isNotNull(preqItem.getExtendedPrice()) && preqItem.getExtendedPrice() != KualiDecimal.ZERO)) {

                        preqItems.add(preqItem);
                    }


                }
            }

        }
    }

    /**
     * Creates a list of item types the eInvoice requirs on
     * the payment request due to valid amounts.
     */
    protected List createInvoiceRequiresItemTypeCodeList(ElectronicInvoiceOrderHolder orderHolder) {
        List itemTypeCodeList = new ArrayList();

        addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, orderHolder);
        addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, orderHolder);
        addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, orderHolder);
        addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DEPOSIT, orderHolder);
        addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DUE, orderHolder);
        addToListIfExists(itemTypeCodeList, ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, orderHolder);

        return itemTypeCodeList;
    }

    /**
     * Utility method to add a kuali item type code to a list from a invoice item type code
     *
     * @param itemTypeCodeList
     * @param invoiceItemTypeCode
     * @param orderHolder
     */
    protected void addToListIfExists(List itemTypeCodeList, String invoiceItemTypeCode, ElectronicInvoiceOrderHolder orderHolder) {

        String itemTypeCode = orderHolder.getKualiItemTypeCodeFromMappings(invoiceItemTypeCode);

        if (ObjectUtils.isNotNull(itemTypeCode)) {
            itemTypeCodeList.add(itemTypeCode);
        }
    }

    /**
     * Finds the mapped item type code to invoice item type code and applies the appropriate values
     * to the correct payment request item.
     *
     * @param preqItem
     * @param orderHolder
     */
    protected void processInvoiceItem(PaymentRequestItem preqItem, ElectronicInvoiceOrderHolder orderHolder) {

        if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_ITEM, orderHolder)) {
            processAboveTheLineItem(preqItem, orderHolder);
        } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_TAX, orderHolder)) {
            processTaxItem(preqItem, orderHolder);
        } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SHIPPING, orderHolder)) {
            processShippingItem(preqItem, orderHolder);
        } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_SPECIAL_HANDLING, orderHolder)) {
            processSpecialHandlingItem(preqItem, orderHolder);
        } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DEPOSIT, orderHolder)) {
            processDepositItem(preqItem, orderHolder);
        } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DUE, orderHolder)) {
            processDueItem(preqItem, orderHolder);
        } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_DISCOUNT, orderHolder)) {
            processDiscountItem(preqItem, orderHolder);
        } else if (isItemValidForUpdation(preqItem.getItemTypeCode(), ElectronicInvoice.INVOICE_AMOUNT_TYPE_CODE_EXMT, orderHolder)) {
            processAboveTheLineItem(preqItem, orderHolder);
        }

    }

    protected void processAboveTheLineItem(PaymentRequestItem purapItem,
                                           ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Processing above the line item");
        }

        ElectronicInvoiceItemHolder itemHolder = orderHolder.getItemByLineNumber(purapItem.getItemLineNumber().intValue());
        if (itemHolder == null) {
            LOG.info("Electronic Invoice does not have item with Ref Item Line number " + purapItem.getItemLineNumber());
            return;
        }

        purapItem.setItemUnitPrice(itemHolder.getInvoiceItemUnitPrice());
        purapItem.setItemQuantity(new KualiDecimal(itemHolder.getInvoiceItemQuantity()));
        purapItem.setItemTaxAmount(new KualiDecimal(itemHolder.getTaxAmount()));
        purapItem.setItemCatalogNumber(itemHolder.getInvoiceItemCatalogNumber());
        purapItem.setItemDescription(itemHolder.getInvoiceItemDescription());

        if (itemHolder.getSubTotalAmount() != null &&
            itemHolder.getSubTotalAmount().compareTo(KualiDecimal.ZERO) != 0) {

            purapItem.setExtendedPrice(itemHolder.getSubTotalAmount());

        } else {

            if (purapItem.getItemQuantity() != null) {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Item number " + purapItem.getItemLineNumber() + " needs calculation of extended " +
                        "price from quantity " + purapItem.getItemQuantity() + " and unit cost " + purapItem.getItemUnitPrice());
                }
                purapItem.setExtendedPrice(purapItem.getItemQuantity().multiply(new KualiDecimal(purapItem.getItemUnitPrice())));
            } else {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Item number " + purapItem.getItemLineNumber() + " has no quantity so extended price " +
                        "equals unit price of " + purapItem.getItemUnitPrice());
                }
                purapItem.setExtendedPrice(new KualiDecimal(purapItem.getItemUnitPrice()));
            }
        }

    }

    protected void processSpecialHandlingItem(PaymentRequestItem purapItem,
                                              ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Processing special handling item");
        }

        purapItem.addToUnitPrice(orderHolder.getInvoiceSpecialHandlingAmount());
        purapItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceSpecialHandlingAmount()));

        String invoiceSpecialHandlingDescription = orderHolder.getInvoiceSpecialHandlingDescription();

        if (invoiceSpecialHandlingDescription == null && (orderHolder.getInvoiceSpecialHandlingAmount() != null && BigDecimal.ZERO.compareTo(orderHolder.getInvoiceSpecialHandlingAmount()) != 0)) {
            invoiceSpecialHandlingDescription = PurapConstants.ElectronicInvoice.DEFAULT_SPECIAL_HANDLING_DESCRIPTION;
        }
        if (StringUtils.isNotEmpty(invoiceSpecialHandlingDescription)) {
            if (StringUtils.isEmpty(purapItem.getItemDescription())) {
                purapItem.setItemDescription(invoiceSpecialHandlingDescription);
            } else {
                purapItem.setItemDescription(purapItem.getItemDescription() + " - " + invoiceSpecialHandlingDescription);
            }
        }

    }

    protected void processTaxItem(PaymentRequestItem preqItem,
                                  ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Processing Tax Item");
        }

        preqItem.addToUnitPrice(orderHolder.getTaxAmount());
        preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getTaxAmount()));

        if (StringUtils.isNotEmpty(orderHolder.getTaxDescription())) {
            if (StringUtils.isEmpty(preqItem.getItemDescription())) {
                preqItem.setItemDescription(orderHolder.getTaxDescription());
            } else {
                preqItem.setItemDescription(preqItem.getItemDescription() + " - " + orderHolder.getTaxDescription());
            }
        }

    }

    protected void processShippingItem(PaymentRequestItem preqItem,
                                       ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Processing Shipping Item");
        }

        preqItem.addToUnitPrice(orderHolder.getInvoiceShippingAmount());
        preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceShippingAmount()));

        if (StringUtils.isNotEmpty(orderHolder.getInvoiceShippingDescription())) {
            if (StringUtils.isEmpty(preqItem.getItemDescription())) {
                preqItem.setItemDescription(orderHolder.getInvoiceShippingDescription());
            } else {
                preqItem.setItemDescription(preqItem.getItemDescription() + " - " + orderHolder.getInvoiceShippingDescription());
            }
        }
    }

    protected void processDiscountItem(PaymentRequestItem preqItem,
                                       ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Processing Discount Item");
        }

        preqItem.addToUnitPrice(orderHolder.getInvoiceDiscountAmount());
        preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDiscountAmount()));
    }


    protected void processDepositItem(PaymentRequestItem preqItem,
                                      ElectronicInvoiceOrderHolder orderHolder) {

        LOG.info("Processing Deposit Item");

        preqItem.addToUnitPrice(orderHolder.getInvoiceDepositAmount());
        preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDepositAmount()));

    }

    protected void processDueItem(PaymentRequestItem preqItem,
                                  ElectronicInvoiceOrderHolder orderHolder) {

        LOG.info("Processing Deposit Item");

        preqItem.addToUnitPrice(orderHolder.getInvoiceDueAmount());
        preqItem.addToExtendedPrice(new KualiDecimal(orderHolder.getInvoiceDueAmount()));
    }

    protected boolean isNullOrZero(BigDecimal value) {

        if (ObjectUtils.isNull(value) || value.compareTo(BigDecimal.ZERO) == 0) {
            return true;
        } else {
            return false;
        }
    }

    protected boolean isNullOrZero(KualiDecimal value) {

        if (ObjectUtils.isNull(value) || value.isZero()) {
            return true;
        } else {
            return false;
        }
    }

    protected void setItemDefaultDescription(PaymentRequestItem preqItem) {

        //If description is empty and item is not type "ITEM"... use default description
        if (StringUtils.isEmpty(preqItem.getItemDescription()) &&
            !StringUtils.equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_ITEM_CODE, preqItem.getItemTypeCode())) {
            if (ArrayUtils.contains(PurapConstants.ElectronicInvoice.ITEM_TYPES_REQUIRES_DESCRIPTION, preqItem.getItemTypeCode())) {
                preqItem.setItemDescription(PurapConstants.ElectronicInvoice.DEFAULT_BELOW_LINE_ITEM_DESCRIPTION);
            }
        }
    }

    protected boolean isItemValidForUpdation(String itemTypeCode,
                                             String invoiceItemTypeCode,
                                             ElectronicInvoiceOrderHolder orderHolder) {

        boolean isItemTypeAvailableInItemMapping = orderHolder.isItemTypeAvailableInItemMapping(invoiceItemTypeCode);
        String itemTypeCodeFromMappings = orderHolder.getKualiItemTypeCodeFromMappings(invoiceItemTypeCode);
        return isItemTypeAvailableInItemMapping && StringUtils.equals(itemTypeCodeFromMappings, itemTypeCode);
    }


    protected String generatePREQDocumentDescription(PurchaseOrderDocument poDocument) {
        String description = "PO: " + poDocument.getPurapDocumentIdentifier() + " Vendor: " + poDocument.getVendorName() + " Electronic Invoice";
        return checkDescriptionLengthAndStripIfNeeded(description);
    }

    /**
     * This validates an electronic invoice and makes sure it can be turned into a Payment Request
     */
    @NonTransactional
    public void validateInvoiceOrderValidForPREQCreation(ElectronicInvoiceOrderHolder orderHolder) {

        if (LOG.isInfoEnabled()) {
            LOG.info("Validiting ElectronicInvoice Order to make sure that it can be turned into a Payment Request document");
        }

        PurchaseOrderDocument poDoc = orderHolder.getPurchaseOrderDocument();

        if (poDoc == null) {
            throw new RuntimeException("PurchaseOrder not available");
        }

        if (!orderHolder.isInvoiceNumberAcceptIndicatorEnabled()) {
            List preqs = paymentRequestService.getPaymentRequestsByVendorNumberInvoiceNumber(poDoc.getVendorHeaderGeneratedIdentifier(),
                poDoc.getVendorDetailAssignedIdentifier(),
                orderHolder.getInvoiceNumber());

            if (preqs != null && preqs.size() > 0) {
                ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_ORDER_DUPLICATE, null, orderHolder.getFileName());
                orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_NUMBER, PurapKeyConstants.ERROR_REJECT_INVOICE_DUPLICATE);
                return;
            }
        }

        if (orderHolder.getInvoiceDate() == null) {
            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_INVALID, null, orderHolder.getFileName());
            orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE, PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_INVALID);
            return;
        } else if (orderHolder.getInvoiceDate().after(dateTimeService.getCurrentDate())) {
            ElectronicInvoiceRejectReason rejectReason = matchingService.createRejectReason(PurapConstants.ElectronicInvoice.INVOICE_DATE_GREATER, null, orderHolder.getFileName());
            orderHolder.addInvoiceOrderRejectReason(rejectReason, PurapConstants.ElectronicInvoice.RejectDocumentFields.INVOICE_FILE_DATE, PurapKeyConstants.ERROR_REJECT_INVOICE_DATE_GREATER);
            return;
        }

    }

    protected void moveFileList(Map filesToMove) {
        for (Iterator iter = filesToMove.keySet().iterator(); iter.hasNext(); ) {
            File fileToMove = (File) iter.next();

            boolean success = this.moveFile(fileToMove, (String) filesToMove.get(fileToMove));
            if (!success) {
                String errorMessage = "File with name '" + fileToMove.getName() + "' could not be moved";
                throw new PurError(errorMessage);
            }
        }
    }

    protected boolean moveFile(File fileForMove, String location) {
        File moveDir = new File(location);
        boolean success = fileForMove.renameTo(new File(moveDir, fileForMove.getName()));
        return success;
    }

    protected void deleteDoneFile(File invoiceFile) {
        File doneFile = new File(invoiceFile.getAbsolutePath().replace(electronicInvoiceInputFileType.getFileExtension(), ".done"));
        if (doneFile.exists()) {
            doneFile.delete();
        }
    }

    /**
     * returns a list of all error messages as a string
     *
     * @param errorMap
     * @return
     */
    protected String getErrorMessages(Map<String, ArrayList> errorMap) {

        ArrayList errorMessages = null;
        ErrorMessage errorMessage = null;
        StringBuffer errorList = new StringBuffer("");
        String errorText = null;

        for (Map.Entry<String, ArrayList> errorEntry : errorMap.entrySet()) {

            errorMessages = errorEntry.getValue();

            for (int i = 0; i < errorMessages.size(); i++) {

                errorMessage = (ErrorMessage) errorMessages.get(i);

                // get error text
                errorText = kualiConfigurationService
                    .getPropertyValueAsString(errorMessage.getErrorKey());
                // apply parameters
                errorText = MessageFormat.format(errorText,
                    (Object[]) errorMessage.getMessageParameters());

                // add key and error message together
                errorList.append(errorText + "\n");
            }
        }

        return errorList.toString();
    }

    protected String getBaseDirName() {
        return electronicInvoiceInputFileType.getDirectoryPath() + File.separator;
    }

    @NonTransactional
    public String getRejectDirName() {
        return getBaseDirName() + "reject" + File.separator;
    }

    @NonTransactional
    public String getAcceptDirName() {
        return getBaseDirName() + "accept" + File.separator;
    }

    protected File getInvoiceFile(String fileName) {
        return new File(getBaseDirName() + fileName);
    }

    protected ElectronicInvoiceLoadSummary saveElectronicInvoiceLoadSummary(ElectronicInvoiceLoadSummary eils) {
        SpringContext.getBean(BusinessObjectService.class).save(eils);
        eils.refreshNonUpdateableReferences();
        return eils;
    }

    @NonTransactional
    public void setElectronicInvoiceInputFileType(ElectronicInvoiceInputFileType electronicInvoiceInputFileType) {
        this.electronicInvoiceInputFileType = electronicInvoiceInputFileType;
    }

    @NonTransactional
    public void setMailService(MailService mailService) {
        this.mailService = mailService;
    }

    @NonTransactional
    public void setElectronicInvoicingDao(ElectronicInvoicingDao electronicInvoicingDao) {
        this.electronicInvoicingDao = electronicInvoicingDao;
    }

    @NonTransactional
    public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
        this.batchInputFileService = batchInputFileService;
    }

    @NonTransactional
    public void setElectronicInvoiceMatchingService(ElectronicInvoiceMatchingService matchingService) {
        this.matchingService = matchingService;
    }

    @NonTransactional
    public void setVendorService(VendorService vendorService) {
        this.vendorService = vendorService;
    }

    @NonTransactional
    public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
        this.purchaseOrderService = purchaseOrderService;
    }

    @NonTransactional
    public void setPaymentRequestService(PaymentRequestService paymentRequestService) {
        this.paymentRequestService = paymentRequestService;
    }

    @NonTransactional
    public void setConfigurationService(ConfigurationService kualiConfigurationService) {
        this.kualiConfigurationService = kualiConfigurationService;
    }

    @NonTransactional
    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

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

    /**
     * @see org.kuali.kfs.sys.batch.service.impl.InitiateDirectoryImpl#getRequiredDirectoryNames()
     */
    @Override
    @NonTransactional
    public List<String> getRequiredDirectoryNames() {
        return new ArrayList<String>() {{
            add(getBaseDirName());
            add(getAcceptDirName());
            add(getRejectDirName());
        }};
    }

}

