/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 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 java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.Account; import org.kuali.kfs.coa.businessobject.ObjectCode; import org.kuali.kfs.coa.service.ObjectCodeService; import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService; import org.kuali.kfs.module.cam.CamsConstants; import org.kuali.kfs.module.cam.CamsConstants.DocumentTypeName; 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.AssetGlobal; import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail; import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail; import org.kuali.kfs.module.cam.businessobject.AssetType; import org.kuali.kfs.module.cam.document.gl.AssetGlobalGeneralLedgerPendingEntrySource; import org.kuali.kfs.module.cam.document.service.AssetAcquisitionTypeService; import org.kuali.kfs.module.cam.document.service.AssetGlobalService; 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.AssetPaymentService; import org.kuali.kfs.module.cam.document.service.AssetService; import org.kuali.kfs.module.cam.document.service.PaymentSummaryService; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.Building; import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader; import org.kuali.kfs.sys.businessobject.Room; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.authorization.FinancialSystemMaintenanceDocumentAuthorizerBase; import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.parameter.ParameterEvaluator; import org.kuali.rice.core.api.parameter.ParameterEvaluatorService; import org.kuali.rice.core.api.util.RiceKeyConstants; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.core.web.format.CurrencyFormatter; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.kns.maintenance.Maintainable; import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; import org.kuali.rice.kns.service.DataDictionaryService; import org.kuali.rice.kns.service.KNSServiceLocator; import org.kuali.rice.krad.bo.DocumentHeader; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.datadictionary.ReferenceDefinition; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.exception.ValidationException; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DocumentDictionaryService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADPropertyConstants; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.location.api.campus.Campus; import org.kuali.rice.location.api.campus.CampusService; /** * Rule implementation for Asset Global document. */ public class AssetGlobalRule extends MaintenanceDocumentRuleBase { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetGlobalRule.class); protected static final Map<LocationField, String> LOCATION_FIELD_MAP = new HashMap<LocationField, String>(); static { LOCATION_FIELD_MAP.put(LocationField.CAMPUS_CODE, CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE); LOCATION_FIELD_MAP.put(LocationField.BUILDING_CODE, CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE); LOCATION_FIELD_MAP.put(LocationField.ROOM_NUMBER, CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER); LOCATION_FIELD_MAP.put(LocationField.SUB_ROOM_NUMBER, CamsPropertyConstants.AssetGlobalDetail.BUILDING_SUB_ROOM_NUMBER); LOCATION_FIELD_MAP.put(LocationField.CONTACT_NAME, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_NAME); LOCATION_FIELD_MAP.put(LocationField.STREET_ADDRESS, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_ADDRESS); LOCATION_FIELD_MAP.put(LocationField.CITY_NAME, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_CITY_NAME); LOCATION_FIELD_MAP.put(LocationField.STATE_CODE, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_STATE_CODE); LOCATION_FIELD_MAP.put(LocationField.ZIP_CODE, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_ZIP_CODE); LOCATION_FIELD_MAP.put(LocationField.COUNTRY_CODE, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_COUNTRY_CODE); } /** * This method checks reference fields when adding new payment into collection. * * @param assetGlobal * @param assetPaymentDetail * @return */ protected boolean checkReferenceExists(AssetGlobal assetGlobal, AssetPaymentDetail assetPaymentDetail) { boolean valid = true; // validate required field for object code. Skip the error message because the maintenance DD 'defaultExistenceChecks' can // generate the same error message. We won't duplicate it. if (StringUtils.isBlank(assetPaymentDetail.getFinancialObjectCode())) { valid = false; } // Validate Financial Posted date if (assetPaymentDetail.getExpenditureFinancialDocumentPostedDate() != null) { valid &= validatePostedDate(assetPaymentDetail); } if (valid) { // Check for ObjectCode if (StringUtils.isNotBlank(assetPaymentDetail.getFinancialObjectCode())) { assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE); if (ObjectUtils.isNull(assetPaymentDetail.getObjectCode())) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label); valid &= false; } } } if (StringUtils.isBlank(assetGlobal.getAcquisitionTypeCode()) || ObjectUtils.isNull(assetGlobal.getAcquisitionType())) { putFieldError(CamsPropertyConstants.AssetGlobal.ACQUISITION_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_ACQUISITION_TYPE_CODE_REQUIRED); valid &= false; } if (StringUtils.isBlank(assetGlobal.getInventoryStatusCode())) { putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_INVENTORY_STATUS_REQUIRED_FOR_PAYMENT); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SEQUENCE_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_ASSET_PAYMENT_DEPENDENCY); valid &= false; } if (StringUtils.isBlank(assetGlobal.getCapitalAssetTypeCode())) { putFieldError(CamsPropertyConstants.AssetGlobal.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_ASSET_TYPE_REQUIRED); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SEQUENCE_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_ASSET_PAYMENT_DEPENDENCY); valid &= false; } // check for existence and active when not separating. This can't be done in the DD because we have a condition on it. // Note: Even though on separate the payment lines can't be edited we still can't force the rules because data may have // gone inactive since then. if (!getAssetGlobalService().isAssetSeparate(assetGlobal)) { assetPaymentDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT); if (StringUtils.isBlank(assetPaymentDetail.getAccountNumber()) || isAccountInvalid(assetPaymentDetail.getAccount())) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_PAYMENT_ACCT_NOT_VALID, new String[] { assetPaymentDetail.getChartOfAccountsCode(), assetPaymentDetail.getAccountNumber() }); valid &= false; } else if (!StringUtils.isBlank(assetPaymentDetail.getAccountNumber()) && !assetPaymentDetail.getAccount().isActive()) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT_NUMBER).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT_NUMBER, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.SUB_ACCOUNT); if (!StringUtils.isBlank(assetPaymentDetail.getSubAccountNumber()) && ObjectUtils.isNull(assetPaymentDetail.getSubAccount())) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER, RiceKeyConstants.ERROR_EXISTENCE, label); valid &= false; } else if (!StringUtils.isBlank(assetPaymentDetail.getSubAccountNumber()) && !assetPaymentDetail.getSubAccount().isActive()) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE); if (!StringUtils.isBlank(assetPaymentDetail.getFinancialObjectCode()) && ObjectUtils.isNull(assetPaymentDetail.getObjectCode())) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label); valid &= false; } else if (!StringUtils.isBlank(assetPaymentDetail.getFinancialObjectCode()) && !assetPaymentDetail.getObjectCode().isActive()) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.SUB_OBJECT_CODE); if (!StringUtils.isBlank(assetPaymentDetail.getFinancialSubObjectCode()) && ObjectUtils.isNull(assetPaymentDetail.getSubObjectCode())) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label); valid &= false; } else if (!StringUtils.isBlank(assetPaymentDetail.getFinancialSubObjectCode()) && !assetPaymentDetail.getSubObjectCode().isActive()) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.PROJECT); if (!StringUtils.isBlank(assetPaymentDetail.getProjectCode()) && ObjectUtils.isNull(assetPaymentDetail.getProject())) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label); valid &= false; } else if (!StringUtils.isBlank(assetPaymentDetail.getProjectCode()) && !assetPaymentDetail.getProject().isActive()) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } assetPaymentDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION); if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialSystemOriginationCode()) && ObjectUtils.isNull(assetPaymentDetail.getExpenditureFinancialSystemOrigination())) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE, RiceKeyConstants.ERROR_EXISTENCE, label); valid &= false; } else if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialSystemOriginationCode()) && !assetPaymentDetail.getExpenditureFinancialSystemOrigination().isActive()) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentTypeCode()) && ObjectUtils.isNull(assetPaymentDetail.getExpenditureFinancialSystemDocumentTypeCode())) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, RiceKeyConstants.ERROR_EXISTENCE, label); valid &= false; return valid; } if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentTypeCode()) && !assetPaymentDetail.getExpenditureFinancialSystemDocumentTypeCode().isActive()) { String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } } return valid; } /** * This method checks reference fields when adding one shared location information into collection. * * @param assetGlobalDetail * @return */ protected boolean checkReferenceExists(AssetGlobalDetail assetGlobalDetail) { boolean valid = true; if (StringUtils.isNotBlank(assetGlobalDetail.getCampusCode())) { Campus campus = SpringContext.getBean(CampusService.class).getCampus(assetGlobalDetail.getCampusCode()); if (ObjectUtils.isNull(campus)) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, CamsKeyConstants.AssetLocation.ERROR_INVALID_CAMPUS_CODE, assetGlobalDetail.getCampusCode()); valid &= false; } else if (!campus.isActive()) { String label = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(Campus.class.getName()).getAttributeDefinition(KFSPropertyConstants.CAMPUS_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } } if (StringUtils.isNotBlank(assetGlobalDetail.getBuildingCode())) { Map<String, String> objectKeys = new HashMap<String, String>(); objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, assetGlobalDetail.getCampusCode()); objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, assetGlobalDetail.getBuildingCode()); Building building = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Building.class, objectKeys); if (ObjectUtils.isNull(building)) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, CamsKeyConstants.AssetLocation.ERROR_INVALID_BUILDING_CODE, assetGlobalDetail.getBuildingCode(), assetGlobalDetail.getCampusCode()); valid &= false; } else if (!building.isActive()) { String label = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(Building.class.getName()).getAttributeDefinition(KFSPropertyConstants.BUILDING_CODE).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } } if (StringUtils.isNotBlank(assetGlobalDetail.getBuildingRoomNumber())) { Map<String, String> objectKeys = new HashMap<String, String>(); objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, assetGlobalDetail.getCampusCode()); objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, assetGlobalDetail.getBuildingCode()); objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER, assetGlobalDetail.getBuildingRoomNumber()); Room room = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Room.class, objectKeys); if (ObjectUtils.isNull(room)) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_INVALID_ROOM_NUMBER, assetGlobalDetail.getBuildingCode(), assetGlobalDetail.getBuildingRoomNumber(), assetGlobalDetail.getCampusCode()); valid &= false; } else if (!room.isActive()) { String label = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(Room.class.getName()).getAttributeDefinition(KFSPropertyConstants.BUILDING_ROOM_NUMBER).getLabel(); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER, RiceKeyConstants.ERROR_INACTIVE, label); valid &= false; } } return valid; } protected boolean isCapitalStatus(AssetGlobal assetGlobal) { return getParameterService().getParameterValuesAsString(Asset.class, CamsConstants.Parameters.CAPITAL_ASSET_STATUS_CODES).contains(assetGlobal.getInventoryStatusCode()); } protected boolean isStatusCodeRetired(String statusCode) { return getParameterService().getParameterValuesAsString(Asset.class, CamsConstants.Parameters.RETIRED_STATUS_CODES).contains(statusCode); } @Override public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) { boolean success = super.processCustomAddCollectionLineBusinessRules(document, collectionName, line); if (GlobalVariables.getMessageMap().hasErrors()) { return false; } AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject(); List<AssetGlobalDetail> assetSharedDetails = assetGlobal.getAssetSharedDetails(); if (CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS.equals(collectionName)) { // handle location information AssetGlobalDetail assetGlobalDetail = (AssetGlobalDetail) line; success &= checkReferenceExists(assetGlobalDetail); success &= validateLocation(assetGlobal, assetGlobalDetail); // qty. of assets (unique) to be created success &= validateLocationQuantity(line); } else if (StringUtils.isNotBlank(collectionName) && collectionName.contains(CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS)) { // handle unique information AssetGlobalDetail assetUniqueDetail = (AssetGlobalDetail) line; String campusTagNumber = assetUniqueDetail.getCampusTagNumber(); if (StringUtils.isNotBlank(campusTagNumber)) { success &= validateTagDuplication(assetSharedDetails, campusTagNumber); } } else if (CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS.equals(collectionName)) { AssetPaymentDetail assetPaymentDetail = (AssetPaymentDetail) line; if (success &= checkReferenceExists(assetGlobal, assetPaymentDetail)) { success &= validatePaymentLine(document, assetGlobal, assetPaymentDetail); success &= checkNegativeOrZeroPayment(document, assetPaymentDetail); } } // only for "Asset Separate" document if (getAssetGlobalService().isAssetSeparate(assetGlobal)) { // total cost must be > 0 success &= validateTotalCostAmount(assetGlobal); } return success; } /** * Validated the location quantity * * @param line * @return boolean */ protected boolean validateLocationQuantity(PersistableBusinessObject line) { boolean success = true; AssetGlobalDetail assetGlobalDetail = (AssetGlobalDetail) line; if (assetGlobalDetail.getLocationQuantity() <= 0) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.LOCATION_QUANTITY, CamsKeyConstants.AssetSeparate.ERROR_ZERO_OR_NEGATIVE_LOCATION_QUANTITY, assetGlobalDetail.getLocationQuantity().toString()); success &= false; } return success; } protected boolean validateTagDuplication(List<AssetGlobalDetail> assetSharedDetails, String campusTagNumber) { boolean success = true; if (!campusTagNumber.equalsIgnoreCase(CamsConstants.Asset.NON_TAGGABLE_ASSET)) { for (AssetGlobalDetail assetSharedDetail : assetSharedDetails) { List<AssetGlobalDetail> assetGlobalUniqueDetails = assetSharedDetail.getAssetGlobalUniqueDetails(); for (AssetGlobalDetail assetSharedUniqueDetail : assetGlobalUniqueDetails) { if (campusTagNumber.equalsIgnoreCase(assetSharedUniqueDetail.getCampusTagNumber())) { success &= false; GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber); } } } if (success) { List<Asset> tagMatches = getAssetService().findActiveAssetsMatchingTagNumber(campusTagNumber); if (!tagMatches.isEmpty()) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber); success &= false; } } } return success; } protected boolean validateTagDuplication(List<AssetGlobalDetail> assetSharedDetails) { HashSet<String> assetTags = new HashSet<String>(); boolean success = true; int parentIndex = -1; int childIndex = -1; for (AssetGlobalDetail assetSharedDetail : assetSharedDetails) { parentIndex++; List<AssetGlobalDetail> assetGlobalUniqueDetails = assetSharedDetail.getAssetGlobalUniqueDetails(); for (AssetGlobalDetail assetSharedUniqueDetail : assetGlobalUniqueDetails) { childIndex++; String campusTagNumber = assetSharedUniqueDetail.getCampusTagNumber(); if (StringUtils.isNotBlank(campusTagNumber) && !assetTags.add(campusTagNumber) && !campusTagNumber.equalsIgnoreCase(CamsConstants.Asset.NON_TAGGABLE_ASSET)) { success &= false; String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + parentIndex + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS + "[" + childIndex + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); } } childIndex = -1; for (AssetGlobalDetail assetSharedUniqueDetail : assetGlobalUniqueDetails) { childIndex++; String campusTagNumber = assetSharedUniqueDetail.getCampusTagNumber(); if (StringUtils.isNotBlank(campusTagNumber) && !campusTagNumber.equalsIgnoreCase(CamsConstants.Asset.NON_TAGGABLE_ASSET)) { List<Asset> tagMatches = getAssetService().findActiveAssetsMatchingTagNumber(campusTagNumber); if (!tagMatches.isEmpty()) { success &= false; String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + parentIndex + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS + "[" + childIndex + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); } } } } return success; } protected boolean validatePaymentLine(MaintenanceDocument maintenanceDocument, AssetGlobal assetGlobal, AssetPaymentDetail assetPaymentDetail) { boolean success = true; // If Acquisition type is "New" or "non-capital", check required fields including Document number, Document type code, // Posted date. if (getAssetGlobalService().existsInGroup(getAssetGlobalService().getNewAcquisitionTypeCode(), assetGlobal.getAcquisitionTypeCode()) || !getAssetGlobalService().existsInGroup(getAssetGlobalService().getCapitalObjectAcquisitionCodeGroup(), assetGlobal.getAcquisitionTypeCode())) { success &= checkRequiredFieldsForNewOrNonCapital(assetPaymentDetail); } else { // Validate Financial Document Type Code success &= validateDocumentTypeForNonNew(assetGlobal.getAcquisitionTypeCode(), assetPaymentDetail); } assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE); success &= validateObjectCode(assetPaymentDetail.getObjectCode(), assetGlobal); return success; } /** * "Add Negative Payment" permission check. * * @param maintenanceDocument * @param assetPaymentDetail * @return */ protected boolean checkNegativeOrZeroPayment(MaintenanceDocument maintenanceDocument, AssetPaymentDetail assetPaymentDetail) { boolean success = true; FinancialSystemMaintenanceDocumentAuthorizerBase documentAuthorizer = (FinancialSystemMaintenanceDocumentAuthorizerBase) SpringContext.getBean(DocumentDictionaryService.class).getDocumentAuthorizer(maintenanceDocument); boolean isAuthorized = documentAuthorizer.isAuthorized(maintenanceDocument, CamsConstants.CAM_MODULE_CODE, CamsConstants.PermissionNames.ADD_NEGATIVE_PAYMENTS, GlobalVariables.getUserSession().getPerson().getPrincipalId()); if (!isAuthorized && assetPaymentDetail.getAmount() != null && assetPaymentDetail.getAmount().isNegative()) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.AMOUNT, CamsKeyConstants.AssetGlobal.ERROR_INVALID_PAYMENT_AMOUNT); success = false; } // amount can not be zero for any user if (assetPaymentDetail.getAmount().isZero()) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.AMOUNT, CamsKeyConstants.AssetGlobal.ERROR_INVALID_PAYMENT_AMOUNT); success = false; } return success; } /** * This method check the required fields for acquisition type New . * * @param assetPaymentDetail * @return */ protected boolean checkRequiredFieldsForNewOrNonCapital(AssetPaymentDetail assetPaymentDetail) { boolean valid = true; if (StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentNumber())) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_EXPENDITURE_FINANCIAL_DOCUMENT_NUMBER_REQUIRED); valid &= false; } if (assetPaymentDetail.getExpenditureFinancialDocumentPostedDate() == null) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_POSTING_DATE, CamsKeyConstants.AssetGlobal.ERROR_DOCUMENT_POSTING_DATE_REQUIRED); valid &= false; } if (StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentTypeCode())) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_EXPENDITURE_FINANCIAL_DOCUMENT_TYPE_CODE_REQUIRED); valid &= false; } return valid; } /** * Validates the posted date payment posted date can't be a future date * * @param assetPaymentDetail * @return boolean */ protected boolean validatePostedDate(AssetPaymentDetail assetPaymentDetail) { boolean valid = true; Date currentDate = SpringContext.getBean(DateTimeService.class).getCurrentDate(); if (!getAssetPaymentService().extractPostedDatePeriod(assetPaymentDetail)) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_POSTING_FISCAL_YEAR, CamsKeyConstants.AssetGlobal.ERROR_UNIVERSITY_NOT_DEFINED_FOR_DATE, new String[] { assetPaymentDetail.getExpenditureFinancialDocumentPostedDate().toString() }); valid &= false; } else if (assetPaymentDetail.getExpenditureFinancialDocumentPostedDate().compareTo(currentDate) > 0) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_POSTING_DATE, CamsKeyConstants.Payment.ERROR_INVALID_DOC_POST_DATE); valid = false; } return valid; } /** * When acquisition type code is Capital (Gifts, Transfer-in, State excess, and Found), payment document type code will be * assigned to AA for Add Asset Document. * * @param documentTypeCode * @return */ protected boolean validateDocumentTypeForNonNew(String acquisitionTypeCode, AssetPaymentDetail assetPaymentDetail) { String documentTypeCode = assetPaymentDetail.getExpenditureFinancialDocumentTypeCode(); boolean valid = true; if (StringUtils.isNotBlank(acquisitionTypeCode) && getAssetGlobalService().existsInGroup(getAssetGlobalService().getNonNewAcquisitionCodeGroup(), acquisitionTypeCode)) { if (StringUtils.isNotBlank(documentTypeCode) && !CamsConstants.DocumentTypeName.ASSET_ADD_GLOBAL.equalsIgnoreCase(documentTypeCode)) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_DOCUMENT_TYPE_CODE_NOT_ALLOWED, documentTypeCode); valid = false; } else { // system set document type code as 'AA' assetPaymentDetail.setExpenditureFinancialDocumentTypeCode(CamsConstants.DocumentTypeName.ASSET_ADD_GLOBAL); } } return valid; } /** * Check object code is set to capital only when the status is capital. * * @param assetGlobal * @param assetPaymentDetail * @return valid */ protected boolean validateObjectCode(ObjectCode objectCode, AssetGlobal assetGlobal) { boolean valid = true; // The acquisition type code of (F, G, N, S, T) requires a capital object code. ParameterService parameterService = SpringContext.getBean(ParameterService.class); ParameterEvaluator parameterEvaluator = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(AssetGlobal.class, CamsConstants.Parameters.VALID_OBJECT_SUB_TYPES_BY_ACQUISITION_TYPE, CamsConstants.Parameters.INVALID_OBJECT_SUB_TYPES_BY_ACQUISITION_TYPE, assetGlobal.getAcquisitionTypeCode(), objectCode.getFinancialObjectSubTypeCode()); valid &= parameterEvaluator.evaluateAndAddError(ObjectCode.class, CamsPropertyConstants.Asset.FINANCIAL_OBJECT_SUB_TYP_CODE, CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE); return valid; } protected boolean isAccountInvalid(Account account) { return ObjectUtils.isNull(account) || account.isExpired(); } @Override protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject(); List<AssetGlobalDetail> assetSharedDetails = assetGlobal.getAssetSharedDetails(); boolean success = super.processCustomRouteDocumentBusinessRules(document); if (GlobalVariables.getMessageMap().hasErrors()) { return false; } // need at least one asset location if (assetSharedDetails.isEmpty() || assetSharedDetails.get(0).getAssetGlobalUniqueDetails().isEmpty()) { putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS, CamsKeyConstants.AssetGlobal.MIN_ONE_ASSET_REQUIRED); success &= false; } // Capital Asset must have payment zone. if (isCapitalStatus(assetGlobal) && assetGlobal.getAssetPaymentDetails().isEmpty()) { putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS, CamsKeyConstants.AssetGlobal.MIN_ONE_PAYMENT_REQUIRED); success &= false; } // check total amount for Asset Create if (!getAssetGlobalService().isAssetSeparate(assetGlobal)) { success &= validateAssetTotalAmount(document); } else { // only for "Asset Separate" document if (getAssetPaymentService().getAssetPaymentDetailQuantity(assetGlobal) >= 10) { /* * @TODO KULCAP-828 putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS, * CamsKeyConstants.AssetSeparate.ERROR_ASSET_SPLIT_MAX_LIMIT); success &= false; */ } // new source payments must be greater than the capital asset cost amount KualiDecimal totalSeparateSourceAmount = getAssetGlobalService().getUniqueAssetsTotalAmount(assetGlobal); if (totalSeparateSourceAmount.isGreaterThan(assetGlobal.getTotalCostAmount())) { putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS, CamsKeyConstants.AssetSeparate.ERROR_INVALID_TOTAL_SEPARATE_SOURCE_AMOUNT, new String[] { assetGlobal.getSeparateSourceCapitalAssetNumber().toString() }); success &= false; } // only active capital assets can be separated assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET); if (ObjectUtils.isNotNull(assetGlobal.getSeparateSourceCapitalAsset())) { if (!getAssetService().isCapitalAsset(assetGlobal.getSeparateSourceCapitalAsset())) { if (StringUtils.isNotBlank(assetGlobal.getAcquisitionTypeCode())) { putFieldError(CamsPropertyConstants.AssetGlobal.ACQUISITION_TYPE_CODE, CamsKeyConstants.AssetSeparate.ERROR_NON_CAPITAL_ASSET_SEPARATE_REQUIRED); success &= false; } } } // validate required fields within "Asset Unique Information" tab int sharedIndex = 0; for (AssetGlobalDetail addLocationDetail : assetSharedDetails) { int uniqueIndex = 0; for (AssetGlobalDetail assetGlobalUniqueDetail : addLocationDetail.getAssetGlobalUniqueDetails()) { String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + sharedIndex + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_GLOBAL_UNIQUE_DETAILS + "[" + uniqueIndex + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); success &= validateCapitalAssetTypeCode(assetGlobalUniqueDetail); success &= validateAssetType(assetGlobalUniqueDetail, sharedIndex, uniqueIndex); success &= validateAssetDescription(assetGlobalUniqueDetail); success &= validateManufacturer(assetGlobalUniqueDetail); success &= validateSeparateSourceAmount(assetGlobalUniqueDetail, document); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); uniqueIndex++; } sharedIndex++; } // total cost must be > 0 success &= validateTotalCostAmount(assetGlobal); success &= validateAssetTotalCostMatchesPaymentTotalCost(assetGlobal); if (getAssetGlobalService().isAssetSeparateByPayment(assetGlobal)) { AssetGlobalRule.validateAssetAlreadySeparated(assetGlobal.getSeparateSourceCapitalAssetNumber()); } } // end ASEP success &= validateLocationCollection(assetGlobal, assetSharedDetails); success &= validateTagDuplication(assetSharedDetails); return success; } /** * Validate all separate source amount is above the capital asset threshold amount. * * @param document * @return */ protected boolean validateSeparateSourceAmountAboveThreshold(MaintenanceDocument document, AssetGlobalDetail assetGlobalUniqueDetail) { boolean success = true; String capitalizationThresholdAmount = this.getCapitalizationThresholdAmount(); KualiDecimal separateAmount = assetGlobalUniqueDetail.getSeparateSourceAmount(); // check for the minimal amount only among all separate source amount. if it's above the threshold, safe... if (separateAmount != null && !getAssetService().isDocumentEnrouting(document) && !validateCapitalAssetAmountAboveThreshhold(document, separateAmount, capitalizationThresholdAmount)) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.SEPARATE_SOURCE_AMOUNT, CamsKeyConstants.AssetSeparate.ERROR_SEPARATE_ASSET_BELOW_THRESHOLD, new String[] { assetGlobalUniqueDetail.getCapitalAssetNumber().toString(), capitalizationThresholdAmount }); success = false; } return success; } /** * check if amount is above threshold for capital assets for normal user. minTotalPaymentByAsset and maxTotalPaymentByAsset are * used to check against threshold. Due to the decimal rounding, the asset total amount could have 1 cent difference with each * other. We need to pick up the right value for different threshold check. * * @param document * @return */ protected boolean validateAssetTotalAmount(MaintenanceDocument document) { boolean success = true; AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject(); KualiDecimal minTotalPaymentByAsset = getAssetGlobalService().totalPaymentByAsset(assetGlobal, false); KualiDecimal maxTotalPaymentByAsset = getAssetGlobalService().totalPaymentByAsset(assetGlobal, true); if (minTotalPaymentByAsset.isGreaterThan(maxTotalPaymentByAsset)) { // swap min and max KualiDecimal totalPayment = minTotalPaymentByAsset; minTotalPaymentByAsset = maxTotalPaymentByAsset; maxTotalPaymentByAsset = totalPayment; } // Disallow FO change asset total amount during routing. Asset total amount is derived from asset payments total and the // quantity of assets if (getAssetService().isDocumentEnrouting(document) && (!minTotalPaymentByAsset.equals(assetGlobal.getMinAssetTotalAmount()) || !maxTotalPaymentByAsset.equals(assetGlobal.getMaxAssetTotalAmount()))) { putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS, CamsKeyConstants.AssetGlobal.ERROR_CHANGE_ASSET_TOTAL_AMOUNT_DISALLOW, !minTotalPaymentByAsset.equals(assetGlobal.getMinAssetTotalAmount()) ? new String[] { (String) new CurrencyFormatter().format(assetGlobal.getMinAssetTotalAmount()), (String) new CurrencyFormatter().format(minTotalPaymentByAsset) } : new String[] { (String) new CurrencyFormatter().format(assetGlobal.getMaxAssetTotalAmount()), (String) new CurrencyFormatter().format(maxTotalPaymentByAsset) }); success = false; } // run threshold checking before routing if (!getAssetService().isDocumentEnrouting(document)) { String capitalizationThresholdAmount = getCapitalizationThresholdAmount(); if (isCapitalStatus(assetGlobal)) { // check if amount is above threshold for capital asset. if (!validateCapitalAssetAmountAboveThreshhold(document, minTotalPaymentByAsset, capitalizationThresholdAmount)) { putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_CAPITAL_ASSET_PAYMENT_AMOUNT_MIN, capitalizationThresholdAmount); success = false; } } else { // check if amount is less than threshold for non-capital assets for all users success &= validateNonCapitalAssetAmountBelowThreshold(maxTotalPaymentByAsset, capitalizationThresholdAmount); } } return success; } /** * Get the capitalization threshold amount from the system parameter setting. * * @return */ protected String getCapitalizationThresholdAmount() { return getParameterService().getParameterValueAsString(AssetGlobal.class, CamsConstants.Parameters.CAPITALIZATION_LIMIT_AMOUNT); } /** * Validate Capital Asset Amount above the threshold or below the amount for authorized user only. * * @param document * @param assetAmount * @param capitalizationThresholdAmount * @return */ protected boolean validateCapitalAssetAmountAboveThreshhold(MaintenanceDocument document, KualiDecimal assetAmount, String capitalizationThresholdAmount) { boolean success = true; FinancialSystemMaintenanceDocumentAuthorizerBase documentAuthorizer = (FinancialSystemMaintenanceDocumentAuthorizerBase) SpringContext.getBean(DocumentDictionaryService.class).getDocumentAuthorizer(document); boolean isOverrideAuthorized = documentAuthorizer.isAuthorized(document, CamsConstants.CAM_MODULE_CODE, CamsConstants.PermissionNames.OVERRIDE_CAPITALIZATION_LIMIT_AMOUNT, GlobalVariables.getUserSession().getPerson().getPrincipalId()); if (assetAmount.isLessThan(new KualiDecimal(capitalizationThresholdAmount)) && !isOverrideAuthorized) { success = false; } return success; } /** * Validate non-capital asset amount below the threshold. * * @param assetAmount * @param capitalizationThresholdAmount * @return */ protected boolean validateNonCapitalAssetAmountBelowThreshold(KualiDecimal assetAmount, String capitalizationThresholdAmount) { boolean success = true; if (assetAmount.isGreaterEqual(new KualiDecimal(capitalizationThresholdAmount))) { putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_NON_CAPITAL_ASSET_PAYMENT_AMOUNT_MAX, capitalizationThresholdAmount); success &= false; } return success; } /** * Validate that the total cost of the source asset is not zero or a negative amount. * * @param assetGlobal * @return boolean */ protected boolean validateTotalCostAmount(AssetGlobal assetGlobal) { boolean success = true; if (!assetGlobal.getTotalCostAmount().isGreaterThan(KualiDecimal.ZERO)) { GlobalVariables.getMessageMap().putErrorForSectionId(CamsConstants.AssetGlobal.SECTION_ID_ASSET_INFORMATION, CamsKeyConstants.AssetSeparate.ERROR_ZERO_OR_NEGATIVE_DOLLAR_AMOUNT); success &= false; } return success; } /** * Validates the capital asset type code. This only checks for the existence of some contents, not whether the contents are valid. * * @param uniqueLocationDetails * @return boolean */ protected boolean validateCapitalAssetTypeCode(AssetGlobalDetail uniqueLocationDetails) { boolean success = true; if (StringUtils.isEmpty(uniqueLocationDetails.getCapitalAssetTypeCode())) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetSeparate.ERROR_CAPITAL_ASSET_TYPE_CODE_REQUIRED, uniqueLocationDetails.getCapitalAssetTypeCode()); success &= false; } return success; } /** * Validates the asset description. * * @param uniqueLocationDetails * @return boolean */ protected boolean validateAssetDescription(AssetGlobalDetail uniqueLocationDetails) { boolean success = true; if (StringUtils.isEmpty(uniqueLocationDetails.getCapitalAssetDescription())) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAPITAL_ASSET_DESCRIPTION, CamsKeyConstants.AssetSeparate.ERROR_ASSET_DESCRIPTION_REQUIRED, uniqueLocationDetails.getCapitalAssetTypeCode()); success &= false; } return success; } /** * Validates the manufacturer. * * @param uniqueLocationDetails * @return boolean */ protected boolean validateManufacturer(AssetGlobalDetail uniqueLocationDetails) { boolean success = true; if (StringUtils.isEmpty(uniqueLocationDetails.getManufacturerName())) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.MANUFACTURER_NAME, CamsKeyConstants.AssetSeparate.ERROR_MANUFACTURER_REQUIRED, uniqueLocationDetails.getCapitalAssetTypeCode()); success &= false; } return success; } /** * Validates the separate source amount. * * @param uniqueLocationDetails * @return boolean */ protected boolean validateSeparateSourceAmount(AssetGlobalDetail uniqueLocationDetail, MaintenanceDocument document) { boolean success = true; KualiDecimal separateSourceAmount = uniqueLocationDetail.getSeparateSourceAmount(); if (separateSourceAmount == null || separateSourceAmount.isLessEqual(KualiDecimal.ZERO)) { GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.SEPARATE_SOURCE_AMOUNT, CamsKeyConstants.AssetSeparate.ERROR_TOTAL_SEPARATE_SOURCE_AMOUNT_REQUIRED); success &= false; } else { // for capital asset separate, validate the minimal separate source amount above the threshold success &= validateSeparateSourceAmountAboveThreshold(document, uniqueLocationDetail); } return success; } protected boolean validateLocationCollection(AssetGlobal assetGlobal, List<AssetGlobalDetail> assetSharedDetails) { boolean success = true; // for each shared location info, validate boolean isCapitalAsset = isCapitalStatus(assetGlobal); int index = 0; for (AssetGlobalDetail assetLocationDetail : assetSharedDetails) { String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + index + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); success &= SpringContext.getBean(AssetLocationService.class).validateLocation(LOCATION_FIELD_MAP, assetLocationDetail, isCapitalAsset, assetGlobal.getCapitalAssetType()); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); index++; } return success; } @Override protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject(); boolean success = true; success &= super.processCustomSaveDocumentBusinessRules(document); if (GlobalVariables.getMessageMap().hasErrors()) { return false; } String acquisitionTypeCode = assetGlobal.getAcquisitionTypeCode(); String statusCode = assetGlobal.getInventoryStatusCode(); // no need to validate specific fields if document is "Asset Separate" if (!getAssetGlobalService().isAssetSeparate(assetGlobal)) { success &= validateAccount(assetGlobal); if (StringUtils.isNotBlank(acquisitionTypeCode) && StringUtils.isNotBlank(statusCode)) { // check if status code and acquisition type code combination is valid success &= /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(AssetGlobal.class, CamsConstants.Parameters.VALID_ASSET_STATUSES_BY_ACQUISITION_TYPE, CamsConstants.Parameters.INVALID_ASSET_STATUSES_BY_ACQUISITION_TYPE, acquisitionTypeCode, statusCode).evaluateAndAddError(AssetGlobal.class, CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE); } success &= validateAssetType(assetGlobal); if (isCapitalStatus(assetGlobal)) { success &= validateVendorAndManufacturer(assetGlobal); } success &= validatePaymentCollection(document, assetGlobal); } else { // append doc type to existing doc header description if (!document.getDocumentHeader().getDocumentDescription().toLowerCase().contains(CamsConstants.AssetSeparate.SEPARATE_AN_ASSET_DESCRIPTION.toLowerCase())) { Integer maxDocumentDescription = ddService.getAttributeMaxLength(DocumentHeader.class, KRADPropertyConstants.DOCUMENT_DESCRIPTION); String documentDescription = CamsConstants.AssetSeparate.SEPARATE_AN_ASSET_DESCRIPTION + " " + document.getDocumentHeader().getDocumentDescription(); documentDescription = StringUtils.left(documentDescription, maxDocumentDescription); document.getDocumentHeader().setDocumentDescription(documentDescription); } } // System shall only generate GL entries if we have an incomeAssetObjectCode for this acquisitionTypeCode and the statusCode // is for capital assets // GLs should not be generated while separating assets too if ((success && !getAssetGlobalService().isAssetSeparate(assetGlobal) && super.processCustomSaveDocumentBusinessRules(document)) && getAssetAcquisitionTypeService().hasIncomeAssetObjectCode(acquisitionTypeCode) && this.isCapitalStatus(assetGlobal)) { if (success &= validateAcquisitionIncomeObjectCode(assetGlobal)) { // create poster AssetGlobalGeneralLedgerPendingEntrySource assetGlobalGlPoster = new AssetGlobalGeneralLedgerPendingEntrySource((FinancialSystemDocumentHeader) document.getDocumentHeader()); // create postables getAssetGlobalService().createGLPostables(assetGlobal, assetGlobalGlPoster); if (SpringContext.getBean(GeneralLedgerPendingEntryService.class).generateGeneralLedgerPendingEntries(assetGlobalGlPoster)) { assetGlobal.setGeneralLedgerPendingEntries(assetGlobalGlPoster.getPendingEntries()); } else { assetGlobalGlPoster.getPendingEntries().clear(); } } } return success; } /** * Locking on separate source asset number * * @param document * @param assetGlobal * @return */ protected boolean setAssetLock(MaintenanceDocument document, AssetGlobal assetGlobal) { List<Long> capitalAssetNumbers = new ArrayList<Long>(); if (assetGlobal.getSeparateSourceCapitalAssetNumber() != null) { capitalAssetNumbers.add(assetGlobal.getSeparateSourceCapitalAssetNumber()); } return SpringContext.getBean(CapitalAssetManagementModuleService.class).storeAssetLocks(capitalAssetNumbers, document.getDocumentNumber(), DocumentTypeName.ASSET_SEPARATE, null); } /** * Validate offset object code * * @param assetGlobal * @return */ protected boolean validateAcquisitionIncomeObjectCode(AssetGlobal assetGlobal) { boolean valid = true; for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) { // check offset object code existence ObjectCode objectCode = SpringContext.getBean(ObjectCodeService.class).getByPrimaryId(assetPaymentDetail.getPostingYear(), assetPaymentDetail.getChartOfAccountsCode(), assetGlobal.getAcquisitionType().getIncomeAssetObjectCode()); if (ObjectUtils.isNull(objectCode)) { putFieldError(CamsPropertyConstants.AssetGlobal.ACQUISITION_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_INVALID_ACQUISITION_INCOME_OBJECT_CODE, new String[] { assetGlobal.getAcquisitionType().getIncomeAssetObjectCode(), assetPaymentDetail.getPostingYear().toString(), assetPaymentDetail.getChartOfAccountsCode() }); valid = false; } // check Object Code active else if (!objectCode.isActive()) { putFieldError(CamsPropertyConstants.AssetGlobal.ACQUISITION_TYPE_CODE, CamsKeyConstants.GLPosting.ERROR_OBJECT_CODE_FROM_ASSET_OBJECT_CODE_INACTIVE, new String[] { CamsConstants.GLPostingObjectCodeType.INCOME, assetGlobal.getAcquisitionType().getIncomeAssetObjectCode(), assetPaymentDetail.getChartOfAccountsCode() }); valid = false; } } return valid; } protected boolean validatePaymentCollection(MaintenanceDocument maintenanceDocument, AssetGlobal assetGlobal) { boolean success = true; int index = 0; for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) { String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS + "[" + index + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); success &= validatePaymentLine(maintenanceDocument, assetGlobal, assetPaymentDetail); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); index++; } return success; } protected boolean validateVendorAndManufacturer(AssetGlobal assetGlobal) { boolean success = true; if (StringUtils.isBlank(assetGlobal.getVendorName())) { putFieldError(CamsPropertyConstants.AssetGlobal.VENDOR_NAME, CamsKeyConstants.AssetGlobal.ERROR_VENDOR_NAME_REQUIRED); success &= false; } if (StringUtils.isBlank(assetGlobal.getManufacturerName())) { putFieldError(CamsPropertyConstants.AssetGlobal.MFR_NAME, CamsKeyConstants.AssetGlobal.ERROR_MFR_NAME_REQUIRED); success &= false; } return success; } @Override public boolean processSaveDocument(Document document) { boolean valid = true; MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document; AssetGlobal assetGlobal = (AssetGlobal) maintenanceDocument.getNewMaintainableObject().getBusinessObject(); List<AssetGlobalDetail> assetSharedDetails = assetGlobal.getAssetSharedDetails(); int index = 0; for (AssetGlobalDetail assetLocationDetail : assetSharedDetails) { String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + index + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); valid &= checkReferenceExists(assetLocationDetail); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); // checks that all of the asset types entered in the collection of Unique Details actually exist in persistent storage int indexUniqueDetails = 0; for (AssetGlobalDetail assetGlobalUniqueDetails : assetLocationDetail.getAssetGlobalUniqueDetails()) { valid &= validateAssetType(assetGlobalUniqueDetails, index, indexUniqueDetails); indexUniqueDetails++; } index++; } // Creates locking representation for this global document. The locking is only applicable for assets that are being split. // The assets that are being created do not need to be locked since they don't exist yet. if (valid && getAssetGlobalService().isAssetSeparate(assetGlobal)) { valid &= setAssetLock(maintenanceDocument, assetGlobal); } return valid && super.processSaveDocument(document); } protected boolean validateAccount(AssetGlobal assetGlobal) { boolean success = true; assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.ORGANIZATION_OWNER_ACCOUNT); Account organizationOwnerAccount = assetGlobal.getOrganizationOwnerAccount(); boolean skipAccountAvailiablityCheck; if (StringUtils.isBlank(assetGlobal.getOrganizationOwnerChartOfAccountsCode()) || StringUtils.isBlank(assetGlobal.getOrganizationOwnerAccountNumber())) { skipAccountAvailiablityCheck = true; } else { skipAccountAvailiablityCheck = isOrgOwnerAccountFromCab(assetGlobal); } // when check if organizationOwnerAccount is existing, use ObjectUtils rather than comparing with 'null' since OJB proxy // object is used here. if (!skipAccountAvailiablityCheck && (ObjectUtils.isNull(organizationOwnerAccount) || !organizationOwnerAccount.isActive() || organizationOwnerAccount.isExpired())) { putFieldError(CamsPropertyConstants.AssetGlobal.ORGANIZATION_OWNER_ACCOUNT_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_OWNER_ACCT_NOT_ACTIVE, new String[] { assetGlobal.getOrganizationOwnerChartOfAccountsCode(), assetGlobal.getOrganizationOwnerAccountNumber() }); success &= false; } return success; } /** * Check if organization owner account is set from CAB. We honor all accounting lines from CAB are valid payments even thougth * they are expired. * * @param assetGlobal * @return */ protected boolean isOrgOwnerAccountFromCab(AssetGlobal assetGlobal) { String orgOwnerChartCode = assetGlobal.getOrganizationOwnerChartOfAccountsCode(); String orgOwnerAccountNbr = assetGlobal.getOrganizationOwnerAccountNumber(); if (StringUtils.isBlank(assetGlobal.getOrganizationOwnerChartOfAccountsCode()) || StringUtils.isBlank(assetGlobal.getOrganizationOwnerAccountNumber())) { return true; } if (assetGlobal.isCapitalAssetBuilderOriginIndicator()) { // If CAB submits, allow use of inactive accounting line from the payments for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) { if (orgOwnerChartCode.equalsIgnoreCase(assetPaymentDetail.getChartOfAccountsCode()) && orgOwnerAccountNbr.equalsIgnoreCase(assetPaymentDetail.getAccountNumber())) { return true; } } } return false; } /** * Validate location * * @param assetGlobal * @return boolean */ protected boolean validateLocation(AssetGlobal assetGlobal, AssetGlobalDetail assetGlobalDetail) { boolean success = true; if (StringUtils.isBlank(assetGlobal.getInventoryStatusCode())) { putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_INVENTORY_STATUS_REQUIRED); success &= false; } success = validateAssetType(assetGlobal); if (success) { boolean isCapitalAsset = isCapitalStatus(assetGlobal); success &= SpringContext.getBean(AssetLocationService.class).validateLocation(LOCATION_FIELD_MAP, assetGlobalDetail, isCapitalAsset, assetGlobal.getCapitalAssetType()); } else { putFieldError(CamsPropertyConstants.AssetGlobal.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_ASSET_LOCATION_DEPENDENCY); } return success; } /** * Validate asset type at the AssetGlobal level. Only checks that there are contents in the object. * * @param assetGlobal * @return boolean */ protected boolean validateAssetType(AssetGlobal assetGlobal) { boolean success = true; assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.CAPITAL_ASSET_TYPE); if (ObjectUtils.isNull(assetGlobal.getCapitalAssetType())) { putFieldError(CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS, CamsKeyConstants.AssetGlobal.ERROR_ASSET_TYPE_REQUIRED); success &= false; } return success; } /** * Validate asset type in the AssetGlobalUniqueDetails level, and ensures the value is in the list of valid types. * The incoming indices are for creation of the errorPath for the global variable message map, which will determine * what text field to mark as having a problem. This was written to be called within a loop. * * @param assetGlobalUniqueDetails * @param sharedIndex the index of the shared details within the AssetGlobal * @param uniqueIndex the index of the unique details within the shared details * @return boolean */ protected boolean validateAssetType(AssetGlobalDetail assetGlobalUniqueDetails, Integer sharedIndex, Integer uniqueIndex) { boolean success = true; int sharedInd = 0; int uniqueInd = 0; // In hindsite, maybe this should have been written to perform the loop-within-loop fully contained within this // method, and not used within the typical double loop structure of processSaveDocument() and // processCustomRouteDecumentBusinessRules. // don't change the value of the incoming param! if (sharedIndex != null) { sharedInd = sharedIndex; } if (uniqueIndex != null) { uniqueInd = uniqueIndex; } if (ObjectUtils.isNull(assetGlobalUniqueDetails)) { putFieldError(CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS, CamsKeyConstants.AssetGlobal.ERROR_ASSET_LOCATION_DEPENDENCY); success &= false; } // validate asset type if (StringUtils.isNotBlank(assetGlobalUniqueDetails.getCapitalAssetTypeCode())) { AssetType assetType = SpringContext.getBean(BusinessObjectService.class).findBySinglePrimaryKey(AssetType.class, assetGlobalUniqueDetails.getCapitalAssetTypeCode()); if (assetType == null || StringUtils.isBlank(assetType.getCapitalAssetTypeCode())) { String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + sharedInd + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS + "[" + uniqueInd + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetSeparate.ERROR_CAPITAL_ASSET_TYPE_CODE_INVALID, assetGlobalUniqueDetails.getCapitalAssetTypeCode()); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); success &= false; } } return success; } /** * Give an error if this asset can't be separated due to mismatching amount on asset and AssetPayment records * * @param assetGlobal * @return validation success of failure */ public static boolean validateAssetTotalCostMatchesPaymentTotalCost(AssetGlobal assetGlobal) { PaymentSummaryService paymentSummaryService = SpringContext.getBean(PaymentSummaryService.class); assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET); KualiDecimal assetTotalCost = ObjectUtils.isNull(assetGlobal.getSeparateSourceCapitalAsset().getTotalCostAmount()) ? new KualiDecimal(0) : assetGlobal.getSeparateSourceCapitalAsset().getTotalCostAmount(); KualiDecimal paymentTotalCost = paymentSummaryService.calculatePaymentTotalCost(assetGlobal.getSeparateSourceCapitalAsset()); if (!paymentTotalCost.equals(assetTotalCost)) { GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_SEPARATE_ASSET_TOTAL_COST_NOT_MATCH_PAYMENT_TOTAL_COST); return false; } return true; } /** * Give an error if this asset has already been separated * * @param assetGlobal * @return validation success of failure */ public static boolean validateAssetAlreadySeparated(Long separateSourceCapitalAssetNumber) { AssetService assetService = SpringContext.getBean(AssetService.class); List<String> documentNumbers = assetService.getDocumentNumbersThatSeparatedThisAsset(separateSourceCapitalAssetNumber); if (!documentNumbers.isEmpty()) { GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_SEPARATE_ASSET_ALREADY_SEPARATED, new String[] { documentNumbers.toString() }); return false; } return true; } /** * * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#dataDictionaryValidate(org.kuali.rice.kns.document.MaintenanceDocument) * Override this method to only validate reference exists for asset separate , otherwise do default Existence Checks. * KFSMI-6584 */ @Override protected boolean dataDictionaryValidate(MaintenanceDocument document) { AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject(); LOG.debug("MaintenanceDocument validation beginning"); // explicitly put the errorPath that the dictionaryValidationService requires GlobalVariables.getMessageMap().addToErrorPath("document.newMaintainableObject"); // document must have a newMaintainable object Maintainable newMaintainable = document.getNewMaintainableObject(); if (newMaintainable == null) { GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject"); throw new ValidationException("Maintainable object from Maintenance Document '" + document.getDocumentTitle() + "' is null, unable to proceed."); } // document's newMaintainable must contain an object (ie, not null) PersistableBusinessObject businessObject = newMaintainable.getBusinessObject(); if (businessObject == null) { GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject."); throw new ValidationException("Maintainable's component business object is null."); } // run required check from maintenance data dictionary maintDocDictionaryService.validateMaintenanceRequiredFields(document); //check for duplicate entries in collections if necessary maintDocDictionaryService.validateMaintainableCollectionsForDuplicateEntries(document); // run the DD DictionaryValidation (non-recursive) dictionaryValidationService.validateBusinessObjectOnMaintenanceDocument(businessObject, document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName()); // do default (ie, mandatory) existence checks if (!getAssetGlobalService().isAssetSeparate(assetGlobal)) { dictionaryValidationService.validateDefaultExistenceChecks(businessObject); } else { Collection references = KNSServiceLocator.getMaintenanceDocumentDictionaryService().getDefaultExistenceChecks(businessObject.getClass()); // walk through the references, doing the tests on each for (Iterator iter = references.iterator(); iter.hasNext();) { ReferenceDefinition reference = (ReferenceDefinition) iter.next(); // do the existence and validation testing dictionaryValidationService.validateReferenceExists(assetGlobal,reference ); } } // explicitly remove the errorPath we've added GlobalVariables.getMessageMap().removeFromErrorPath("document.newMaintainableObject"); LOG.debug("MaintenanceDocument validation ending"); return true; } protected ParameterService getParameterService() { return SpringContext.getBean(ParameterService.class); } protected AssetService getAssetService() { return SpringContext.getBean(AssetService.class); } protected AssetPaymentService getAssetPaymentService() { return SpringContext.getBean(AssetPaymentService.class); } protected AssetAcquisitionTypeService getAssetAcquisitionTypeService() { return SpringContext.getBean(AssetAcquisitionTypeService.class); } protected AssetGlobalService getAssetGlobalService() { return SpringContext.getBean(AssetGlobalService.class); } }