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    
017    package org.kuali.kfs.module.cam.document.validation.impl;
018    
019    import java.util.ArrayList;
020    import java.util.HashSet;
021    import java.util.List;
022    import java.util.Set;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
026    import org.kuali.kfs.module.cam.CamsConstants;
027    import org.kuali.kfs.module.cam.CamsKeyConstants;
028    import org.kuali.kfs.module.cam.CamsPropertyConstants;
029    import org.kuali.kfs.module.cam.CamsConstants.DocumentTypeName;
030    import org.kuali.kfs.module.cam.businessobject.Asset;
031    import org.kuali.kfs.module.cam.businessobject.AssetLocationGlobal;
032    import org.kuali.kfs.module.cam.businessobject.AssetLocationGlobalDetail;
033    import org.kuali.kfs.module.cam.document.service.AssetService;
034    import org.kuali.kfs.sys.context.SpringContext;
035    import org.kuali.kfs.sys.identity.KfsKimAttributes;
036    import org.kuali.rice.kim.bo.types.dto.AttributeSet;
037    import org.kuali.rice.kim.service.IdentityManagementService;
038    import org.kuali.rice.kim.util.KimCommonUtils;
039    import org.kuali.rice.kns.bo.PersistableBusinessObject;
040    import org.kuali.rice.kns.document.MaintenanceDocument;
041    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
042    import org.kuali.rice.kns.util.GlobalVariables;
043    import org.kuali.rice.kns.util.ObjectUtils;
044    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
045    
046    /**
047     * Business rules applicable to AssetLocationGlobal documents.
048     */
049    public class AssetLocationGlobalRule extends MaintenanceDocumentRuleBase {
050    
051        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetLocationGlobalRule.class);
052        protected AssetService assetService = SpringContext.getBean(AssetService.class);
053    
054        /**
055         * Constructs an AssetLocationGlobalRule
056         */
057        public AssetLocationGlobalRule() {
058        }
059    
060        /**
061         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
062         */
063        @Override
064        protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
065            boolean valid = super.processCustomSaveDocumentBusinessRules(document);
066            AssetLocationGlobal assetLocationGlobal = (AssetLocationGlobal) document.getNewMaintainableObject().getBusinessObject();
067            valid &= !getCapitalAssetManagementModuleService().isAssetLocked(retrieveAssetNumberForLocking(assetLocationGlobal), DocumentTypeName.ASSET_LOCATION_GLOBAL, document.getDocumentNumber());
068            return valid;
069        }
070    
071        /**
072         * Processes rules when routing this global.
073         * 
074         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
075         */
076        @Override
077        protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument documentCopy) {
078            boolean success = true;
079    
080            AssetLocationGlobal assetLocationGlobal = (AssetLocationGlobal) documentCopy.getNewMaintainableObject().getBusinessObject();
081            List<AssetLocationGlobalDetail> oldAssetLocationGlobalDetail = assetLocationGlobal.getAssetLocationGlobalDetails();
082    
083            // validate multi add w/red highlight (collection)
084            int index = 0;
085    
086            if (hasAssetLocationGlobalDetails(oldAssetLocationGlobalDetail)) {
087                Set<String> tags = new HashSet<String>();
088                int pos = 0;
089                for (AssetLocationGlobalDetail detail : assetLocationGlobal.getAssetLocationGlobalDetails()) {
090                    String errorPath = MAINTAINABLE_ERROR_PREFIX + CamsPropertyConstants.AssetLocationGlobal.ASSET_LOCATION_GLOBAL_DETAILS + "[" + index + "]";
091                    GlobalVariables.getMessageMap().addToErrorPath(errorPath);
092                    success &= validateActiveCapitalAsset(detail);
093                    success &= validateCampusCode(detail);
094                    success &= validateBuildingCode(detail);
095                    success &= validateBuildingRoomNumber(detail);
096                    success &= validateTagNumber(detail);
097                    success &= validateTagDuplicationWithinDocument(detail, tags);
098                    success &= validateTagDuplication(detail.getCapitalAssetNumber(), detail.getCampusTagNumber());
099                    success &= checkRequiredFieldsAfterAdd(detail);
100                    GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
101                    index++;
102                }
103            }
104            success &= super.processCustomRouteDocumentBusinessRules(documentCopy);
105    
106            KualiWorkflowDocument workflowDoc = documentCopy.getDocumentHeader().getWorkflowDocument();
107            // adding asset locks
108            if (!GlobalVariables.getMessageMap().hasErrors() && (workflowDoc.stateIsInitiated() || workflowDoc.stateIsSaved())) {
109                success &= setAssetLocks(documentCopy);
110            }
111    
112            return success;
113        }
114    
115        /**
116         * retrieve asset numbers need to be locked.
117         * 
118         * @param assetLocationGlobal
119         * @return
120         */
121        protected List<Long> retrieveAssetNumberForLocking(AssetLocationGlobal assetLocationGlobal) {
122            List<Long> capitalAssetNumbers = new ArrayList<Long>();
123            for (AssetLocationGlobalDetail locationDetail : assetLocationGlobal.getAssetLocationGlobalDetails()) {
124                if (locationDetail.getCapitalAssetNumber() != null) {
125                    capitalAssetNumbers.add(locationDetail.getCapitalAssetNumber());
126                }
127            }
128            return capitalAssetNumbers;
129        }
130    
131        /**
132         * Locking for asset numbers.
133         * 
134         * @param documentCopy
135         * @param assetLocationGlobal
136         * @return
137         */
138        private boolean setAssetLocks(MaintenanceDocument document) {
139            AssetLocationGlobal assetLocationGlobal = (AssetLocationGlobal) document.getNewMaintainableObject().getBusinessObject();
140    
141            return this.getCapitalAssetManagementModuleService().storeAssetLocks(retrieveAssetNumberForLocking(assetLocationGlobal), document.getDocumentNumber(), DocumentTypeName.ASSET_LOCATION_GLOBAL, null);
142        }
143    
144        protected CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
145            return SpringContext.getBean(CapitalAssetManagementModuleService.class);
146        }
147    
148        /**
149         * Process rules for any new {@link AssetLocationGlobalDetail} that is added to this global.
150         * 
151         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
152         *      java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject)
153         */
154        @Override
155        public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument documentCopy, String collectionName, PersistableBusinessObject bo) {
156            boolean success = true;
157            AssetLocationGlobal assetLocationGlobal = (AssetLocationGlobal) documentCopy.getNewMaintainableObject().getBusinessObject();
158            Set<String> tags = new HashSet<String>();
159            for (AssetLocationGlobalDetail detail : assetLocationGlobal.getAssetLocationGlobalDetails()) {
160                if (detail.getCampusTagNumber() != null) {
161                    tags.add(detail.getCampusTagNumber());
162                }
163            }
164    
165            AssetLocationGlobalDetail newLineDetail = (AssetLocationGlobalDetail) bo;
166            success = validateActiveCapitalAsset(newLineDetail);
167            if (success) {
168                success &= authorizeCapitalAsset(newLineDetail);
169                success &= validateCampusCode(newLineDetail);
170                success &= validateBuildingCode(newLineDetail);
171                success &= validateBuildingRoomNumber(newLineDetail);
172                success &= validateTagDuplicationWithinDocument(newLineDetail, tags);
173                if (success) {
174                    success &= validateTagDuplication(newLineDetail.getCapitalAssetNumber(), newLineDetail.getCampusTagNumber());
175                }
176            }
177            return success & super.processCustomAddCollectionLineBusinessRules(documentCopy, collectionName, bo);
178        }
179    
180        /**
181         * Asset user authorization.
182         * 
183         * @param assetLocationGlobalDetail
184         * @return boolean
185         */
186        protected boolean authorizeCapitalAsset(AssetLocationGlobalDetail assetLocationGlobalDetail) {
187            boolean success = true;
188    
189            if (ObjectUtils.isNotNull(assetLocationGlobalDetail.getCapitalAssetNumber())) {
190                assetLocationGlobalDetail.refreshReferenceObject(CamsPropertyConstants.AssetLocationGlobalDetail.ASSET);
191                assetLocationGlobalDetail.getAsset().refreshReferenceObject(CamsPropertyConstants.Asset.ORGANIZATION_OWNER_ACCOUNT);
192    
193                AttributeSet qualification = new AttributeSet();
194                qualification.put(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE, assetLocationGlobalDetail.getAsset().getOrganizationOwnerChartOfAccountsCode());
195                qualification.put(KfsKimAttributes.ORGANIZATION_CODE, assetLocationGlobalDetail.getAsset().getOrganizationOwnerAccount().getOrganizationCode());
196                AttributeSet permissionDetails = new AttributeSet();
197                permissionDetails.putAll(KimCommonUtils.getNamespaceAndComponentSimpleName(Asset.class));
198                if (!SpringContext.getBean(IdentityManagementService.class).isAuthorized(GlobalVariables.getUserSession().getPerson().getPrincipalId(), CamsConstants.CAM_MODULE_CODE, CamsConstants.PermissionNames.MAINTAIN_ASSET_LOCATION, permissionDetails, qualification)) {
199                    success &= false;
200                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAPITAL_ASSET_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_ASSET_AUTHORIZATION, new String[] { GlobalVariables.getUserSession().getPerson().getPrincipalName(), assetLocationGlobalDetail.getCapitalAssetNumber().toString() });
201                }
202            }
203    
204            return success;
205        }
206    
207        /**
208         * Validate if any {@link AssetLocationGlobalDetail} exist.
209         * 
210         * @param assetLocationGlobal
211         * @return boolean
212         */
213        protected boolean hasAssetLocationGlobalDetails(List<AssetLocationGlobalDetail> assetLocationGlobalDetails) {
214            boolean success = true;
215    
216            if (assetLocationGlobalDetails.size() == 0) {
217                success = false;
218                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAPITAL_ASSET_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_ASSET_LOCATION_GLOBAL_NO_ASSET_DETAIL);
219            }
220    
221            return success;
222        }
223    
224        /**
225         * Validate the capital {@link Asset}. This method also calls {@link AssetService} while validating retired {@link Asset}.
226         * 
227         * @param assetLocationGlobalDetail
228         * @return boolean
229         */
230        protected boolean validateActiveCapitalAsset(AssetLocationGlobalDetail assetLocationGlobalDetail) {
231            boolean success = true;
232    
233            if (ObjectUtils.isNotNull(assetLocationGlobalDetail.getCapitalAssetNumber())) {
234                assetLocationGlobalDetail.refreshReferenceObject(CamsPropertyConstants.AssetLocationGlobalDetail.ASSET);
235    
236                if (ObjectUtils.isNull(assetLocationGlobalDetail.getAsset())) {
237                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAPITAL_ASSET_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_INVALID_CAPITAL_ASSET_NUMBER, new String[] { assetLocationGlobalDetail.getCapitalAssetNumber().toString() });
238                    success = false;
239                }
240                else if (assetService.isAssetRetired(assetLocationGlobalDetail.getAsset())) {
241                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAPITAL_ASSET_NUMBER, CamsKeyConstants.Retirement.ERROR_NON_ACTIVE_ASSET_RETIREMENT, new String[] { assetLocationGlobalDetail.getCapitalAssetNumber().toString() });
242                    success = false;
243                }
244            }
245    
246            return success;
247        }
248    
249        /**
250         * Validate {@link Campus} code.
251         * 
252         * @param assetLocationGlobalDetail
253         * @return boolean
254         */
255        protected boolean validateCampusCode(AssetLocationGlobalDetail assetLocationGlobalDetail) {
256            boolean success = true;
257    
258            if (StringUtils.isNotBlank(assetLocationGlobalDetail.getCampusCode())) {
259                // assetLocationGlobalDetail.refreshReferenceObject("campus");
260    
261                if (ObjectUtils.isNull(assetLocationGlobalDetail.getCampus())) {
262                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAMPUS_CODE, CamsKeyConstants.AssetLocationGlobal.ERROR_INVALID_CAMPUS_CODE, new String[] { assetLocationGlobalDetail.getCampusCode(), assetLocationGlobalDetail.getCapitalAssetNumber().toString() });
263                    success = false;
264                }
265            }
266    
267            return success;
268        }
269    
270        /**
271         * Validate {@link Building} code.
272         * 
273         * @param assetLocationGlobalDetail
274         * @return boolean
275         */
276        protected boolean validateBuildingCode(AssetLocationGlobalDetail assetLocationGlobalDetail) {
277            boolean success = true;
278    
279            if (StringUtils.isNotBlank(assetLocationGlobalDetail.getCampusCode()) && StringUtils.isNotBlank(assetLocationGlobalDetail.getBuildingCode())) {
280                assetLocationGlobalDetail.refreshReferenceObject("building");
281    
282                if (ObjectUtils.isNull(assetLocationGlobalDetail.getBuilding())) {
283                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.BUILDING_CODE, CamsKeyConstants.AssetLocationGlobal.ERROR_INVALID_BUILDING_CODE, new String[] { assetLocationGlobalDetail.getBuildingCode(), assetLocationGlobalDetail.getCampusCode() });
284                    success = false;
285                }
286            }
287    
288            return success;
289        }
290    
291        /**
292         * Validate building {@link Room} number.
293         * 
294         * @param assetLocationGlobalDetail
295         * @return boolean
296         */
297        protected boolean validateBuildingRoomNumber(AssetLocationGlobalDetail assetLocationGlobalDetail) {
298            boolean success = true;
299    
300            if (StringUtils.isNotBlank(assetLocationGlobalDetail.getBuildingRoomNumber())) {
301                assetLocationGlobalDetail.refreshReferenceObject("buildingRoom");
302    
303                if (ObjectUtils.isNull(assetLocationGlobalDetail.getBuildingRoom())) {
304                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.BUILDING_ROOM_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_INVALID_ROOM_NUMBER, new String[] { assetLocationGlobalDetail.getBuildingCode(), assetLocationGlobalDetail.getBuildingRoomNumber(), assetLocationGlobalDetail.getCampusCode() });
305                    success = false;
306                }
307            }
308    
309            return success;
310        }
311    
312        /**
313         * Validate tag number.
314         * 
315         * @param assetLocationGlobalDetail
316         * @return boolean
317         */
318        protected boolean validateTagNumber(AssetLocationGlobalDetail assetLocationGlobalDetail) {
319            boolean success = true;
320    
321            if (StringUtils.isBlank(assetLocationGlobalDetail.getCampusTagNumber())) {
322                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_TAG_NUMBER_REQUIRED);
323                success = false;
324            }
325    
326            return success;
327        }
328    
329        /**
330         * Validate duplicate tag number. This method also calls {@link AssetService}.
331         * 
332         * @param assetLocationGlobalDetail
333         * @return boolean
334         */
335        protected boolean validateTagDuplication(Long capitalAssetNumber, String campusTagNumber) {
336            boolean success = true;
337            if (capitalAssetNumber != null && ObjectUtils.isNotNull(campusTagNumber) && !CamsConstants.Asset.NON_TAGGABLE_ASSET.equalsIgnoreCase(campusTagNumber)) {
338                // call AssetService, get Assets from doc, gather all assets matching this tag number
339                List<Asset> activeAssetsMatchingTagNumber = assetService.findActiveAssetsMatchingTagNumber(campusTagNumber);
340                for (Asset asset : activeAssetsMatchingTagNumber) {
341                    // compare asset numbers
342                    if (!asset.getCapitalAssetNumber().equals(capitalAssetNumber)) {
343                        GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_DUPLICATE_TAG_NUMBER_FOUND, new String[] { campusTagNumber, String.valueOf(asset.getCapitalAssetNumber()), capitalAssetNumber.toString() });
344                        success = false;
345                        break;
346                    }
347                }
348            }
349    
350            return success;
351        }
352    
353        protected boolean validateTagDuplicationWithinDocument(AssetLocationGlobalDetail assetLocationGlobalDetail, Set<String> tagsList) {
354            boolean success = true;
355            Long capitalAssetNumber = assetLocationGlobalDetail.getCapitalAssetNumber();
356            String campusTagNumber = assetLocationGlobalDetail.getCampusTagNumber();
357            if (capitalAssetNumber != null && ObjectUtils.isNotNull(campusTagNumber) && !CamsConstants.Asset.NON_TAGGABLE_ASSET.equalsIgnoreCase(campusTagNumber)) {
358                // if duplicate within list
359                if (!tagsList.add(campusTagNumber)) {
360                    success &= false;
361                    GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAMPUS_TAG_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_DUPLICATE_TAG_NUMBER_WITHIN_DOCUMENT, new String[] { campusTagNumber, capitalAssetNumber.toString() });
362                }
363            }
364            return success;
365        }
366    
367        /**
368         * Check required fields after a new asset location has been added.
369         * 
370         * @param assetLocationGlobalDetail
371         * @return boolean
372         */
373        protected boolean checkRequiredFieldsAfterAdd(AssetLocationGlobalDetail assetLocationGlobalDetail) {
374            boolean success = true;
375    
376            if (StringUtils.isBlank(assetLocationGlobalDetail.getCampusCode())) {
377                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.CAMPUS_CODE, CamsKeyConstants.AssetLocationGlobal.ERROR_CAMPUS_CODE_REQUIRED);
378                success = false;
379            }
380    
381            if (StringUtils.isBlank(assetLocationGlobalDetail.getBuildingCode())) {
382                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.BUILDING_CODE, CamsKeyConstants.AssetLocationGlobal.ERROR_BUILDING_CODE_REQUIRED);
383                success = false;
384            }
385    
386            if (StringUtils.isBlank(assetLocationGlobalDetail.getBuildingRoomNumber())) {
387                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.AssetLocationGlobal.BUILDING_ROOM_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_ROOM_NUMBER_REQUIRED);
388                success = false;
389            }
390    
391            return success;
392        }
393    }