/*
 * 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.cam.batch.service.impl;

import org.apache.commons.lang.StringUtils;
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.krad.bo.AdHocRoutePerson;
import org.kuali.kfs.krad.bo.AdHocRouteRecipient;
import org.kuali.kfs.krad.document.Document;
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.util.GlobalVariables;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.CamsPropertyConstants;
import org.kuali.kfs.module.cam.batch.service.AssetBarcodeInventoryLoadService;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.cam.businessobject.AssetLocation;
import org.kuali.kfs.module.cam.businessobject.BarcodeInventoryErrorDetail;
import org.kuali.kfs.module.cam.document.BarcodeInventoryErrorDocument;
import org.kuali.kfs.module.cam.document.validation.event.ValidateBarcodeInventoryEvent;
import org.kuali.kfs.module.cam.document.web.struts.AssetBarCodeInventoryInputFileForm;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.document.WorkflowDocumentService;
import org.kuali.rice.kew.api.exception.WorkflowException;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Implementation of the AssetBarcodeInventoryLoadService interface. Handles loading, parsing, and storing of incoming barcode
 * inventory files.
 */
public class AssetBarcodeInventoryLoadServiceImpl implements AssetBarcodeInventoryLoadService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetBarcodeInventoryLoadServiceImpl.class);

    public static final String MESSAGE_NO_DOCUMENT_CREATED = "NO barcode inventory error document was created.";
    public static final String DOCUMENTS_MSG = "The following barcode inventory error document were created";
    public static final String TOTAL_RECORDS_UPLOADED_MSG = "Total records uploaded";
    public static final String TOTAL_RECORDS_IN_ERROR_MSG = "Total records in error";

    protected static final int MAX_NUMBER_OF_COLUMNS = 8;
    protected static final String DOCUMENT_EXPLANATION = "BARCODE ERROR INVENTORY";

    private BusinessObjectService businessObjectService;
    private WorkflowDocumentService workflowDocumentService;
    private DataDictionaryService dataDictionaryService;
    private KualiRuleService kualiRuleService;
    private DocumentService documentService;
    private ParameterService parameterService;
    private DateTimeService dateTimeService;

    /**
     * Determines whether or not the BCIE document has all its records corrected or deleted
     *
     * @param document
     * @return boolean
     */
    public boolean isFullyProcessed(Document document) {
        BarcodeInventoryErrorDocument barcodeInventoryErrorDocument = (BarcodeInventoryErrorDocument) document;
        boolean result = true;
        List<BarcodeInventoryErrorDetail> barcodeInventoryErrorDetails = barcodeInventoryErrorDocument.getBarcodeInventoryErrorDetail();
        BarcodeInventoryErrorDetail barcodeInventoryErrorDetail;

        for (BarcodeInventoryErrorDetail detail : barcodeInventoryErrorDetails) {
            if (detail.getErrorCorrectionStatusCode().equals(CamsConstants.BarCodeInventoryError.STATUS_CODE_ERROR)) {
                result = false;
                break;
            }
        }
        return result;
    }

    /**
     * @see org.kuali.kfs.module.cam.batch.service.AssetBarcodeInventoryLoadService#isCurrentUserInitiator(org.kuali.rice.krad.document.Document)
     */
    public boolean isCurrentUserInitiator(Document document) {
        if (document != null) {
            return GlobalVariables.getUserSession().getPerson().getPrincipalId().equalsIgnoreCase(document.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId());
        }
        return false;
    }

    /**
     * @see org.kuali.module.cams.service.AssetBarcodeInventoryLoadService#isFileFormatValid(java.io.File)
     */
    public boolean isFileFormatValid(File file) {
        LOG.debug("isFileFormatValid(File file) - start");
        String fileName = file.getName();

        BufferedReader input = null;

        // Getting the length of each field that needs to be validated
        Integer campusTagNumberMaxLength = dataDictionaryService.getAttributeMaxLength(Asset.class, CamsPropertyConstants.Asset.CAMPUS_TAG_NUMBER);
        Integer inventoryScannedCodeMaxLength = new Integer(1);
        Integer InventoryDateMaxLength = dataDictionaryService.getAttributeMaxLength(BarcodeInventoryErrorDetail.class, CamsPropertyConstants.BarcodeInventory.INVENTORY_DATE);
        Integer campusCodeMaxLength = dataDictionaryService.getAttributeMaxLength(Asset.class, CamsPropertyConstants.Asset.CAMPUS_CODE);
        Integer buildingCodeMaxLength = dataDictionaryService.getAttributeMaxLength(Asset.class, CamsPropertyConstants.Asset.BUILDING_CODE);
        Integer buildingRoomNumberMaxLength = dataDictionaryService.getAttributeMaxLength(Asset.class, CamsPropertyConstants.Asset.BUILDING_ROOM_NUMBER);
        Integer buildingSubRoomNumberMaxLength = dataDictionaryService.getAttributeMaxLength(Asset.class, CamsPropertyConstants.Asset.BUILDING_SUB_ROOM_NUMBER);
        Integer conditionCodeMaxLength = dataDictionaryService.getAttributeMaxLength(Asset.class, CamsPropertyConstants.Asset.CONDITION_CODE);

        // Getting the label of each field from data dictionary.
        String campusTagNumberLabel = dataDictionaryService.getAttributeLabel(Asset.class, CamsPropertyConstants.Asset.CAMPUS_TAG_NUMBER);
        String inventoryScannedCodeLabel = dataDictionaryService.getAttributeLabel(Asset.class, CamsPropertyConstants.BarcodeInventory.UPLOAD_SCAN_INDICATOR);
        String InventoryDateLabel = dataDictionaryService.getAttributeLabel(BarcodeInventoryErrorDetail.class, CamsPropertyConstants.BarcodeInventory.INVENTORY_DATE);
        String campusCodeLabel = dataDictionaryService.getAttributeLabel(Asset.class, CamsPropertyConstants.Asset.CAMPUS_CODE);
        String buildingCodeLabel = dataDictionaryService.getAttributeLabel(Asset.class, CamsPropertyConstants.Asset.BUILDING_CODE);
        String buildingRoomNumberLabel = dataDictionaryService.getAttributeLabel(Asset.class, CamsPropertyConstants.Asset.BUILDING_ROOM_NUMBER);
        String buildingSubRoomNumberLabel = dataDictionaryService.getAttributeLabel(Asset.class, CamsPropertyConstants.Asset.BUILDING_SUB_ROOM_NUMBER);
        String conditionCodeLabel = dataDictionaryService.getAttributeLabel(Asset.class, CamsPropertyConstants.Asset.CONDITION_CODE);

        try {
            int recordCount = 0;
            String errorMsg = "";
            String errorMessage = "";
            boolean proceed = true;
            String lengthError = "exceeds maximum length";

            input = new BufferedReader(new FileReader(file));
            String line = null;


            while ((line = input.readLine()) != null) {
                recordCount++;
                errorMsg = "";
                line = StringUtils.remove(line, "\"");

                String[] column = org.springframework.util.StringUtils.delimitedListToStringArray(line, ",");

                if (MAX_NUMBER_OF_COLUMNS < column.length) {
                    // Error more columns that allowed. put it in the constants class.
                    errorMsg += "  Barcode inventory file has record(s) with more than " + MAX_NUMBER_OF_COLUMNS + " columns\n";
                    proceed = false;
                } else if (MAX_NUMBER_OF_COLUMNS > column.length) {
                    errorMsg += "  Barcode inventory file has record(s) with less than " + MAX_NUMBER_OF_COLUMNS + " columns\n";
                    proceed = false;
                } else {

                    // Validating length of each field
                    if (column[0].length() > campusTagNumberMaxLength.intValue()) {
                        errorMsg += ", " + campusTagNumberLabel;
                    }

                    if (column[1].length() > inventoryScannedCodeMaxLength.intValue()) {
                        errorMsg += ", " + inventoryScannedCodeLabel;
                    }

                    if (column[2].length() > InventoryDateMaxLength.intValue()) {
                        errorMsg += ", " + InventoryDateLabel;
                    }

                    if (column[3].length() > campusCodeMaxLength.intValue()) {
                        errorMsg += ", " + campusCodeLabel;
                    }
                    if (column[4].length() > buildingCodeMaxLength.intValue()) {
                        errorMsg += ", " + buildingCodeLabel;
                    }
                    if (column[5].length() > buildingRoomNumberMaxLength.intValue()) {
                        errorMsg += ", " + buildingRoomNumberLabel;
                    }
                    if (column[6].length() > buildingSubRoomNumberMaxLength.intValue()) {
                        errorMsg += ", " + buildingSubRoomNumberLabel;
                    }
                    if (column[7].length() > conditionCodeMaxLength.intValue()) {
                        errorMsg += ", " + conditionCodeLabel;
                    }

                    if (!StringUtils.isBlank(errorMsg)) {
                        errorMsg += " " + lengthError;
                    }

                    // Validating other than the length of the fields
                    if (!column[1].equals(CamsConstants.BarCodeInventory.BCI_SCANED_INTO_DEVICE) && !column[1].equals(CamsConstants.BarCodeInventory.BCI_MANUALLY_KEYED_CODE)) {
                        errorMsg += ", " + inventoryScannedCodeLabel + " is invalid";
                    }

                    // validate date
                    if (!validateDate(column[2])) {
                        errorMsg += ", " + InventoryDateLabel + " is invalid";
                    }


                }
                if (!StringUtils.isBlank(errorMsg)) {
                    errorMsg = "Error on record number " + recordCount + ": " + errorMsg.substring(2) + "\n";
                    GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM, errorMsg);
                    errorMessage += errorMsg;
                    LOG.error(errorMsg);
                }
                if (!proceed)
                    break;
            }
            if (!StringUtils.isBlank(errorMessage)) {
                return false;
            }

            return true;
        } catch (FileNotFoundException e1) {
            LOG.error("file to parse not found " + fileName, e1);
            throw new RuntimeException("Cannot find the file requested to be parsed " + fileName + " " + e1.getMessage(), e1);
        } catch (Exception e) {
            LOG.error("Error running file validation - File: " + fileName, e);
            throw new IllegalArgumentException("Error running file validation - File: " + fileName);
        } finally {
            LOG.debug("isFileFormatValid(File file) - end");
            try {
                if (input != null) {
                    input.close();
                }
            } catch (IOException ex) {
                LOG.error("isFileFormatValid() error closing file.", ex);
            }
        }

    }

    /**
     * @see org.kuali.module.cams.service.AssetBarCodeInventoryLoadService#processFile(java.io.File)
     */
    public boolean processFile(File file, AssetBarCodeInventoryInputFileForm form) {
        LOG.debug("processFile(File file) - start");

        // Removing *.done files that are created automatically by the framework.
        this.removeDoneFile(file);

        BufferedReader input = null;
        String fileName = file.getName();

        String day;
        String month;
        String year;
        String hours;
        String minutes;
        String seconds;
        boolean isValid = true;

        SimpleDateFormat formatter = new SimpleDateFormat(CamsConstants.DateFormats.MONTH_DAY_YEAR + " " + CamsConstants.DateFormats.MILITARY_TIME, Locale.US);
        formatter.setLenient(false);

        BarcodeInventoryErrorDetail barcodeInventoryErrorDetail;
        List<BarcodeInventoryErrorDetail> barcodeInventoryErrorDetails = new ArrayList<BarcodeInventoryErrorDetail>();

        List<BarcodeInventoryErrorDocument> barcodeInventoryErrorDocuments = new ArrayList<BarcodeInventoryErrorDocument>();
        try {
            Long ln = new Long(1);
            input = new BufferedReader(new FileReader(file));
            String line = null;

            while ((line = input.readLine()) != null) {
                line = StringUtils.remove(line, "\"");
                String[] lineStrings = org.springframework.util.StringUtils.delimitedListToStringArray(line, ",");

                // Parsing date so it can be validated.
                lineStrings[2] = StringUtils.rightPad(lineStrings[2].trim(), 14, "0");

                day = lineStrings[2].substring(0, 2);
                month = lineStrings[2].substring(2, 4);
                year = lineStrings[2].substring(4, 8);
                hours = lineStrings[2].substring(8, 10);
                minutes = lineStrings[2].substring(10, 12);
                seconds = lineStrings[2].substring(12);

                String stringDate = month + "/" + day + "/" + year + " " + hours + ":" + minutes + ":" + seconds;
                Timestamp timestamp = null;

                // If date has invalid format set its value to null
                try {
                    timestamp = new Timestamp(formatter.parse(stringDate).getTime());
                } catch (Exception e) {
                }

                // Its set to null because for some reason java parses "00000000000000" as 0002-11-30
                if (lineStrings[2].equals(StringUtils.repeat("0", 14))) {
                    timestamp = null;
                }

                barcodeInventoryErrorDetail = new BarcodeInventoryErrorDetail();
                barcodeInventoryErrorDetail.setUploadRowNumber(ln);
                barcodeInventoryErrorDetail.setAssetTagNumber(lineStrings[0].trim());
                barcodeInventoryErrorDetail.setUploadScanIndicator(lineStrings[1].equals(CamsConstants.BarCodeInventory.BCI_SCANED_INTO_DEVICE));
                barcodeInventoryErrorDetail.setUploadScanTimestamp(timestamp);
                barcodeInventoryErrorDetail.setCampusCode(lineStrings[3].trim().toUpperCase());
                barcodeInventoryErrorDetail.setBuildingCode(lineStrings[4].trim().toUpperCase());
                barcodeInventoryErrorDetail.setBuildingRoomNumber(lineStrings[5].trim().toUpperCase());
                barcodeInventoryErrorDetail.setBuildingSubRoomNumber(lineStrings[6].trim().toUpperCase());
                barcodeInventoryErrorDetail.setAssetConditionCode(lineStrings[7].trim().toUpperCase());
                barcodeInventoryErrorDetail.setErrorCorrectionStatusCode(CamsConstants.BarCodeInventoryError.STATUS_CODE_ERROR);
                barcodeInventoryErrorDetail.setCorrectorUniversalIdentifier(GlobalVariables.getUserSession().getPerson().getPrincipalId());

                barcodeInventoryErrorDetails.add(barcodeInventoryErrorDetail);
                ln++;
            }
            processBarcodeInventory(barcodeInventoryErrorDetails, form);

            return true;
        } catch (FileNotFoundException e1) {
            LOG.error("file to parse not found " + fileName, e1);
            throw new RuntimeException("Cannot find the file requested to be parsed " + fileName + " " + e1.getMessage(), e1);
        } catch (Exception ex) {
            LOG.error("Error reading file", ex);
            throw new IllegalArgumentException("Error reading file: " + ex.getMessage(), ex);
        } finally {
            LOG.debug("processFile(File file) - End");

            try {
                if (input != null) {
                    input.close();
                }
            } catch (IOException ex) {
                LOG.error("loadFlatFile() error closing file.", ex);
            }
        }
    }

    /**
     * This method removes the *.done files. If not deleted, then the program will display the name of the file in a puldown menu
     * with a label of ready for process.
     *
     * @param file
     */
    protected void removeDoneFile(File file) {
        String filePath = file.getAbsolutePath();
        File doneFile = new File(StringUtils.substringBeforeLast(filePath, ".") + ".done");

        if (doneFile.exists()) {
            doneFile.delete();
        }
    }

    /**
     * This method invokes the rules in order to validate each records of the barcode file and invokes the method that updates the
     * asset table with the records that passes the rules validation
     *
     * @param barcodeInventoryErrorDetails
     */
    protected void processBarcodeInventory(List<BarcodeInventoryErrorDetail> barcodeInventoryErrorDetails, AssetBarCodeInventoryInputFileForm form) throws Exception {
        Long lineNumber = new Long(0);
        boolean docCreated = false;
        int errorRecCount = 0;
        int totalRecCount = 0;

        BarcodeInventoryErrorDocument barcodeInventoryErrorDocument = createInvalidBarcodeInventoryDocument(barcodeInventoryErrorDetails, form.getUploadDescription());
        // apply rules for the new cash control detail
        kualiRuleService.applyRules(new ValidateBarcodeInventoryEvent("", barcodeInventoryErrorDocument, true));

        List<BarcodeInventoryErrorDetail> tmpBarcodeInventoryErrorDetails = new ArrayList<BarcodeInventoryErrorDetail>();

        for (BarcodeInventoryErrorDetail barcodeInventoryErrorDetail : barcodeInventoryErrorDetails) {
            totalRecCount++;
            // if no error found, then update asset table.
            if (!barcodeInventoryErrorDetail.getErrorCorrectionStatusCode().equals(CamsConstants.BarCodeInventoryError.STATUS_CODE_ERROR)) {
                this.updateAssetInformation(barcodeInventoryErrorDetail, true);
            } else {
                errorRecCount++;
                lineNumber++;
                // Assigning the row number to each invalid BCIE record
                barcodeInventoryErrorDetail.setUploadRowNumber(lineNumber);

                // Storing in temp collection the invalid BCIE records.
                tmpBarcodeInventoryErrorDetails.add(barcodeInventoryErrorDetail);
            }
        }
        // *********************************************************************
        // Storing the invalid barcode inventory records.
        // *********************************************************************
        String documentsCreated = "";
        if (!tmpBarcodeInventoryErrorDetails.isEmpty()) {
            documentsCreated = this.createBarcodeInventoryErrorDocuments(tmpBarcodeInventoryErrorDetails, barcodeInventoryErrorDocument, form);
            docCreated = true;
        }

        if (!docCreated) {
            form.getMessages().add(MESSAGE_NO_DOCUMENT_CREATED);
        } else {
            // Adding the list of documents that were created in the message list
            form.getMessages().add(DOCUMENTS_MSG + ": " + documentsCreated.substring(2));
        }
        form.getMessages().add(TOTAL_RECORDS_UPLOADED_MSG + ": " + StringUtils.rightPad(Integer.toString(totalRecCount), 5, " "));
        form.getMessages().add(TOTAL_RECORDS_IN_ERROR_MSG + ": " + StringUtils.rightPad(Integer.toString(errorRecCount), 5, " "));
    }


    /**
     * This method...
     *
     * @param bcies
     * @param barcodeInventoryErrorDocument
     */
    protected String createBarcodeInventoryErrorDocuments(List<BarcodeInventoryErrorDetail> bcies, BarcodeInventoryErrorDocument barcodeInventoryErrorDocument, AssetBarCodeInventoryInputFileForm form) {
        List<BarcodeInventoryErrorDetail> barcodeInventoryErrorDetails = new ArrayList<BarcodeInventoryErrorDetail>();
        boolean isFirstDocument = true;
        int ln = 0;
        int bcieCount = 0;
        String documentsCreated = "";
        int maxNumberRecordsPerDocument = 300;

        try {
            if (parameterService.parameterExists(BarcodeInventoryErrorDocument.class, CamsConstants.Parameters.MAX_NUMBER_OF_RECORDS_PER_DOCUMENT)) {
                maxNumberRecordsPerDocument = new Integer(parameterService.getParameterValueAsString(BarcodeInventoryErrorDocument.class, CamsConstants.Parameters.MAX_NUMBER_OF_RECORDS_PER_DOCUMENT)).intValue();
            }

            while (true) {
                if ((ln > maxNumberRecordsPerDocument) || (bcieCount >= bcies.size())) {
                    // This if was added in order to not waste the document already created and not create a new one.
                    if (!isFirstDocument) {
                        barcodeInventoryErrorDocument = createInvalidBarcodeInventoryDocument(barcodeInventoryErrorDetails, form.getUploadDescription());
                    }
                    documentsCreated += ", " + barcodeInventoryErrorDocument.getDocumentNumber();

                    barcodeInventoryErrorDocument.setBarcodeInventoryErrorDetail(barcodeInventoryErrorDetails);
                    saveInvalidBarcodeInventoryDocument(barcodeInventoryErrorDocument);

                    barcodeInventoryErrorDetails = new ArrayList<BarcodeInventoryErrorDetail>();

                    if (bcieCount >= bcies.size())
                        break;

                    ln = 0;
                    isFirstDocument = false;
                }

                BarcodeInventoryErrorDetail barcodeInventoryErrorDetail = bcies.get(bcieCount);
                barcodeInventoryErrorDetail.setUploadRowNumber(Long.valueOf(ln + 1));
                barcodeInventoryErrorDetails.add(barcodeInventoryErrorDetail);

                ln++;
                bcieCount++;
            }
        } catch (Exception e) {
            LOG.error("Error creating BCIE documents", e);
            throw new IllegalArgumentException("Error creating BCIE documents: " + e.getMessage(), e);
        }
        return documentsCreated;
    }


    /**
     * This method updates the asset information particularly the building code, bulding room, building subrool, campus code, and
     * condition code
     *
     * @param barcodeInventoryErrorDetail
     */
    public void updateAssetInformation(BarcodeInventoryErrorDetail barcodeInventoryErrorDetail, boolean updateWithDateAssetWasScanned) {
        Map<String, String> fieldValues = new HashMap<String, String>();
        fieldValues.put(CamsPropertyConstants.Asset.CAMPUS_TAG_NUMBER, barcodeInventoryErrorDetail.getAssetTagNumber());
        Asset asset = ((List<Asset>) businessObjectService.findMatching(Asset.class, fieldValues)).get(0);

        asset.setInventoryScannedCode((barcodeInventoryErrorDetail.isUploadScanIndicator() ? CamsConstants.BarCodeInventory.BCI_SCANED_INTO_DEVICE : CamsConstants.BarCodeInventory.BCI_MANUALLY_KEYED_CODE));
        asset.setBuildingCode(barcodeInventoryErrorDetail.getBuildingCode());
        asset.setBuildingRoomNumber(barcodeInventoryErrorDetail.getBuildingRoomNumber());
        asset.setBuildingSubRoomNumber(barcodeInventoryErrorDetail.getBuildingSubRoomNumber());
        asset.setCampusCode(barcodeInventoryErrorDetail.getCampusCode());
        asset.setConditionCode(barcodeInventoryErrorDetail.getAssetConditionCode());

        // set building code and room number to null if they are empty string, to avoid FK violation exception
        if (StringUtils.isEmpty(asset.getBuildingCode())) {
            asset.setBuildingCode(null);
            asset.setBuilding(null);
        }
        if (StringUtils.isEmpty(asset.getBuildingRoomNumber())) {
            asset.setBuildingRoomNumber(null);
            asset.setBuildingRoom(null);
        }

        if (updateWithDateAssetWasScanned) {
            asset.setLastInventoryDate(barcodeInventoryErrorDetail.getUploadScanTimestamp());
        } else {
            asset.setLastInventoryDate(new Timestamp(dateTimeService.getCurrentSqlDate().getTime()));
        }

        // Purposefully deleting off-campus locations when loading locations via barcode scanning.
        List<AssetLocation> assetLocations = asset.getAssetLocations();
        for (AssetLocation assetLocation : assetLocations) {
            if (CamsConstants.AssetLocationTypeCode.OFF_CAMPUS.equals(assetLocation.getAssetLocationTypeCode())) {
                assetLocations.remove(assetLocation);
                break;
            }
        }

        // Updating asset information
        businessObjectService.save(asset);
    }

    /**
     * This method creates a transaction document with the invalid barcode inventory records
     *
     * @param barcodeInventoryErrorDetails
     * @return BarcodeInventoryErrorDocument
     */
    protected BarcodeInventoryErrorDocument createInvalidBarcodeInventoryDocument(List<BarcodeInventoryErrorDetail> barcodeInventoryErrorDetails, String uploadDescription) throws WorkflowException {
        BarcodeInventoryErrorDocument document = (BarcodeInventoryErrorDocument) documentService.getNewDocument(BarcodeInventoryErrorDocument.class);

        document.getDocumentHeader().setExplanation(DOCUMENT_EXPLANATION);
        document.getFinancialSystemDocumentHeader().setFinancialDocumentTotalAmount(KualiDecimal.ZERO);
        document.getDocumentHeader().setDocumentDescription(uploadDescription);
        document.setUploaderUniversalIdentifier(GlobalVariables.getUserSession().getPerson().getPrincipalId());
        document.setBarcodeInventoryErrorDetail(barcodeInventoryErrorDetails);

        return document;
    }


    /**
     * saves the barcode inventory document
     *
     * @param document
     */
    protected void saveInvalidBarcodeInventoryDocument(BarcodeInventoryErrorDocument document) {
        try {
            // The errors are being deleted because, when the document services finds any error then, changes are not saved.
            GlobalVariables.clear();

            // no adhoc recipient need to add when submit doc. doc will route to the doc uploader, i.e. initiator automtically.
            List<AdHocRouteRecipient> adHocRouteRecipients = new ArrayList<AdHocRouteRecipient>();
            documentService.routeDocument(document, "Routed Update Barcode Inventory Document", adHocRouteRecipients);
        } catch (Exception e) {
            LOG.error("Error persisting document # " + document.getDocumentHeader().getDocumentNumber() + " " + e.getMessage(), e);
            throw new RuntimeException("Error persisting document # " + document.getDocumentHeader().getDocumentNumber() + " " + e.getMessage(), e);
        }
    }

    /**
     * This method builds a recipient for Approval.
     *
     * @param userId
     * @return
     */
    protected AdHocRouteRecipient buildApprovePersonRecipient(String userId) {
        AdHocRouteRecipient adHocRouteRecipient = new AdHocRoutePerson();
        adHocRouteRecipient.setActionRequested(KewApiConstants.ACTION_REQUEST_APPROVE_REQ);
        adHocRouteRecipient.setId(userId);
        return adHocRouteRecipient;
    }

    private boolean validateDate(String date) {
        // Parsing date so it can be validated.
        boolean valid = true;
        if (StringHelper.isEmpty(date)) {
            valid = false;
        } else {
            SimpleDateFormat formatter = new SimpleDateFormat(CamsConstants.DateFormats.MONTH_DAY_YEAR + " " + CamsConstants.DateFormats.STANDARD_TIME, Locale.US);
            date = StringUtils.rightPad(date.trim(), 14, "0");
            String day = date.substring(0, 2);
            String month = date.substring(2, 4);
            String year = date.substring(4, 8);
            String hours = date.substring(8, 10);
            String minutes = date.substring(10, 12);
            String seconds = date.substring(12);

            String stringDate = month + "/" + day + "/" + year + " " + hours + ":" + minutes + ":" + seconds;
            Timestamp timestamp = null;

            // If date has invalid format set its value to null
            try {
                timestamp = new Timestamp(formatter.parse(stringDate).getTime());
            } catch (Exception e) {
                valid = false;
            }

        }

        return valid;
    }

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

    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
        this.dataDictionaryService = dataDictionaryService;
    }

    public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
        this.workflowDocumentService = workflowDocumentService;
    }

    public void setKualiRuleService(KualiRuleService ruleService) {
        this.kualiRuleService = ruleService;
    }

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

    public ParameterService getParameterService() {
        return parameterService;
    }

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

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