/*
 * 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.document.validation.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRuleBase;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.util.ErrorMessage;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.CamsKeyConstants;
import org.kuali.kfs.module.cam.CamsPropertyConstants;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.cam.businessobject.AssetComponent;
import org.kuali.kfs.module.cam.businessobject.AssetFabrication;
import org.kuali.kfs.module.cam.businessobject.AssetLocation;
import org.kuali.kfs.module.cam.businessobject.AssetRepairHistory;
import org.kuali.kfs.module.cam.businessobject.AssetWarranty;
import org.kuali.kfs.module.cam.businessobject.defaultvalue.NextAssetNumberFinder;
import org.kuali.kfs.module.cam.document.service.AssetComponentService;
import org.kuali.kfs.module.cam.document.service.AssetDateService;
import org.kuali.kfs.module.cam.document.service.AssetLocationService;
import org.kuali.kfs.module.cam.document.service.AssetLocationService.LocationField;
import org.kuali.kfs.module.cam.document.service.AssetService;
import org.kuali.kfs.module.cam.document.service.EquipmentLoanOrReturnService;
import org.kuali.kfs.module.cam.document.service.PaymentSummaryService;
import org.kuali.kfs.module.cam.document.service.RetirementInfoService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.kfs.sys.util.KfsDateUtils;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.parameter.ParameterEvaluatorService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.springframework.util.AutoPopulatingList;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.kuali.kfs.module.cam.CamsKeyConstants.ERROR_INVALID_ASSET_WARRANTY_NO;
import static org.kuali.kfs.module.cam.CamsKeyConstants.PreTag.ERROR_PRE_TAG_INVALID_REPRESENTATIVE_ID;
import static org.kuali.kfs.module.cam.CamsPropertyConstants.Asset.ASSET_REPRESENTATIVE;
import static org.kuali.kfs.module.cam.CamsPropertyConstants.Asset.ASSET_WARRANTY_WARRANTY_NUMBER;

/**
 * AssetRule for Asset edit.
 */
public class AssetRule extends MaintenanceDocumentRuleBase {
    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetRule.class);
    protected static final Map<LocationField, String> LOCATION_FIELD_MAP = new HashMap<LocationField, String>();

    static {
        LOCATION_FIELD_MAP.put(LocationField.CAMPUS_CODE, CamsPropertyConstants.Asset.CAMPUS_CODE);
        LOCATION_FIELD_MAP.put(LocationField.BUILDING_CODE, CamsPropertyConstants.Asset.BUILDING_CODE);
        LOCATION_FIELD_MAP.put(LocationField.ROOM_NUMBER, CamsPropertyConstants.Asset.BUILDING_ROOM_NUMBER);
        LOCATION_FIELD_MAP.put(LocationField.SUB_ROOM_NUMBER, CamsPropertyConstants.Asset.BUILDING_SUB_ROOM_NUMBER);
        LOCATION_FIELD_MAP.put(LocationField.CONTACT_NAME, CamsPropertyConstants.Asset.AssetLocation.CONTACT_NAME);
        LOCATION_FIELD_MAP.put(LocationField.STREET_ADDRESS, CamsPropertyConstants.Asset.AssetLocation.STREET_ADDRESS);
        LOCATION_FIELD_MAP.put(LocationField.CITY_NAME, CamsPropertyConstants.Asset.AssetLocation.CITY_NAME);
        LOCATION_FIELD_MAP.put(LocationField.STATE_CODE, CamsPropertyConstants.Asset.AssetLocation.STATE_CODE);
        LOCATION_FIELD_MAP.put(LocationField.ZIP_CODE, CamsPropertyConstants.Asset.AssetLocation.ZIP_CODE);
        LOCATION_FIELD_MAP.put(LocationField.COUNTRY_CODE, CamsPropertyConstants.Asset.AssetLocation.COUNTRY_CODE);
    }

    // protected AgencyService agencyService = SpringContext.getBean(AgencyService.class);
    protected AssetService assetService = SpringContext.getBean(AssetService.class);
    protected ParameterService parameterService = SpringContext.getBean(ParameterService.class);
    protected PaymentSummaryService paymentSummaryService = SpringContext.getBean(PaymentSummaryService.class);
    protected RetirementInfoService retirementInfoService = SpringContext.getBean(RetirementInfoService.class);
    protected EquipmentLoanOrReturnService equipmentLoanOrReturnService = SpringContext.getBean(EquipmentLoanOrReturnService.class);
    protected AssetDateService assetDateService = SpringContext.getBean(AssetDateService.class);
    protected AssetComponentService assetComponentService = SpringContext.getBean(AssetComponentService.class);
    protected UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class);
    protected AssetLocationService assetLocationService = SpringContext.getBean(AssetLocationService.class);
    protected DateTimeService dateTimeService = SpringContext.getBean(DateTimeService.class);

    protected Asset newAsset;
    protected Asset oldAsset;
    protected boolean isFabrication;


    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
        initializeAttributes(document);
        boolean valid = true;
        if (SpringContext.getBean(AssetService.class).isAssetFabrication(document)) {
            this.isFabrication = true;
            valid &= validateAccount();
            valid &= validateLocation();
            valid &= validateFabricationDetails();
        } else {
            setAssetComponentNumbers(newAsset);
            paymentSummaryService.calculateAndSetPaymentSummary(oldAsset);
            paymentSummaryService.calculateAndSetPaymentSummary(newAsset);

            assetService.setSeparateHistory(oldAsset);
            assetService.setSeparateHistory(newAsset);

            retirementInfoService.setRetirementInfo(oldAsset);
            retirementInfoService.setRetirementInfo(newAsset);

            equipmentLoanOrReturnService.setEquipmentLoanInfo(oldAsset);
            equipmentLoanOrReturnService.setEquipmentLoanInfo(newAsset);

            valid &= processAssetValidation(document);
            valid &= validateWarrantyInformation(newAsset);
            valid &= validateDepreciationData(newAsset);
            valid &= checkAssetDepreciationMethodChange();

            valid &= super.processCustomSaveDocumentBusinessRules(document);
            if (valid) {
                assetDateService.checkAndUpdateLastInventoryDate(oldAsset, newAsset);
                assetDateService.checkAndUpdateDepreciationDate(oldAsset, newAsset);
                assetDateService.checkAndUpdateFiscalYearAndPeriod(oldAsset, newAsset);
            }

            valid &= checkAssetLocked(document);
        }
        valid &= validateManufacturer(newAsset);
        return valid;
    }

    /**
     * Check if asset is locked by other document.
     *
     * @param document
     * @param valid
     * @return
     */
    protected boolean checkAssetLocked(MaintenanceDocument document) {
        Asset asset = (Asset) document.getNewMaintainableObject().getBusinessObject();
        return !getCapitalAssetManagementModuleService().isAssetLocked(retrieveAssetNumberForLocking(asset), CamsConstants.DocumentTypeName.ASSET_EDIT, document.getDocumentNumber());
    }

    /**
     * Retrieve asset numbers need to be locked.
     *
     * @return
     */
    protected List<Long> retrieveAssetNumberForLocking(Asset asset) {
        List<Long> capitalAssetNumbers = new ArrayList<Long>();
        if (asset.getCapitalAssetNumber() != null) {
            capitalAssetNumbers.add(asset.getCapitalAssetNumber());
        }
        return capitalAssetNumbers;
    }

    /**
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
     * java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
     */
    @Override
    public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument documentCopy, String collectionName, PersistableBusinessObject bo) {
        boolean success = true;

        // get all incidentDates from AssetRepairHistory collection and check for duplicate dates.
        if (collectionName.equals(CamsConstants.Asset.COLLECTION_ID_ASSET_REPAIR_HISTORY)) {

            Asset asset = (Asset) documentCopy.getNewMaintainableObject().getBusinessObject();
            Set<Date> incidentDateSet = new HashSet<Date>();

            for (AssetRepairHistory assetRepairHistory : asset.getAssetRepairHistory()) {
                if (assetRepairHistory.getIncidentDate() != null) {
                    incidentDateSet.add(assetRepairHistory.getIncidentDate());
                }
            }

            AssetRepairHistory assetRepairHistoryDetails = (AssetRepairHistory) bo;

            success &= checkDuplicateIncidentDate(assetRepairHistoryDetails, incidentDateSet);

            return success & super.processCustomAddCollectionLineBusinessRules(documentCopy, collectionName, bo);
        }

        return success;
    }

    /**
     * Check for duplicate incident dates within the Repair History section
     *
     * @param assetRepairHistory
     * @param incidentDateSet
     * @return boolean
     */
    protected boolean checkDuplicateIncidentDate(AssetRepairHistory assetRepairHistory, Set<Date> incidentDateSet) {
        boolean success = true;

        if (!incidentDateSet.add(assetRepairHistory.getIncidentDate())) {
            GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetRepairHistory.INCIDENT_DATE, CamsKeyConstants.AssetRepairHistory.ERROR_DUPLICATE_INCIDENT_DATE);
            success &= false;
        }

        return success;
    }

    /**
     * Validate fabrication details
     *
     * @return boolean
     */
    protected boolean validateFabricationDetails() {
        /**
         * Please don't remove this validation, forcing required fields from DD file is not possible and will break asset edit
         * screen, so please leave this validation here.
         */
        boolean valid = true;
        if (newAsset.getFabricationEstimatedTotalAmount() != null && newAsset.getFabricationEstimatedTotalAmount().isNegative()) {
            putFieldError(CamsPropertyConstants.Asset.FABRICATION_ESTIMATED_TOTAL_AMOUNT, CamsKeyConstants.ERROR_FABRICATION_ESTIMATED_TOTAL_AMOUNT_NEGATIVE);
            valid &= false;
        }
        if (newAsset.getEstimatedFabricationCompletionDate() != null && newAsset.getEstimatedFabricationCompletionDate().before(KfsDateUtils.clearTimeFields(dateTimeService.getCurrentDate()))) {
            putFieldError(CamsPropertyConstants.Asset.ESTIMATED_FABRICATION_COMPLETION_DATE, CamsKeyConstants.ERROR_ESTIMATED_FABRICATION_COMPLETION_DATE_PAST);
            valid &= false;
        }
        if (newAsset.getFabricationEstimatedRetentionYears() != null && newAsset.getFabricationEstimatedRetentionYears().intValue() < 0) {
            putFieldError(CamsPropertyConstants.Asset.FABRICATION_ESTIMATED_RETENTION_YEARS, CamsKeyConstants.ERROR_ESTIMATED_FABRICATION_LIFE_LIMIT_NEGATIVE);
            valid &= false;
        }
        return valid;
    }

    /**
     * Validate account
     *
     * @return boolean
     */
    protected boolean validateAccount() {
        boolean valid = true;
        Account currentOwnerAccount = newAsset.getOrganizationOwnerAccount();
        Account previoudOwnerAccount = oldAsset.getOrganizationOwnerAccount();

        // Account is valid?
        if (ObjectUtils.isNull(currentOwnerAccount) && StringUtils.isNotBlank(newAsset.getOrganizationOwnerAccountNumber())) {
            putFieldError(CamsPropertyConstants.Asset.ORGANIZATION_OWNER_ACCOUNT_NUMBER, CamsKeyConstants.ORGANIZATION_OWNER_ACCOUNT_INVALID);
            valid &= false;
        }

        // check if values changed, if not return
        if (ObjectUtils.isNotNull(previoudOwnerAccount) && ObjectUtils.isNotNull(currentOwnerAccount) && previoudOwnerAccount.getChartOfAccountsCode().equals(currentOwnerAccount.getChartOfAccountsCode()) && previoudOwnerAccount.getAccountNumber().equals(currentOwnerAccount.getAccountNumber())) {
            return valid;

        } else if (ObjectUtils.isNotNull(currentOwnerAccount) && (currentOwnerAccount.isExpired() || !currentOwnerAccount.isActive())) {
            // Account is not active
            putFieldError(CamsPropertyConstants.Asset.ORGANIZATION_OWNER_ACCOUNT_NUMBER, CamsKeyConstants.ORGANIZATION_OWNER_ACCOUNT_INACTIVE);
            valid &= false;
        }
        return valid;
    }

    /**
     * Validate asset representative
     *
     * @return boolean
     */
    protected boolean validateAssetRepresentative() {
        boolean valid = true;
        Person assetRepresentative = SpringContext.getBean(PersonService.class).getPersonByPrincipalName(newAsset.getAssetRepresentative().getPrincipalName());
        if (ObjectUtils.isNull(assetRepresentative)) {
            putFieldError(ASSET_REPRESENTATIVE, ERROR_PRE_TAG_INVALID_REPRESENTATIVE_ID);
            valid = false;
        }
        return valid;
    }

    /**
     * Set asset component numbers
     *
     * @param asset
     */
    protected void setAssetComponentNumbers(Asset asset) {
        List<AssetComponent> assetComponents = asset.getAssetComponents();
        Integer maxNo = null;
        for (AssetComponent assetComponent : assetComponents) {
            assetComponent.setCapitalAssetNumber(asset.getCapitalAssetNumber());
            if (maxNo == null) {
                maxNo = assetComponentService.getMaxSequenceNumber(assetComponent);
            }
            if (assetComponent.getComponentNumber() == null) {
                assetComponent.setComponentNumber(++maxNo);
            }
        }
    }

    /**
     * Validates Asset document.
     *
     * @param document MaintenanceDocument instance
     * @return boolean false or true
     */
    protected boolean processAssetValidation(MaintenanceDocument document) {
        boolean valid = true;

        // validate Inventory Status Code.
        if (!StringUtils.equalsIgnoreCase(oldAsset.getInventoryStatusCode(), newAsset.getInventoryStatusCode())) {
            valid &= validateInventoryStatusCode(oldAsset.getInventoryStatusCode(), newAsset.getInventoryStatusCode());
        }

        // validate Organization Owner Account Number
        if (!StringUtils.equalsIgnoreCase(oldAsset.getOrganizationOwnerAccountNumber(), newAsset.getOrganizationOwnerAccountNumber())) {
            valid &= validateAccount();
        }

        // validate asset representative name
        if (!StringUtils.equalsIgnoreCase(oldAsset.getAssetRepresentative().getPrincipalName(), newAsset.getAssetRepresentative().getPrincipalName())) {
            valid &= validateAssetRepresentative();
        }


        // validate Vendor Name.
        if (!StringUtils.equalsIgnoreCase(oldAsset.getVendorName(), newAsset.getVendorName())) {
            valid &= validateVendorName();
        }

        // validate Tag Number.
        if (!StringUtils.equalsIgnoreCase(oldAsset.getCampusTagNumber(), newAsset.getCampusTagNumber())) {
            valid &= validateTagNumber();
        }

        // validate location.
        valid &= validateLocation();

        // validate In-service Date
        if (assetService.isInServiceDateChanged(oldAsset, newAsset)) {
            valid &= validateInServiceDate();
        }
        return valid;
    }


    /**
     * Check if the new In-service Date is a valid University Date
     *
     * @return
     */
    protected boolean validateInServiceDate() {
        boolean valid = true;
        // if asset already starts depreciation, the user can't blank in-service date.
        if (ObjectUtils.isNull(newAsset.getCapitalAssetInServiceDate()) && assetService.isAssetDepreciationStarted(oldAsset)) {
            putFieldError(CamsPropertyConstants.Asset.ASSET_DATE_OF_SERVICE, CamsKeyConstants.ERROR_BLANK_IN_SERVICE_DATE_DISALLOWED);
            valid = false;
        } else if (ObjectUtils.isNotNull(newAsset.getCapitalAssetInServiceDate()) && universityDateService.getFiscalYear(newAsset.getCapitalAssetInServiceDate()) == null) {
            putFieldError(CamsPropertyConstants.Asset.ASSET_DATE_OF_SERVICE, CamsKeyConstants.ERROR_INVALID_IN_SERVICE_DATE);
            valid = false;
        }
        return valid;
    }


    /**
     * Check if off campus fields has changed.
     *
     * @return
     */
    protected boolean isOffCampusLocationChanged() {
        boolean changed = false;
        AssetLocation oldLocation = oldAsset.getOffCampusLocation();
        AssetLocation newLocation = newAsset.getOffCampusLocation();

        if (!StringUtils.equalsIgnoreCase(newLocation.getAssetLocationContactName(), oldLocation.getAssetLocationContactName()) || !StringUtils.equalsIgnoreCase(newLocation.getAssetLocationStreetAddress(), oldLocation.getAssetLocationStreetAddress()) || !StringUtils.equalsIgnoreCase(newLocation.getAssetLocationCityName(), oldLocation.getAssetLocationCityName()) || !StringUtils.equalsIgnoreCase(newLocation.getAssetLocationStateCode(), oldLocation.getAssetLocationStateCode()) || !StringUtils.equalsIgnoreCase(newLocation.getAssetLocationZipCode(), oldLocation.getAssetLocationZipCode()) || !StringUtils.equalsIgnoreCase(newLocation.getAssetLocationCountryCode(), oldLocation.getAssetLocationCountryCode())) {
            changed = true;
        }
        return changed;
    }

    /**
     * Validate Inventory Status Code Change
     */
    protected boolean validateInventoryStatusCode(String oldInventoryStatusCode, String newInventoryStatusCode) {
        boolean valid = true;
        if (assetService.isCapitalAsset(oldAsset) && assetService.isAssetRetired(newAsset)) {
            // disallow retire capital asset.
            putFieldError(CamsPropertyConstants.Asset.ASSET_INVENTORY_STATUS, CamsKeyConstants.ERROR_ASSET_RETIRED_CAPITAL);
            valid = false;
        } else {
            // validate inventory status change per system parameter.
            GlobalVariables.getMessageMap().addToErrorPath(MAINTAINABLE_ERROR_PATH);
            valid &= /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(Asset.class, CamsConstants.Parameters.VALID_INVENTROY_STATUS_CODE_CHANGE, CamsConstants.Parameters.INVALID_INVENTROY_STATUS_CODE_CHANGE, oldAsset.getInventoryStatusCode(), newAsset.getInventoryStatusCode()).evaluateAndAddError(newAsset.getClass(), CamsPropertyConstants.Asset.ASSET_INVENTORY_STATUS);
            GlobalVariables.getMessageMap().removeFromErrorPath(MAINTAINABLE_ERROR_PATH);
        }
        return valid;
    }

    protected void initializeAttributes(MaintenanceDocument document) {
        if (newAsset == null) {
            newAsset = (Asset) document.getNewMaintainableObject().getBusinessObject();
        }
        if (oldAsset == null) {
            oldAsset = (Asset) document.getOldMaintainableObject().getBusinessObject();
        }
        // for fabrication
        if (oldAsset == null) {
            oldAsset = newAsset;
        }
    }

    /**
     * If the tag number has not been assigned, the departmental user will be able to update the tag number. The Tag Number shall be
     * verified that the tag number does not exist on another asset.
     *
     * @param asset
     * @return
     */
    protected boolean validateTagNumber() {
        boolean valid = true;
        boolean anyFound = false;

        if (!assetService.isTagNumberCheckExclude(newAsset)) {

            Map<String, Object> fieldValues = new HashMap<String, Object>();
            if (ObjectUtils.isNotNull(newAsset.getCampusTagNumber())) {
                fieldValues.put(CamsPropertyConstants.Asset.CAMPUS_TAG_NUMBER, newAsset.getCampusTagNumber().toUpperCase());
                Collection<Asset> results = getBoService().findMatching(Asset.class, fieldValues);

                for (Asset asset : results) {
                    if (!asset.getCapitalAssetNumber().equals(newAsset.getCapitalAssetNumber())) {
                        // KFSMI-6149 - do not invalidate if the asset from the database is retired
                        if (StringUtils.isBlank(asset.getRetirementReasonCode())) {
                            putFieldError(CamsPropertyConstants.Asset.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_DUPLICATE_TAG_NUMBER_FOUND, new String[]{newAsset.getCampusTagNumber(), asset.getCapitalAssetNumber().toString(), newAsset.getCapitalAssetNumber().toString()});
                            valid &= false;
                            LOG.info("The asset tag number [" + newAsset.getCampusTagNumber().toUpperCase() + "] is a duplicate of asset number [" + asset.getCapitalAssetNumber().toString() + "]'s tag number");
                        } else {
                            LOG.info("Although the asset tag number [" + newAsset.getCampusTagNumber().toUpperCase() + "] is a duplicate of asset number [" + asset.getCapitalAssetNumber().toString() + "]'s tag number, the old asset has already been retired");
                        }
                    }
                }
            }
        }

        return valid;
    }

    /**
     * The Vendor Name is required for capital equipment and not required for non-capital assets.
     *
     * @param asset
     * @return
     */
    protected boolean validateVendorName() {
        boolean valid = true;

        if (assetService.isCapitalAsset(newAsset) && StringUtils.isBlank(newAsset.getVendorName())) {
            putFieldError(CamsPropertyConstants.Asset.VENDOR_NAME, CamsKeyConstants.ERROR_CAPITAL_ASSET_VENDOR_NAME_REQUIRED);
            valid &= false;

        }
        return valid;
    }


    /**
     * Validate Asset Location fields
     *
     * @param asset
     * @return
     */
    protected boolean validateLocation() {
        GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject");
        boolean isCapitalAsset = assetService.isCapitalAsset(newAsset);
        boolean valid = assetLocationService.validateLocation(LOCATION_FIELD_MAP, newAsset, isCapitalAsset, newAsset.getCapitalAssetType());
        GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject");

        if (valid && (this.isFabrication || isOffCampusLocationChanged())) {
            assetLocationService.updateOffCampusLocation(newAsset);
        }
        return valid;
    }

    /**
     * Validate warranty information if user enters value
     *
     * @param asset Asset
     * @return validation result
     */
    protected boolean validateWarrantyInformation(Asset asset) {
        AssetWarranty warranty = asset.getAssetWarranty();
        if (warranty != null) {
            if (!StringUtils.isEmpty(warranty.getWarrantyContactName()) || !StringUtils.isEmpty(warranty.getWarrantyPhoneNumber()) || !StringUtils.isEmpty(warranty.getWarrantyText()) || warranty.getWarrantyBeginningDate() != null || warranty.getWarrantyEndingDate() != null) {
                if (StringUtils.isEmpty(warranty.getWarrantyNumber())) {
                    // warranty number is mandatory when any other related info is known
                    putFieldError(ASSET_WARRANTY_WARRANTY_NUMBER, ERROR_INVALID_ASSET_WARRANTY_NO);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * validates depreciation data
     *
     * @param asset
     * @return boolean
     */
    protected boolean validateDepreciationData(Asset asset) {
        //do not run the validation if the total cost amount is negative.
        //see KFSMI-9266 for details.
        if (ObjectUtils.isNotNull(asset.getTotalCostAmount()) && asset.getTotalCostAmount().compareTo(KualiDecimal.ZERO) < 0) {
            return true;
        }
        if (asset.getSalvageAmount() == null) {
            asset.setSalvageAmount(KualiDecimal.ZERO);
        }

        if (asset.getBaseAmount() == null) {
            asset.setBaseAmount(KualiDecimal.ZERO);
        }

        // If the salvage amount is greater than the base amount, then data is invalid
        if (asset.getSalvageAmount().compareTo(asset.getBaseAmount()) > 0) {
            GlobalVariables.getMessageMap().putWarning(MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.Asset.SALVAGE_AMOUNT, CamsKeyConstants.Asset.ERROR_INVALID_SALVAGE_AMOUNT);
        }

        // If book value is negative then depreciation data is invalid.
        if (asset.getBookValue().compareTo(KualiDecimal.ZERO) < 0) {
            putFieldError(CamsPropertyConstants.Asset.BOOK_VALUE, CamsKeyConstants.Asset.ERROR_INVALID_BOOKVALUE_AMOUNT);
            return false;
        }

        return true;
    }

    @Override
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
        boolean valid = super.processCustomRouteDocumentBusinessRules(document);
        initializeAttributes(document);
        if (SpringContext.getBean(AssetService.class).isAssetFabrication(document) && newAsset.getCapitalAssetNumber() == null) {
            newAsset.setCapitalAssetNumber(NextAssetNumberFinder.getLongValue());
            oldAsset.setCapitalAssetNumber(newAsset.getCapitalAssetNumber());
            newAsset.setLastInventoryDate(new Timestamp(SpringContext.getBean(DateTimeService.class).getCurrentSqlDate().getTime()));
            oldAsset.setLastInventoryDate(new Timestamp(SpringContext.getBean(DateTimeService.class).getCurrentSqlDate().getTime()));
        }

        // check for change
        valid &= checkAcquisitionTypeCodeChange();
        valid &= checkConditionCodeChange();
        valid &= checkAssetStatusCodeChange();
        valid &= checkAssetTypeCodeChange();
        valid &= checkFinancialObjectSubtypeCodeChange();
        valid &= validateAccount();

        WorkflowDocument workflowDoc = document.getDocumentHeader().getWorkflowDocument();
        // adding asset locks for asset edit only
        if (newAsset instanceof Asset && !(newAsset instanceof AssetFabrication) && !GlobalVariables.getMessageMap().hasErrors() && (workflowDoc.isInitiated() || workflowDoc.isSaved())) {
            valid &= setAssetLock(document);
        }
        return valid;
    }

    /**
     * Locking asset number
     *
     * @param document
     * @return
     */
    protected boolean setAssetLock(MaintenanceDocument document) {
        Asset asset = (Asset) document.getNewMaintainableObject().getBusinessObject();
        return this.getCapitalAssetManagementModuleService().storeAssetLocks(retrieveAssetNumberForLocking(asset), document.getDocumentNumber(), CamsConstants.DocumentTypeName.ASSET_EDIT, null);
    }

    protected CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
        return SpringContext.getBean(CapitalAssetManagementModuleService.class);
    }

    /**
     * Convenience method to append the path prefix
     */
    public AutoPopulatingList<ErrorMessage> putError(String propertyName, String errorKey, String... errorParameters) {
        return GlobalVariables.getMessageMap().putError(CamsConstants.DOCUMENT_PATH + "." + propertyName, errorKey, errorParameters);
    }

    /**
     * Check if the Acquisition Type Code is valid or is inactive.
     *
     * @param oldAcquisitionTypeCode
     * @param newAcquisitionTypeCode
     * @return boolean
     */
    protected boolean checkAcquisitionTypeCodeChange() {
        if (ObjectUtils.isNull(newAsset.getAcquisitionType())) {
            putFieldError(CamsPropertyConstants.Asset.ACQUISITION_TYPE_CODE, CamsKeyConstants.Asset.ERROR_ACQUISITION_TYPE_CODE_INVALID);
            return false;
        } else if (!StringUtils.equalsIgnoreCase(newAsset.getAcquisitionTypeCode(), oldAsset.getAcquisitionTypeCode())) {
            newAsset.refreshReferenceObject(CamsPropertyConstants.Asset.REF_ACQUISITION_TYPE);
            if (ObjectUtils.isNotNull(newAsset.getAcquisitionType())) {
                if (!newAsset.getAcquisitionType().isActive()) {
                    putFieldError(CamsPropertyConstants.Asset.ACQUISITION_TYPE_CODE, CamsKeyConstants.Asset.ERROR_ACQUISITION_TYPE_CODE_INACTIVE);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Check if the Asset Condition is valid or is inactive.
     *
     * @param oldConditionCode
     * @param newConditionCode
     * @return boolean
     */
    protected boolean checkConditionCodeChange() {
        if (ObjectUtils.isNull(newAsset.getCondition())) {
            putFieldError(CamsPropertyConstants.Asset.CONDITION_CODE, CamsKeyConstants.Asset.ERROR_ASSET_CONDITION_INVALID);
            return false;
        } else if (!StringUtils.equalsIgnoreCase(newAsset.getConditionCode(), oldAsset.getConditionCode())) {
            newAsset.refreshReferenceObject(CamsPropertyConstants.Asset.REF_ASSET_CONDITION);
            if (ObjectUtils.isNotNull(newAsset.getCondition())) {
                if (!newAsset.getCondition().isActive()) {
                    putFieldError(CamsPropertyConstants.Asset.CONDITION_CODE, CamsKeyConstants.Asset.ERROR_ASSET_CONDITION_INACTIVE);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Check if the Asset Depreciation Method is valid or is inactive.
     *
     * @param oldAssetDepreciationMethod
     * @param newAssetDepreciationMethod
     * @return boolean
     */
    protected boolean checkAssetDepreciationMethodChange() {
        if (ObjectUtils.isNull(newAsset.getAssetPrimaryDepreciationMethod())) {
            putFieldError(CamsPropertyConstants.Asset.PRIMARY_DEPRECIATION_METHOD, CamsKeyConstants.Asset.ERROR_DEPRECATION_METHOD_CODE_INVALID);
            return false;
        } else if (!StringUtils.equalsIgnoreCase(newAsset.getPrimaryDepreciationMethodCode(), oldAsset.getPrimaryDepreciationMethodCode())) {
            newAsset.refreshReferenceObject(CamsPropertyConstants.Asset.REF_ASSET_DEPRECATION_METHOD);
            if (ObjectUtils.isNotNull(newAsset.getAssetPrimaryDepreciationMethod())) {
                if (!newAsset.getAssetPrimaryDepreciationMethod().isActive()) {
                    putFieldError(CamsPropertyConstants.Asset.PRIMARY_DEPRECIATION_METHOD, CamsKeyConstants.Asset.ERROR_DEPRECATION_METHOD_CODE_INACTIVE);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Check if the Asset Status Code is valid or is inactive.
     *
     * @param oldAssetStatus
     * @param newAssetStatus
     * @return boolean
     */
    protected boolean checkAssetStatusCodeChange() {
        if (ObjectUtils.isNull(newAsset.getInventoryStatus())) {
            putFieldError(CamsPropertyConstants.Asset.ASSET_INVENTORY_STATUS, CamsKeyConstants.Asset.ERROR_ASSET_STATUS_INVALID);
            return false;
        } else if (!StringUtils.equalsIgnoreCase(newAsset.getInventoryStatusCode(), oldAsset.getInventoryStatusCode())) {
            newAsset.refreshReferenceObject(CamsPropertyConstants.Asset.REF_ASSET_STATUS);
            if (ObjectUtils.isNotNull(newAsset.getInventoryStatus())) {
                if (!newAsset.getInventoryStatus().isActive()) {
                    putFieldError(CamsPropertyConstants.Asset.ASSET_INVENTORY_STATUS, CamsKeyConstants.Asset.ERROR_ASSET_STATUS_INACTIVE);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Check if the Asset Type Code is valid or is inactive.
     *
     * @param oldAssetType
     * @param newAssetType
     * @return boolean
     */
    protected boolean checkAssetTypeCodeChange() {
        if (ObjectUtils.isNull(newAsset.getCapitalAssetType())) {
            putFieldError(CamsPropertyConstants.Asset.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.Asset.ERROR_TYPE_CODE_INVALID);
            return false;
        } else if (!StringUtils.equalsIgnoreCase(newAsset.getCapitalAssetTypeCode(), oldAsset.getCapitalAssetTypeCode())) {
            newAsset.refreshReferenceObject(CamsPropertyConstants.Asset.REF_ASSET_TYPE);
            if (ObjectUtils.isNotNull(newAsset.getCapitalAssetType())) {
                if (!newAsset.getCapitalAssetType().isActive()) {
                    putFieldError(CamsPropertyConstants.AssetType.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.Asset.ERROR_TYPE_CODE_INACTIVE);
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Check if the Financial Object Sub-Type Code is valid or is inactive.
     *
     * @param oldObjectSubType
     * @param newObjectSubType
     * @return boolean
     */
    protected boolean checkFinancialObjectSubtypeCodeChange() {
        if (ObjectUtils.isNotNull(newAsset.getFinancialObjectSubType()) || StringUtils.isNotBlank(newAsset.getFinancialObjectSubTypeCode())) {
            newAsset.refreshReferenceObject(CamsPropertyConstants.Asset.REF_OBJECT_SUB_TYPE);
            if (ObjectUtils.isNull(newAsset.getFinancialObjectSubType())) {
                putFieldError(CamsPropertyConstants.Asset.FINANCIAL_OBJECT_SUB_TYP_CODE, CamsKeyConstants.Asset.ERROR_FINANCIAL_OBJECT_SUBTYPE_CODE_INVALID);
                return false;
            } else if (!newAsset.getFinancialObjectSubType().isActive()) {
                putFieldError(CamsPropertyConstants.Asset.FINANCIAL_OBJECT_SUB_TYP_CODE, CamsKeyConstants.Asset.ERROR_FINANCIAL_OBJECT_SUBTYPE_CODE_INACTIVE);
                return false;
            }
        }
        return true;
    }

    protected boolean validateManufacturer(Asset asset) {
        boolean valid = true;
        if (assetService.isCapitalAsset(asset)) {
            if (parameterService.getParameterValueAsBoolean(CamsConstants.CAM_MODULE_CODE, "Asset", CamsConstants.Parameters.MANUFACTURER_REQUIRED_FOR_NON_MOVEABLE_ASSET_IND) &&
                StringUtils.isEmpty(asset.getManufacturerName())) {
                putFieldError(CamsPropertyConstants.Asset.MANUFACTURER_NAME, CamsKeyConstants.AssetGlobal.ERROR_MFR_NAME_REQUIRED);
                valid = false;
            }
        }
        return valid;
    }
}
