001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.cam.document.validation.impl;
017    
018    import java.util.ArrayList;
019    import java.util.Date;
020    import java.util.HashMap;
021    import java.util.HashSet;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.kfs.coa.businessobject.Account;
027    import org.kuali.kfs.coa.businessobject.ObjectCode;
028    import org.kuali.kfs.coa.service.ObjectCodeService;
029    import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
030    import org.kuali.kfs.module.cam.CamsConstants;
031    import org.kuali.kfs.module.cam.CamsConstants.DocumentTypeName;
032    import org.kuali.kfs.module.cam.CamsKeyConstants;
033    import org.kuali.kfs.module.cam.CamsPropertyConstants;
034    import org.kuali.kfs.module.cam.businessobject.Asset;
035    import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
036    import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail;
037    import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
038    import org.kuali.kfs.module.cam.businessobject.AssetType;
039    import org.kuali.kfs.module.cam.document.gl.AssetGlobalGeneralLedgerPendingEntrySource;
040    import org.kuali.kfs.module.cam.document.service.AssetAcquisitionTypeService;
041    import org.kuali.kfs.module.cam.document.service.AssetGlobalService;
042    import org.kuali.kfs.module.cam.document.service.AssetLocationService;
043    import org.kuali.kfs.module.cam.document.service.AssetLocationService.LocationField;
044    import org.kuali.kfs.module.cam.document.service.AssetPaymentService;
045    import org.kuali.kfs.module.cam.document.service.AssetService;
046    import org.kuali.kfs.module.cam.document.service.PaymentSummaryService;
047    import org.kuali.kfs.sys.KFSPropertyConstants;
048    import org.kuali.kfs.sys.businessobject.Building;
049    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
050    import org.kuali.kfs.sys.businessobject.Room;
051    import org.kuali.kfs.sys.context.SpringContext;
052    import org.kuali.kfs.sys.document.authorization.FinancialSystemMaintenanceDocumentAuthorizerBase;
053    import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
054    import org.kuali.rice.kns.bo.Campus;
055    import org.kuali.rice.kns.bo.PersistableBusinessObject;
056    import org.kuali.rice.kns.document.Document;
057    import org.kuali.rice.kns.document.MaintenanceDocument;
058    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
059    import org.kuali.rice.kns.service.BusinessObjectService;
060    import org.kuali.rice.kns.service.DataDictionaryService;
061    import org.kuali.rice.kns.service.DateTimeService;
062    import org.kuali.rice.kns.service.KNSServiceLocator;
063    import org.kuali.rice.kns.service.KualiModuleService;
064    import org.kuali.rice.kns.service.ParameterEvaluator;
065    import org.kuali.rice.kns.service.ParameterService;
066    import org.kuali.rice.kns.util.GlobalVariables;
067    import org.kuali.rice.kns.util.KualiDecimal;
068    import org.kuali.rice.kns.util.ObjectUtils;
069    import org.kuali.rice.kns.util.RiceKeyConstants;
070    import org.kuali.rice.kns.web.format.CurrencyFormatter;
071    
072    /**
073     * Rule implementation for Asset Global document.
074     */
075    public class AssetGlobalRule extends MaintenanceDocumentRuleBase {
076        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetGlobalRule.class);
077        protected static final Map<LocationField, String> LOCATION_FIELD_MAP = new HashMap<LocationField, String>();
078        static {
079            LOCATION_FIELD_MAP.put(LocationField.CAMPUS_CODE, CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE);
080            LOCATION_FIELD_MAP.put(LocationField.BUILDING_CODE, CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE);
081            LOCATION_FIELD_MAP.put(LocationField.ROOM_NUMBER, CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER);
082            LOCATION_FIELD_MAP.put(LocationField.SUB_ROOM_NUMBER, CamsPropertyConstants.AssetGlobalDetail.BUILDING_SUB_ROOM_NUMBER);
083            LOCATION_FIELD_MAP.put(LocationField.CONTACT_NAME, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_NAME);
084            LOCATION_FIELD_MAP.put(LocationField.STREET_ADDRESS, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_ADDRESS);
085            LOCATION_FIELD_MAP.put(LocationField.CITY_NAME, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_CITY_NAME);
086            LOCATION_FIELD_MAP.put(LocationField.STATE_CODE, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_STATE_CODE);
087            LOCATION_FIELD_MAP.put(LocationField.ZIP_CODE, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_ZIP_CODE);
088            LOCATION_FIELD_MAP.put(LocationField.COUNTRY_CODE, CamsPropertyConstants.AssetGlobalDetail.OFF_CAMPUS_COUNTRY_CODE);
089        }
090    
091        /**
092         * This method checks reference fields when adding new payment into collection.
093         * 
094         * @param assetGlobal
095         * @param assetPaymentDetail
096         * @return
097         */
098        protected boolean checkReferenceExists(AssetGlobal assetGlobal, AssetPaymentDetail assetPaymentDetail) {
099            boolean valid = true;
100    
101            // validate required field for object code. Skip the error message because the maintenance DD 'defaultExistenceChecks' can
102            // generate the same error message. We won't duplicate it.
103            if (StringUtils.isBlank(assetPaymentDetail.getFinancialObjectCode())) {
104                valid = false;
105            }
106    
107            // Validate Financial Posted date
108            if (assetPaymentDetail.getExpenditureFinancialDocumentPostedDate() != null) {
109                valid &= validatePostedDate(assetPaymentDetail);
110    
111            }
112    
113            if (valid) {
114                // Check for ObjectCode
115    
116                if (StringUtils.isNotBlank(assetPaymentDetail.getFinancialObjectCode())) {
117                    assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE);
118                    if (ObjectUtils.isNull(assetPaymentDetail.getObjectCode())) {
119                        String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE).getLabel();
120                        GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
121                        valid &= false;
122                    }
123                }
124            }
125    
126            if (StringUtils.isBlank(assetGlobal.getAcquisitionTypeCode()) || ObjectUtils.isNull(assetGlobal.getAcquisitionType())) {
127                putFieldError(CamsPropertyConstants.AssetGlobal.ACQUISITION_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_ACQUISITION_TYPE_CODE_REQUIRED);
128                valid &= false;
129            }
130    
131            if (StringUtils.isBlank(assetGlobal.getInventoryStatusCode())) {
132                putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_INVENTORY_STATUS_REQUIRED_FOR_PAYMENT);
133                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SEQUENCE_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_ASSET_PAYMENT_DEPENDENCY);
134                valid &= false;
135            }
136            if (StringUtils.isBlank(assetGlobal.getCapitalAssetTypeCode())) {
137                putFieldError(CamsPropertyConstants.AssetGlobal.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_ASSET_TYPE_REQUIRED);
138                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SEQUENCE_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_ASSET_PAYMENT_DEPENDENCY);
139                valid &= false;
140            }
141    
142            // check for existence and active when not separating. This can't be done in the DD because we have a condition on it.
143            // Note: Even though on separate the payment lines can't be edited we still can't force the rules because data may have
144            // gone inactive since then.
145            if (!getAssetGlobalService().isAssetSeparate(assetGlobal)) {
146                assetPaymentDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT);
147                if (StringUtils.isBlank(assetPaymentDetail.getAccountNumber()) || isAccountInvalid(assetPaymentDetail.getAccount())) {
148                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_PAYMENT_ACCT_NOT_VALID, new String[] { assetPaymentDetail.getChartOfAccountsCode(), assetPaymentDetail.getAccountNumber() });
149                    valid &= false;
150                }
151                else if (!StringUtils.isBlank(assetPaymentDetail.getAccountNumber()) && !assetPaymentDetail.getAccount().isActive()) {
152                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT_NUMBER).getLabel();
153                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ACCOUNT_NUMBER, RiceKeyConstants.ERROR_INACTIVE, label);
154                    valid &= false;
155                }
156    
157                assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.SUB_ACCOUNT);
158                if (!StringUtils.isBlank(assetPaymentDetail.getSubAccountNumber()) && ObjectUtils.isNull(assetPaymentDetail.getSubAccount())) {
159                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER).getLabel();
160                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER, RiceKeyConstants.ERROR_EXISTENCE, label);
161                    valid &= false;
162                }
163                else if (!StringUtils.isBlank(assetPaymentDetail.getSubAccountNumber()) && !assetPaymentDetail.getSubAccount().isActive()) {
164                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER).getLabel();
165                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_ACCOUNT_NUMBER, RiceKeyConstants.ERROR_INACTIVE, label);
166                    valid &= false;
167                }
168    
169                assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE);
170                if (!StringUtils.isBlank(assetPaymentDetail.getFinancialObjectCode()) && ObjectUtils.isNull(assetPaymentDetail.getObjectCode())) {
171                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE).getLabel();
172                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
173                    valid &= false;
174                }
175                else if (!StringUtils.isBlank(assetPaymentDetail.getFinancialObjectCode()) && !assetPaymentDetail.getObjectCode().isActive()) {
176                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE).getLabel();
177                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE, RiceKeyConstants.ERROR_INACTIVE, label);
178                    valid &= false;
179                }
180    
181                assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.SUB_OBJECT_CODE);
182                if (!StringUtils.isBlank(assetPaymentDetail.getFinancialSubObjectCode()) && ObjectUtils.isNull(assetPaymentDetail.getSubObjectCode())) {
183                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE).getLabel();
184                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
185                    valid &= false;
186                }
187                else if (!StringUtils.isBlank(assetPaymentDetail.getFinancialSubObjectCode()) && !assetPaymentDetail.getSubObjectCode().isActive()) {
188                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE).getLabel();
189                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.SUB_OBJECT_CODE, RiceKeyConstants.ERROR_INACTIVE, label);
190                    valid &= false;
191                }
192    
193                assetPaymentDetail.refreshReferenceObject(KFSPropertyConstants.PROJECT);
194                if (!StringUtils.isBlank(assetPaymentDetail.getProjectCode()) && ObjectUtils.isNull(assetPaymentDetail.getProject())) {
195                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE).getLabel();
196                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
197                    valid &= false;
198                }
199                else if (!StringUtils.isBlank(assetPaymentDetail.getProjectCode()) && !assetPaymentDetail.getProject().isActive()) {
200                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE).getLabel();
201                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.PROJECT_CODE, RiceKeyConstants.ERROR_INACTIVE, label);
202                    valid &= false;
203                }
204    
205                assetPaymentDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION);
206                if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialSystemOriginationCode()) && ObjectUtils.isNull(assetPaymentDetail.getExpenditureFinancialSystemOrigination())) {
207                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE).getLabel();
208                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
209                    valid &= false;
210                }
211                else if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialSystemOriginationCode()) && !assetPaymentDetail.getExpenditureFinancialSystemOrigination().isActive()) {
212                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE).getLabel();
213                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.ORIGINATION_CODE, RiceKeyConstants.ERROR_INACTIVE, label);
214                    valid &= false;
215                }
216    
217                if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentTypeCode()) && ObjectUtils.isNull(assetPaymentDetail.getExpenditureFinancialSystemDocumentTypeCode())) {
218                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE).getLabel();
219                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, RiceKeyConstants.ERROR_EXISTENCE, label);
220                    valid &= false;
221                    return valid;
222                }
223                if (!StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentTypeCode()) && !assetPaymentDetail.getExpenditureFinancialSystemDocumentTypeCode().isActive()) {
224                    String label = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(AssetPaymentDetail.class.getName()).getAttributeDefinition(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE).getLabel();
225                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, RiceKeyConstants.ERROR_INACTIVE, label);
226                    valid &= false;
227                }
228            }
229            return valid;
230        }
231    
232        /**
233         * This method checks reference fields when adding one shared location information into collection.
234         * 
235         * @param assetGlobalDetail
236         * @return
237         */
238        protected boolean checkReferenceExists(AssetGlobalDetail assetGlobalDetail) {
239            boolean valid = true;
240            if (StringUtils.isNotBlank(assetGlobalDetail.getCampusCode())) {
241                Map<String, Object> criteria = new HashMap<String, Object>();
242                criteria.put(CamsPropertyConstants.Asset.CAMPUS_CODE, assetGlobalDetail.getCampusCode());
243                Campus campus = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(Campus.class).getExternalizableBusinessObject(Campus.class, criteria);
244    
245                if (ObjectUtils.isNull(campus)) {
246                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, CamsKeyConstants.AssetLocation.ERROR_INVALID_CAMPUS_CODE, assetGlobalDetail.getCampusCode());
247                    valid &= false;
248                }
249                else if (!campus.isActive()) {
250                    String label = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(Campus.class.getName()).getAttributeDefinition(KFSPropertyConstants.CAMPUS_CODE).getLabel();
251                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, RiceKeyConstants.ERROR_INACTIVE, label);
252                    valid &= false;
253                }
254            }
255    
256            if (StringUtils.isNotBlank(assetGlobalDetail.getBuildingCode())) {
257                Map<String, String> objectKeys = new HashMap<String, String>();
258                objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, assetGlobalDetail.getCampusCode());
259                objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, assetGlobalDetail.getBuildingCode());
260                Building building = (Building) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Building.class, objectKeys);
261    
262                if (ObjectUtils.isNull(building)) {
263                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, CamsKeyConstants.AssetLocation.ERROR_INVALID_BUILDING_CODE, assetGlobalDetail.getBuildingCode(), assetGlobalDetail.getCampusCode());
264                    valid &= false;
265                }
266                else if (!building.isActive()) {
267                    String label = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(Building.class.getName()).getAttributeDefinition(KFSPropertyConstants.BUILDING_CODE).getLabel();
268                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, RiceKeyConstants.ERROR_INACTIVE, label);
269                    valid &= false;
270                }
271            }
272    
273            if (StringUtils.isNotBlank(assetGlobalDetail.getBuildingRoomNumber())) {
274                Map<String, String> objectKeys = new HashMap<String, String>();
275                objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_CODE, assetGlobalDetail.getCampusCode());
276                objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.BUILDING_CODE, assetGlobalDetail.getBuildingCode());
277                objectKeys.put(CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER, assetGlobalDetail.getBuildingRoomNumber());
278                Room room = (Room) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Room.class, objectKeys);
279    
280                if (ObjectUtils.isNull(room)) {
281                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_INVALID_ROOM_NUMBER, assetGlobalDetail.getBuildingCode(), assetGlobalDetail.getBuildingRoomNumber(), assetGlobalDetail.getCampusCode());
282                    valid &= false;
283                }
284                else if (!room.isActive()) {
285                    String label = getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(Room.class.getName()).getAttributeDefinition(KFSPropertyConstants.BUILDING_ROOM_NUMBER).getLabel();
286                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.BUILDING_ROOM_NUMBER, RiceKeyConstants.ERROR_INACTIVE, label);
287                    valid &= false;
288                }
289            }
290            return valid;
291        }
292    
293    
294        protected boolean isCapitalStatus(AssetGlobal assetGlobal) {
295            return getParameterService().getParameterValues(Asset.class, CamsConstants.Parameters.CAPITAL_ASSET_STATUS_CODES).contains(assetGlobal.getInventoryStatusCode());
296        }
297    
298        protected boolean isStatusCodeRetired(String statusCode) {
299            return getParameterService().getParameterValues(Asset.class, CamsConstants.Parameters.RETIRED_STATUS_CODES).contains(statusCode);
300        }
301    
302        @Override
303        public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) {
304            boolean success = super.processCustomAddCollectionLineBusinessRules(document, collectionName, line);
305            if (GlobalVariables.getMessageMap().hasErrors()) {
306                return false;
307            }
308    
309            AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject();
310            List<AssetGlobalDetail> assetSharedDetails = assetGlobal.getAssetSharedDetails();
311            if (CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS.equals(collectionName)) {
312                // handle location information
313                AssetGlobalDetail assetGlobalDetail = (AssetGlobalDetail) line;
314                success &= checkReferenceExists(assetGlobalDetail);
315                success &= validateLocation(assetGlobal, assetGlobalDetail);
316    
317                // qty. of assets (unique) to be created
318                success &= validateLocationQuantity(line);
319            }
320            else if (StringUtils.isNotBlank(collectionName) && collectionName.contains(CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS)) {
321                // handle unique information
322                AssetGlobalDetail assetUniqueDetail = (AssetGlobalDetail) line;
323                String campusTagNumber = assetUniqueDetail.getCampusTagNumber();
324                if (StringUtils.isNotBlank(campusTagNumber)) {
325                    success &= validateTagDuplication(assetSharedDetails, campusTagNumber);
326                }
327            }
328            else if (CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS.equals(collectionName)) {
329                AssetPaymentDetail assetPaymentDetail = (AssetPaymentDetail) line;
330    
331                if (success &= checkReferenceExists(assetGlobal, assetPaymentDetail)) {
332                    success &= validatePaymentLine(document, assetGlobal, assetPaymentDetail);
333                    success &= checkNegativeOrZeroPayment(document, assetPaymentDetail);
334                }
335            }
336    
337    
338            // only for "Asset Separate" document
339            if (getAssetGlobalService().isAssetSeparate(assetGlobal)) {
340                // total cost must be > 0
341                success &= validateTotalCostAmount(assetGlobal);
342            }
343            return success;
344        }
345    
346        /**
347         * Validated the location quantity
348         * 
349         * @param line
350         * @return boolean
351         */
352        protected boolean validateLocationQuantity(PersistableBusinessObject line) {
353            boolean success = true;
354            AssetGlobalDetail assetGlobalDetail = (AssetGlobalDetail) line;
355            if (assetGlobalDetail.getLocationQuantity() <= 0) {
356                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.LOCATION_QUANTITY, CamsKeyConstants.AssetSeparate.ERROR_ZERO_OR_NEGATIVE_LOCATION_QUANTITY, assetGlobalDetail.getLocationQuantity().toString());
357                success &= false;
358            }
359            return success;
360        }
361    
362        protected boolean validateTagDuplication(List<AssetGlobalDetail> assetSharedDetails, String campusTagNumber) {
363            boolean success = true;
364            if (!campusTagNumber.equalsIgnoreCase(CamsConstants.Asset.NON_TAGGABLE_ASSET)) {
365                for (AssetGlobalDetail assetSharedDetail : assetSharedDetails) {
366                    List<AssetGlobalDetail> assetGlobalUniqueDetails = assetSharedDetail.getAssetGlobalUniqueDetails();
367                    for (AssetGlobalDetail assetSharedUniqueDetail : assetGlobalUniqueDetails) {
368                        if (campusTagNumber.equalsIgnoreCase(assetSharedUniqueDetail.getCampusTagNumber())) {
369                            success &= false;
370                            GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber);
371                        }
372                    }
373                }
374                if (success) {
375                    List<Asset> tagMatches = getAssetService().findActiveAssetsMatchingTagNumber(campusTagNumber);
376                    if (!tagMatches.isEmpty()) {
377                        GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber);
378                        success &= false;
379                    }
380                }
381            }
382            return success;
383        }
384    
385        protected boolean validateTagDuplication(List<AssetGlobalDetail> assetSharedDetails) {
386            HashSet<String> assetTags = new HashSet<String>();
387            boolean success = true;
388            int parentIndex = -1;
389            int childIndex = -1;
390            for (AssetGlobalDetail assetSharedDetail : assetSharedDetails) {
391                parentIndex++;
392                List<AssetGlobalDetail> assetGlobalUniqueDetails = assetSharedDetail.getAssetGlobalUniqueDetails();
393                for (AssetGlobalDetail assetSharedUniqueDetail : assetGlobalUniqueDetails) {
394                    childIndex++;
395                    String campusTagNumber = assetSharedUniqueDetail.getCampusTagNumber();
396                    if (StringUtils.isNotBlank(campusTagNumber) && !assetTags.add(campusTagNumber) && !campusTagNumber.equalsIgnoreCase(CamsConstants.Asset.NON_TAGGABLE_ASSET)) {
397                        success &= false;
398                        String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + parentIndex + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS + "[" + childIndex + "]";
399                        GlobalVariables.getMessageMap().addToErrorPath(errorPath);
400                        GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber);
401                        GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
402    
403                    }
404                }
405                childIndex = -1;
406                for (AssetGlobalDetail assetSharedUniqueDetail : assetGlobalUniqueDetails) {
407                    childIndex++;
408                    String campusTagNumber = assetSharedUniqueDetail.getCampusTagNumber();
409                    if (StringUtils.isNotBlank(campusTagNumber) && !campusTagNumber.equalsIgnoreCase(CamsConstants.Asset.NON_TAGGABLE_ASSET)) {
410                        List<Asset> tagMatches = getAssetService().findActiveAssetsMatchingTagNumber(campusTagNumber);
411                        if (!tagMatches.isEmpty()) {
412                            success &= false;
413                            String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + parentIndex + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS + "[" + childIndex + "]";
414                            GlobalVariables.getMessageMap().addToErrorPath(errorPath);
415                            GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, campusTagNumber);
416                            GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
417                        }
418                    }
419                }
420            }
421            return success;
422        }
423    
424        protected boolean validatePaymentLine(MaintenanceDocument maintenanceDocument, AssetGlobal assetGlobal, AssetPaymentDetail assetPaymentDetail) {
425            boolean success = true;
426                
427            // If Acquisition type is "New" or "non-capital", check required fields including Document number, Document type code,
428            // Posted date.
429    
430            if (getAssetGlobalService().existsInGroup(getAssetGlobalService().getNewAcquisitionTypeCode(), assetGlobal.getAcquisitionTypeCode()) || !getAssetGlobalService().existsInGroup(getAssetGlobalService().getCapitalObjectAcquisitionCodeGroup(), assetGlobal.getAcquisitionTypeCode())) {
431                success &= checkRequiredFieldsForNewOrNonCapital(assetPaymentDetail);
432            }
433            else {
434                // Validate Financial Document Type Code
435                success &= validateDocumentTypeForNonNew(assetGlobal.getAcquisitionTypeCode(), assetPaymentDetail);
436            }
437    
438            success &= validateObjectCode(assetPaymentDetail.getObjectCode(), assetGlobal);
439    
440    
441            return success;
442        }
443    
444        /**
445         * "Add Negative Payment" permission check.
446         * 
447         * @param maintenanceDocument
448         * @param assetPaymentDetail
449         * @return
450         */
451        protected boolean checkNegativeOrZeroPayment(MaintenanceDocument maintenanceDocument, AssetPaymentDetail assetPaymentDetail) {
452            boolean success = true;
453            FinancialSystemMaintenanceDocumentAuthorizerBase documentAuthorizer = (FinancialSystemMaintenanceDocumentAuthorizerBase) KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(maintenanceDocument);
454            boolean isAuthorized = documentAuthorizer.isAuthorized(maintenanceDocument, CamsConstants.CAM_MODULE_CODE, CamsConstants.PermissionNames.ADD_NEGATIVE_PAYMENTS, GlobalVariables.getUserSession().getPerson().getPrincipalId());
455    
456            if (!isAuthorized && assetPaymentDetail.getAmount() != null && assetPaymentDetail.getAmount().isNegative()) {
457                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.AMOUNT, CamsKeyConstants.AssetGlobal.ERROR_INVALID_PAYMENT_AMOUNT);
458                success = false;
459            }
460    
461            // amount can not be zero for any user
462            if (assetPaymentDetail.getAmount().isZero()) {
463                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.AMOUNT, CamsKeyConstants.AssetGlobal.ERROR_INVALID_PAYMENT_AMOUNT);
464                success = false;
465            }
466            return success;
467        }
468    
469        /**
470         * This method check the required fields for acquisition type New .
471         * 
472         * @param assetPaymentDetail
473         * @return
474         */
475        protected boolean checkRequiredFieldsForNewOrNonCapital(AssetPaymentDetail assetPaymentDetail) {
476            boolean valid = true;
477    
478            if (StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentNumber())) {
479                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_EXPENDITURE_FINANCIAL_DOCUMENT_NUMBER_REQUIRED);
480                valid &= false;
481            }
482            if (assetPaymentDetail.getExpenditureFinancialDocumentPostedDate() == null) {
483                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_POSTING_DATE, CamsKeyConstants.AssetGlobal.ERROR_DOCUMENT_POSTING_DATE_REQUIRED);
484                valid &= false;
485            }
486            if (StringUtils.isBlank(assetPaymentDetail.getExpenditureFinancialDocumentTypeCode())) {
487                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_EXPENDITURE_FINANCIAL_DOCUMENT_TYPE_CODE_REQUIRED);
488                valid &= false;
489            }
490            return valid;
491        }
492    
493        /**
494         * Validates the posted date payment posted date can't be a future date
495         * 
496         * @param assetPaymentDetail
497         * @return boolean
498         */
499        protected boolean validatePostedDate(AssetPaymentDetail assetPaymentDetail) {
500            boolean valid = true;
501            Date currentDate = SpringContext.getBean(DateTimeService.class).getCurrentDate();
502    
503            if (!getAssetPaymentService().extractPostedDatePeriod(assetPaymentDetail)) {
504                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_POSTING_FISCAL_YEAR, CamsKeyConstants.AssetGlobal.ERROR_UNIVERSITY_NOT_DEFINED_FOR_DATE, new String[] { assetPaymentDetail.getExpenditureFinancialDocumentPostedDate().toString() });
505                valid &= false;
506            }
507            else if (assetPaymentDetail.getExpenditureFinancialDocumentPostedDate().compareTo(currentDate) > 0) {
508                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_POSTING_DATE, CamsKeyConstants.Payment.ERROR_INVALID_DOC_POST_DATE);
509                valid = false;
510            }
511            return valid;
512        }
513    
514        /**
515         * When acquisition type code is Capital (Gifts, Transfer-in, State excess, and Found), payment document type code will be
516         * assigned to AA for Add Asset Document.
517         * 
518         * @param documentTypeCode
519         * @return
520         */
521        protected boolean validateDocumentTypeForNonNew(String acquisitionTypeCode, AssetPaymentDetail assetPaymentDetail) {
522            String documentTypeCode = assetPaymentDetail.getExpenditureFinancialDocumentTypeCode();
523    
524            boolean valid = true;
525            if (StringUtils.isNotBlank(acquisitionTypeCode) && getAssetGlobalService().existsInGroup(getAssetGlobalService().getNonNewAcquisitionCodeGroup(), acquisitionTypeCode)) {
526    
527                if (StringUtils.isNotBlank(documentTypeCode) && !CamsConstants.DocumentTypeName.ASSET_ADD_GLOBAL.equalsIgnoreCase(documentTypeCode)) {
528                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetPaymentDetail.DOCUMENT_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_DOCUMENT_TYPE_CODE_NOT_ALLOWED, documentTypeCode);
529                    valid = false;
530                }
531                else {
532                    // system set document type code as 'AA'
533                    assetPaymentDetail.setExpenditureFinancialDocumentTypeCode(CamsConstants.DocumentTypeName.ASSET_ADD_GLOBAL);
534                }
535            }
536            return valid;
537        }
538    
539        /**
540         * Check object code is set to capital only when the status is capital.
541         * 
542         * @param assetGlobal
543         * @param assetPaymentDetail
544         * @return valid
545         */
546        protected boolean validateObjectCode(ObjectCode objectCode, AssetGlobal assetGlobal) {
547            boolean valid = true;
548    
549            // The acquisition type code of (F, G, N, S, T) requires a capital object code.
550            ParameterService parameterService = SpringContext.getBean(ParameterService.class);
551            ParameterEvaluator parameterEvaluator = getParameterService().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());
552            valid &= parameterEvaluator.evaluateAndAddError(ObjectCode.class, CamsPropertyConstants.Asset.FINANCIAL_OBJECT_SUB_TYP_CODE, CamsPropertyConstants.AssetPaymentDetail.FINANCIAL_OBJECT_CODE);
553    
554            return valid;
555        }
556    
557        protected boolean isAccountInvalid(Account account) {
558            return ObjectUtils.isNull(account) || account.isExpired();
559        }
560    
561        @Override
562        protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
563            AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject();
564            List<AssetGlobalDetail> assetSharedDetails = assetGlobal.getAssetSharedDetails();
565            boolean success = super.processCustomRouteDocumentBusinessRules(document);
566            if (GlobalVariables.getMessageMap().hasErrors()) {
567                return false;
568            }
569    
570            // need at least one asset location
571            if (assetSharedDetails.isEmpty() || assetSharedDetails.get(0).getAssetGlobalUniqueDetails().isEmpty()) {
572                putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS, CamsKeyConstants.AssetGlobal.MIN_ONE_ASSET_REQUIRED);
573                success &= false;
574            }
575    
576            // Capital Asset must have payment zone.
577            if (isCapitalStatus(assetGlobal) && assetGlobal.getAssetPaymentDetails().isEmpty()) {
578                putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS, CamsKeyConstants.AssetGlobal.MIN_ONE_PAYMENT_REQUIRED);
579                success &= false;
580            }
581            // check total amount for Asset Create
582            if (!getAssetGlobalService().isAssetSeparate(assetGlobal)) {
583                success &= validateAssetTotalAmount(document);
584            }
585            else {
586                // only for "Asset Separate" document
587                if (getAssetPaymentService().getAssetPaymentDetailQuantity(assetGlobal) >= 10) {
588                    /*
589                     * @TODO KULCAP-828 putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS,
590                     * CamsKeyConstants.AssetSeparate.ERROR_ASSET_SPLIT_MAX_LIMIT); success &= false;
591                     */
592                }
593    
594                // new source payments must be greater than the capital asset cost amount
595                KualiDecimal totalSeparateSourceAmount = getAssetGlobalService().getUniqueAssetsTotalAmount(assetGlobal);
596                if (totalSeparateSourceAmount.isGreaterThan(assetGlobal.getTotalCostAmount())) {
597                    putFieldError(CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS, CamsKeyConstants.AssetSeparate.ERROR_INVALID_TOTAL_SEPARATE_SOURCE_AMOUNT, new String[] { assetGlobal.getSeparateSourceCapitalAssetNumber().toString() });
598                    success &= false;
599                }
600    
601                // only active capital assets can be separated
602                assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET);
603                if (ObjectUtils.isNotNull(assetGlobal.getSeparateSourceCapitalAsset())) {
604                    if (!getAssetService().isCapitalAsset(assetGlobal.getSeparateSourceCapitalAsset())) {
605                        if (StringUtils.isNotBlank(assetGlobal.getAcquisitionTypeCode())) {
606                        putFieldError(CamsPropertyConstants.AssetGlobal.ACQUISITION_TYPE_CODE, CamsKeyConstants.AssetSeparate.ERROR_NON_CAPITAL_ASSET_SEPARATE_REQUIRED);
607                        success &= false;
608                        }
609                    }      
610                }
611    
612                // validate required fields within "Asset Unique Information" tab
613                int sharedIndex = 0;
614                for (AssetGlobalDetail addLocationDetail : assetSharedDetails) {
615                    int uniqueIndex = 0;
616                    for (AssetGlobalDetail assetGlobalUniqueDetail : addLocationDetail.getAssetGlobalUniqueDetails()) {
617                        String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + sharedIndex + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_GLOBAL_UNIQUE_DETAILS + "[" + uniqueIndex + "]";
618                        GlobalVariables.getMessageMap().addToErrorPath(errorPath);
619                        success &= validateCapitalAssetTypeCode(assetGlobalUniqueDetail);
620                        success &= validateAssetType(assetGlobalUniqueDetail, sharedIndex, uniqueIndex);
621                        success &= validateAssetDescription(assetGlobalUniqueDetail);
622                        success &= validateManufacturer(assetGlobalUniqueDetail);
623                        success &= validateSeparateSourceAmount(assetGlobalUniqueDetail, document);
624                        GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
625                        uniqueIndex++;
626                    }
627                    sharedIndex++;
628                }
629    
630                // total cost must be > 0
631                success &= validateTotalCostAmount(assetGlobal);
632    
633                success &= validateAssetTotalCostMatchesPaymentTotalCost(assetGlobal);
634    
635                if (getAssetGlobalService().isAssetSeparateByPayment(assetGlobal)) {
636                    AssetGlobalRule.validateAssetAlreadySeparated(assetGlobal.getSeparateSourceCapitalAssetNumber());
637                }
638            } // end ASEP
639    
640            success &= validateLocationCollection(assetGlobal, assetSharedDetails);
641            success &= validateTagDuplication(assetSharedDetails);
642            return success;
643        }
644    
645    
646        /**
647         * Validate all separate source amount is above the capital asset threshold amount.
648         * 
649         * @param document
650         * @return
651         */
652        protected boolean validateSeparateSourceAmountAboveThreshold(MaintenanceDocument document, AssetGlobalDetail assetGlobalUniqueDetail) {
653            boolean success = true;
654            String capitalizationThresholdAmount = this.getCapitalizationThresholdAmount();
655            KualiDecimal separateAmount = assetGlobalUniqueDetail.getSeparateSourceAmount();
656            // check for the minimal amount only among all separate source amount. if it's above the threshold, safe...
657            if (separateAmount != null && !getAssetService().isDocumentEnrouting(document) && !validateCapitalAssetAmountAboveThreshhold(document, separateAmount, capitalizationThresholdAmount)) {
658                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.SEPARATE_SOURCE_AMOUNT, CamsKeyConstants.AssetSeparate.ERROR_SEPARATE_ASSET_BELOW_THRESHOLD, new String[] { assetGlobalUniqueDetail.getCapitalAssetNumber().toString(), capitalizationThresholdAmount });
659                success = false;
660            }
661            return success;
662        }
663    
664        /**
665         * check if amount is above threshold for capital assets for normal user. minTotalPaymentByAsset and maxTotalPaymentByAsset are
666         * used to check against threshold. Due to the decimal rounding, the asset total amount could have 1 cent difference with each
667         * other. We need to pick up the right value for different threshold check.
668         * 
669         * @param document
670         * @return
671         */
672        protected boolean validateAssetTotalAmount(MaintenanceDocument document) {
673            boolean success = true;
674            AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject();
675    
676            KualiDecimal minTotalPaymentByAsset = getAssetGlobalService().totalPaymentByAsset(assetGlobal, false);
677            KualiDecimal maxTotalPaymentByAsset = getAssetGlobalService().totalPaymentByAsset(assetGlobal, true);
678            if (minTotalPaymentByAsset.isGreaterThan(maxTotalPaymentByAsset)) {
679                // swap min and max
680                KualiDecimal totalPayment = minTotalPaymentByAsset;
681                minTotalPaymentByAsset = maxTotalPaymentByAsset;
682                maxTotalPaymentByAsset = totalPayment;
683            }
684    
685            // Disallow FO change asset total amount during routing. Asset total amount is derived from asset payments total and the
686            // quantity of assets
687            if (getAssetService().isDocumentEnrouting(document) && (!minTotalPaymentByAsset.equals(assetGlobal.getMinAssetTotalAmount()) || !maxTotalPaymentByAsset.equals(assetGlobal.getMaxAssetTotalAmount()))) {
688                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) });
689                success = false;
690            }
691    
692            // run threshold checking before routing
693            if (!getAssetService().isDocumentEnrouting(document)) {
694                String capitalizationThresholdAmount = getCapitalizationThresholdAmount();
695                if (isCapitalStatus(assetGlobal)) {
696                    // check if amount is above threshold for capital asset.
697                    if (!validateCapitalAssetAmountAboveThreshhold(document, minTotalPaymentByAsset, capitalizationThresholdAmount)) {
698                        putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_CAPITAL_ASSET_PAYMENT_AMOUNT_MIN, capitalizationThresholdAmount);
699                        success = false;
700                    }
701                }
702                else {
703                    // check if amount is less than threshold for non-capital assets for all users
704                    success &= validateNonCapitalAssetAmountBelowThreshold(maxTotalPaymentByAsset, capitalizationThresholdAmount);
705                }
706    
707            }
708            return success;
709        }
710    
711        /**
712         * Get the capitalization threshold amount from the system parameter setting.
713         * 
714         * @return
715         */
716        protected String getCapitalizationThresholdAmount() {
717            return getParameterService().getParameterValue(AssetGlobal.class, CamsConstants.Parameters.CAPITALIZATION_LIMIT_AMOUNT);
718        }
719    
720        /**
721         * Validate Capital Asset Amount above the threshold or below the amount for authorized user only.
722         * 
723         * @param document
724         * @param assetAmount
725         * @param capitalizationThresholdAmount
726         * @return
727         */
728        protected boolean validateCapitalAssetAmountAboveThreshhold(MaintenanceDocument document, KualiDecimal assetAmount, String capitalizationThresholdAmount) {
729            boolean success = true;
730            FinancialSystemMaintenanceDocumentAuthorizerBase documentAuthorizer = (FinancialSystemMaintenanceDocumentAuthorizerBase) KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(document);
731            boolean isOverrideAuthorized = documentAuthorizer.isAuthorized(document, CamsConstants.CAM_MODULE_CODE, CamsConstants.PermissionNames.OVERRIDE_CAPITALIZATION_LIMIT_AMOUNT, GlobalVariables.getUserSession().getPerson().getPrincipalId());
732    
733            if (assetAmount.isLessThan(new KualiDecimal(capitalizationThresholdAmount)) && !isOverrideAuthorized) {
734                success = false;
735            }
736            return success;
737        }
738    
739        /**
740         * Validate non-capital asset amount below the threshold.
741         * 
742         * @param assetAmount
743         * @param capitalizationThresholdAmount
744         * @return
745         */
746        protected boolean validateNonCapitalAssetAmountBelowThreshold(KualiDecimal assetAmount, String capitalizationThresholdAmount) {
747            boolean success = true;
748            if (assetAmount.isGreaterEqual(new KualiDecimal(capitalizationThresholdAmount))) {
749                putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_NON_CAPITAL_ASSET_PAYMENT_AMOUNT_MAX, capitalizationThresholdAmount);
750                success &= false;
751            }
752            return success;
753        }
754    
755        /**
756         * Validate that the total cost of the source asset is not zero or a negative amount.
757         * 
758         * @param assetGlobal
759         * @return boolean
760         */
761        protected boolean validateTotalCostAmount(AssetGlobal assetGlobal) {
762            boolean success = true;
763            if (!assetGlobal.getTotalCostAmount().isGreaterThan(KualiDecimal.ZERO)) {
764                GlobalVariables.getMessageMap().putErrorForSectionId(CamsConstants.AssetGlobal.SECTION_ID_ASSET_INFORMATION, CamsKeyConstants.AssetSeparate.ERROR_ZERO_OR_NEGATIVE_DOLLAR_AMOUNT);
765                success &= false;
766            }
767            return success;
768        }
769    
770        /**
771         * Validates the capital asset type code. This only checks for the existence of some contents, not whether the contents are valid.
772         * 
773         * @param uniqueLocationDetails
774         * @return boolean
775         */
776        protected boolean validateCapitalAssetTypeCode(AssetGlobalDetail uniqueLocationDetails) {
777            boolean success = true;
778            if (StringUtils.isEmpty(uniqueLocationDetails.getCapitalAssetTypeCode())) {
779                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetSeparate.ERROR_CAPITAL_ASSET_TYPE_CODE_REQUIRED, uniqueLocationDetails.getCapitalAssetTypeCode());
780                success &= false;
781            }
782            return success;
783        }
784    
785        /**
786         * Validates the asset description.
787         * 
788         * @param uniqueLocationDetails
789         * @return boolean
790         */
791        protected boolean validateAssetDescription(AssetGlobalDetail uniqueLocationDetails) {
792            boolean success = true;
793            if (StringUtils.isEmpty(uniqueLocationDetails.getCapitalAssetDescription())) {
794                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAPITAL_ASSET_DESCRIPTION, CamsKeyConstants.AssetSeparate.ERROR_ASSET_DESCRIPTION_REQUIRED, uniqueLocationDetails.getCapitalAssetTypeCode());
795                success &= false;
796            }
797            return success;
798        }
799    
800        /**
801         * Validates the manufacturer.
802         * 
803         * @param uniqueLocationDetails
804         * @return boolean
805         */
806        protected boolean validateManufacturer(AssetGlobalDetail uniqueLocationDetails) {
807            boolean success = true;
808            if (StringUtils.isEmpty(uniqueLocationDetails.getManufacturerName())) {
809                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.MANUFACTURER_NAME, CamsKeyConstants.AssetSeparate.ERROR_MANUFACTURER_REQUIRED, uniqueLocationDetails.getCapitalAssetTypeCode());
810                success &= false;
811            }
812            return success;
813        }
814    
815        /**
816         * Validates the separate source amount.
817         * 
818         * @param uniqueLocationDetails
819         * @return boolean
820         */
821        protected boolean validateSeparateSourceAmount(AssetGlobalDetail uniqueLocationDetail, MaintenanceDocument document) {
822            boolean success = true;
823            KualiDecimal separateSourceAmount = uniqueLocationDetail.getSeparateSourceAmount();
824            if (separateSourceAmount == null || separateSourceAmount.isLessEqual(KualiDecimal.ZERO)) {
825                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.SEPARATE_SOURCE_AMOUNT, CamsKeyConstants.AssetSeparate.ERROR_TOTAL_SEPARATE_SOURCE_AMOUNT_REQUIRED);
826                success &= false;
827            }
828            else {
829                // for capital asset separate, validate the minimal separate source amount above the threshold
830                success &= validateSeparateSourceAmountAboveThreshold(document, uniqueLocationDetail);
831            }
832            return success;
833        }
834    
835        protected boolean validateLocationCollection(AssetGlobal assetGlobal, List<AssetGlobalDetail> assetSharedDetails) {
836            boolean success = true;
837            // for each shared location info, validate
838            boolean isCapitalAsset = isCapitalStatus(assetGlobal);
839            int index = 0;
840            for (AssetGlobalDetail assetLocationDetail : assetSharedDetails) {
841                String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + index + "]";
842                GlobalVariables.getMessageMap().addToErrorPath(errorPath);
843                success &= SpringContext.getBean(AssetLocationService.class).validateLocation(LOCATION_FIELD_MAP, assetLocationDetail, isCapitalAsset, assetGlobal.getCapitalAssetType());
844                GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
845                index++;
846            }
847            return success;
848        }
849    
850        @Override
851        protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
852            AssetGlobal assetGlobal = (AssetGlobal) document.getNewMaintainableObject().getBusinessObject();
853            boolean success = true;
854            success &= super.processCustomSaveDocumentBusinessRules(document);
855            if (GlobalVariables.getMessageMap().hasErrors()) {
856                return false;
857            }
858    
859            String acquisitionTypeCode = assetGlobal.getAcquisitionTypeCode();
860            String statusCode = assetGlobal.getInventoryStatusCode();
861    
862            // no need to validate specific fields if document is "Asset Separate"
863            if (!getAssetGlobalService().isAssetSeparate(assetGlobal)) {
864                success &= validateAccount(assetGlobal);
865                if (StringUtils.isNotBlank(acquisitionTypeCode) && StringUtils.isNotBlank(statusCode)) {
866                    // check if status code and acquisition type code combination is valid
867                    success &= SpringContext.getBean(ParameterService.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);
868                }
869                success &= validateAssetType(assetGlobal);
870                if (isCapitalStatus(assetGlobal)) {
871                    success &= validateVendorAndManufacturer(assetGlobal);
872                }
873    
874                success &= validatePaymentCollection(document, assetGlobal);
875            }
876            else {
877                // append doc type to existing doc header description
878                if (!document.getDocumentHeader().getDocumentDescription().toLowerCase().contains(CamsConstants.AssetSeparate.SEPARATE_AN_ASSET_DESCRIPTION.toLowerCase())) {
879                    document.getDocumentHeader().setDocumentDescription(CamsConstants.AssetSeparate.SEPARATE_AN_ASSET_DESCRIPTION + " " + document.getDocumentHeader().getDocumentDescription());
880                }
881            }
882    
883            // System shall only generate GL entries if we have an incomeAssetObjectCode for this acquisitionTypeCode and the statusCode
884            // is for capital assets
885            if ((success && super.processCustomSaveDocumentBusinessRules(document)) && getAssetAcquisitionTypeService().hasIncomeAssetObjectCode(acquisitionTypeCode) && this.isCapitalStatus(assetGlobal)) {
886                if (success &= validateAcquisitionIncomeObjectCode(assetGlobal)) {
887                    // create poster
888                    AssetGlobalGeneralLedgerPendingEntrySource assetGlobalGlPoster = new AssetGlobalGeneralLedgerPendingEntrySource((FinancialSystemDocumentHeader) document.getDocumentHeader());
889                    // create postables
890                    getAssetGlobalService().createGLPostables(assetGlobal, assetGlobalGlPoster);
891    
892                    if (SpringContext.getBean(GeneralLedgerPendingEntryService.class).generateGeneralLedgerPendingEntries(assetGlobalGlPoster)) {
893                        assetGlobal.setGeneralLedgerPendingEntries(assetGlobalGlPoster.getPendingEntries());
894                    }
895                    else {
896                        assetGlobalGlPoster.getPendingEntries().clear();
897                    }
898                }
899            }
900    
901            return success;
902        }
903    
904        /**
905         * Locking on separate source asset number
906         * 
907         * @param document
908         * @param assetGlobal
909         * @return
910         */
911        protected boolean setAssetLock(MaintenanceDocument document, AssetGlobal assetGlobal) {
912            List<Long> capitalAssetNumbers = new ArrayList<Long>();
913            if (assetGlobal.getSeparateSourceCapitalAssetNumber() != null) {
914                capitalAssetNumbers.add(assetGlobal.getSeparateSourceCapitalAssetNumber());
915            }
916    
917            return SpringContext.getBean(CapitalAssetManagementModuleService.class).storeAssetLocks(capitalAssetNumbers, document.getDocumentNumber(), DocumentTypeName.ASSET_SEPARATE, null);
918        }
919    
920        /**
921         * Validate offset object code
922         * 
923         * @param assetGlobal
924         * @return
925         */
926        protected boolean validateAcquisitionIncomeObjectCode(AssetGlobal assetGlobal) {
927            boolean valid = true;
928            for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
929                // check offset object code existence
930                ObjectCode objectCode = SpringContext.getBean(ObjectCodeService.class).getByPrimaryId(assetPaymentDetail.getPostingYear(), assetPaymentDetail.getChartOfAccountsCode(), assetGlobal.getAcquisitionType().getIncomeAssetObjectCode());
931                if (ObjectUtils.isNull(objectCode)) {
932                    putFieldError(CamsPropertyConstants.AssetGlobal.ACQUISITION_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_INVALID_ACQUISITION_INCOME_OBJECT_CODE, new String[] { assetGlobal.getAcquisitionType().getIncomeAssetObjectCode(), assetPaymentDetail.getPostingYear().toString(), assetPaymentDetail.getChartOfAccountsCode() });
933                    valid = false;
934                }
935                // check Object Code active
936                else if (!objectCode.isActive()) {
937                    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() });
938                    valid = false;
939                }
940            }
941            return valid;
942        }
943    
944        protected boolean validatePaymentCollection(MaintenanceDocument maintenanceDocument, AssetGlobal assetGlobal) {
945            boolean success = true;
946            int index = 0;
947            for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
948                String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS + "[" + index + "]";
949                GlobalVariables.getMessageMap().addToErrorPath(errorPath);
950                success &= validatePaymentLine(maintenanceDocument, assetGlobal, assetPaymentDetail);
951                GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
952                index++;
953            }
954            return success;
955        }
956    
957        protected boolean validateVendorAndManufacturer(AssetGlobal assetGlobal) {
958            boolean success = true;
959            if (StringUtils.isBlank(assetGlobal.getVendorName())) {
960                putFieldError(CamsPropertyConstants.AssetGlobal.VENDOR_NAME, CamsKeyConstants.AssetGlobal.ERROR_VENDOR_NAME_REQUIRED);
961                success &= false;
962            }
963            if (StringUtils.isBlank(assetGlobal.getManufacturerName())) {
964                putFieldError(CamsPropertyConstants.AssetGlobal.MFR_NAME, CamsKeyConstants.AssetGlobal.ERROR_MFR_NAME_REQUIRED);
965                success &= false;
966            }
967            return success;
968        }
969    
970        @Override
971        public boolean processSaveDocument(Document document) {
972            boolean valid = true;
973            MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document;
974            AssetGlobal assetGlobal = (AssetGlobal) maintenanceDocument.getNewMaintainableObject().getBusinessObject();
975    
976            List<AssetGlobalDetail> assetSharedDetails = assetGlobal.getAssetSharedDetails();
977            int index = 0;
978            for (AssetGlobalDetail assetLocationDetail : assetSharedDetails) {
979                String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + index + "]";
980                GlobalVariables.getMessageMap().addToErrorPath(errorPath);
981                valid &= checkReferenceExists(assetLocationDetail);
982                GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
983    
984                // checks that all of the asset types entered in the collection of Unique Details actually exist in persistent storage
985                int indexUniqueDetails = 0;
986                for (AssetGlobalDetail assetGlobalUniqueDetails : assetLocationDetail.getAssetGlobalUniqueDetails()) {
987                    valid &= validateAssetType(assetGlobalUniqueDetails, index, indexUniqueDetails);
988                    indexUniqueDetails++;
989                }
990                index++;
991            }
992    
993    
994            // Creates locking representation for this global document. The locking is only applicable for assets that are being split.
995            // The assets that are being created do not need to be locked since they don't exist yet.
996            if (valid && getAssetGlobalService().isAssetSeparate(assetGlobal)) {
997                valid &= setAssetLock(maintenanceDocument, assetGlobal);
998            }
999    
1000            return valid && super.processSaveDocument(document);
1001        }
1002    
1003        protected boolean validateAccount(AssetGlobal assetGlobal) {
1004            boolean success = true;
1005            assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.ORGANIZATION_OWNER_ACCOUNT);
1006            Account organizationOwnerAccount = assetGlobal.getOrganizationOwnerAccount();
1007    
1008            boolean skipAccountAvailiablityCheck;
1009            if (StringUtils.isBlank(assetGlobal.getOrganizationOwnerChartOfAccountsCode()) || StringUtils.isBlank(assetGlobal.getOrganizationOwnerAccountNumber())) {
1010                skipAccountAvailiablityCheck = true;
1011            }
1012            else {
1013                skipAccountAvailiablityCheck = isOrgOwnerAccountFromCab(assetGlobal);
1014            }
1015    
1016            // when check if organizationOwnerAccount is existing, use ObjectUtils rather than comparing with 'null' since OJB proxy
1017            // object is used here.
1018            if (!skipAccountAvailiablityCheck && (ObjectUtils.isNull(organizationOwnerAccount) || !organizationOwnerAccount.isActive() || organizationOwnerAccount.isExpired())) {
1019                putFieldError(CamsPropertyConstants.AssetGlobal.ORGANIZATION_OWNER_ACCOUNT_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_OWNER_ACCT_NOT_ACTIVE, new String[] { assetGlobal.getOrganizationOwnerChartOfAccountsCode(), assetGlobal.getOrganizationOwnerAccountNumber() });
1020                success &= false;
1021            }
1022            return success;
1023        }
1024    
1025    
1026        /**
1027         * Check if organization owner account is set from CAB. We honor all accounting lines from CAB are valid payments even thougth
1028         * they are expired.
1029         * 
1030         * @param assetGlobal
1031         * @return
1032         */
1033        protected boolean isOrgOwnerAccountFromCab(AssetGlobal assetGlobal) {
1034            String orgOwnerChartCode = assetGlobal.getOrganizationOwnerChartOfAccountsCode();
1035            String orgOwnerAccountNbr = assetGlobal.getOrganizationOwnerAccountNumber();
1036    
1037            if (StringUtils.isBlank(assetGlobal.getOrganizationOwnerChartOfAccountsCode()) || StringUtils.isBlank(assetGlobal.getOrganizationOwnerAccountNumber())) {
1038                return true;
1039            }
1040    
1041            if (assetGlobal.isCapitalAssetBuilderOriginIndicator()) {
1042                // If CAB submits, allow use of inactive accounting line from the payments
1043                for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
1044                    if (orgOwnerChartCode.equalsIgnoreCase(assetPaymentDetail.getChartOfAccountsCode()) && orgOwnerAccountNbr.equalsIgnoreCase(assetPaymentDetail.getAccountNumber())) {
1045                        return true;
1046                    }
1047                }
1048            }
1049    
1050            return false;
1051        }
1052    
1053        /**
1054         * Validate location
1055         * 
1056         * @param assetGlobal
1057         * @return boolean
1058         */
1059        protected boolean validateLocation(AssetGlobal assetGlobal, AssetGlobalDetail assetGlobalDetail) {
1060            boolean success = true;
1061            if (StringUtils.isBlank(assetGlobal.getInventoryStatusCode())) {
1062                putFieldError(CamsPropertyConstants.AssetGlobal.INVENTORY_STATUS_CODE, CamsKeyConstants.AssetGlobal.ERROR_INVENTORY_STATUS_REQUIRED);
1063                success &= false;
1064            }
1065            success = validateAssetType(assetGlobal);
1066            if (success) {
1067                boolean isCapitalAsset = isCapitalStatus(assetGlobal);
1068                success &= SpringContext.getBean(AssetLocationService.class).validateLocation(LOCATION_FIELD_MAP, assetGlobalDetail, isCapitalAsset, assetGlobal.getCapitalAssetType());
1069            }
1070            else {
1071                putFieldError(CamsPropertyConstants.AssetGlobal.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetGlobal.ERROR_ASSET_LOCATION_DEPENDENCY);
1072            }
1073            return success;
1074        }
1075    
1076        /**
1077         * Validate asset type at the AssetGlobal level. Only checks that there are contents in the object.
1078         * 
1079         * @param assetGlobal
1080         * @return boolean
1081         */
1082        protected boolean validateAssetType(AssetGlobal assetGlobal) {
1083            boolean success = true;
1084            assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.CAPITAL_ASSET_TYPE);
1085            if (ObjectUtils.isNull(assetGlobal.getCapitalAssetType())) {
1086                putFieldError(CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS, CamsKeyConstants.AssetGlobal.ERROR_ASSET_TYPE_REQUIRED);
1087                success &= false;
1088            }
1089    
1090            return success;
1091        }
1092    
1093        /**
1094         * Validate asset type in the AssetGlobalUniqueDetails level, and ensures the value is in the list of valid types.
1095         * The incoming indices are for creation of the errorPath for the global variable message map, which will determine 
1096         * what text field to mark as having a problem. This was written to be called within a loop.
1097         * 
1098         * @param assetGlobalUniqueDetails
1099         * @param sharedIndex the index of the shared details within the AssetGlobal 
1100         * @param uniqueIndex the index of the unique details within the shared details
1101         * @return boolean
1102         */
1103        protected boolean validateAssetType(AssetGlobalDetail assetGlobalUniqueDetails, Integer sharedIndex, Integer uniqueIndex) {
1104            boolean success = true;
1105            int sharedInd = 0;
1106            int uniqueInd = 0;
1107    
1108            // In hindsite, maybe this should have been written to perform the loop-within-loop fully contained within this
1109            // method, and not used within the typical double loop structure of processSaveDocument() and
1110            // processCustomRouteDecumentBusinessRules.
1111    
1112            // don't change the value of the incoming param!
1113            if (sharedIndex != null) {
1114                sharedInd = sharedIndex;
1115            }
1116            if (uniqueIndex != null) {
1117                uniqueInd = uniqueIndex;
1118            }
1119    
1120            if (ObjectUtils.isNull(assetGlobalUniqueDetails)) {
1121                putFieldError(CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS, CamsKeyConstants.AssetGlobal.ERROR_ASSET_LOCATION_DEPENDENCY);
1122                success &= false;
1123            }
1124    
1125            // validate asset type
1126            if (StringUtils.isNotBlank(assetGlobalUniqueDetails.getCapitalAssetTypeCode())) {
1127                AssetType assetType = SpringContext.getBean(BusinessObjectService.class).findBySinglePrimaryKey(AssetType.class, assetGlobalUniqueDetails.getCapitalAssetTypeCode());
1128                if (assetType == null || StringUtils.isBlank(assetType.getCapitalAssetTypeCode())) {
1129                    String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + sharedInd + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS + "[" + uniqueInd + "]";
1130                    GlobalVariables.getMessageMap().addToErrorPath(errorPath);
1131                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetGlobalDetail.CAPITAL_ASSET_TYPE_CODE, CamsKeyConstants.AssetSeparate.ERROR_CAPITAL_ASSET_TYPE_CODE_INVALID, assetGlobalUniqueDetails.getCapitalAssetTypeCode());
1132                    GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
1133                    success &= false;
1134                }
1135            }
1136    
1137            return success;
1138        }
1139    
1140        /**
1141         * Give an error if this asset can't be separated due to mismatching amount on asset and AssetPayment records
1142         * 
1143         * @param assetGlobal
1144         * @return validation success of failure
1145         */
1146        public static boolean validateAssetTotalCostMatchesPaymentTotalCost(AssetGlobal assetGlobal) {
1147            PaymentSummaryService paymentSummaryService = SpringContext.getBean(PaymentSummaryService.class);
1148            assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET);
1149            KualiDecimal assetTotalCost = ObjectUtils.isNull(assetGlobal.getSeparateSourceCapitalAsset().getTotalCostAmount()) ? new KualiDecimal(0) : assetGlobal.getSeparateSourceCapitalAsset().getTotalCostAmount();
1150            KualiDecimal paymentTotalCost = paymentSummaryService.calculatePaymentTotalCost(assetGlobal.getSeparateSourceCapitalAsset());
1151            if (!paymentTotalCost.equals(assetTotalCost)) {
1152                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);
1153    
1154                return false;
1155            }
1156    
1157            return true;
1158        }
1159    
1160        /**
1161         * Give an error if this asset has already been separated
1162         * 
1163         * @param assetGlobal
1164         * @return validation success of failure
1165         */
1166        public static boolean validateAssetAlreadySeparated(Long separateSourceCapitalAssetNumber) {
1167            AssetService assetService = SpringContext.getBean(AssetService.class);
1168    
1169            List<String> documentNumbers = assetService.getDocumentNumbersThatSeparatedThisAsset(separateSourceCapitalAssetNumber);
1170    
1171            if (!documentNumbers.isEmpty()) {
1172                GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_SEPARATE_ASSET_ALREADY_SEPARATED, new String[] { documentNumbers.toString() });
1173    
1174                return false;
1175            }
1176    
1177            return true;
1178        }
1179    
1180        protected ParameterService getParameterService() {
1181            return SpringContext.getBean(ParameterService.class);
1182        }
1183    
1184        protected AssetService getAssetService() {
1185            return SpringContext.getBean(AssetService.class);
1186        }
1187    
1188        protected AssetPaymentService getAssetPaymentService() {
1189            return SpringContext.getBean(AssetPaymentService.class);
1190        }
1191    
1192        protected AssetAcquisitionTypeService getAssetAcquisitionTypeService() {
1193            return SpringContext.getBean(AssetAcquisitionTypeService.class);
1194        }
1195    
1196        protected AssetGlobalService getAssetGlobalService() {
1197            return SpringContext.getBean(AssetGlobalService.class);
1198        }
1199    }