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.cab.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Map;
026    
027    import org.apache.commons.lang.ArrayUtils;
028    import org.apache.commons.lang.StringUtils;
029    import org.apache.log4j.Logger;
030    import org.kuali.kfs.coa.businessobject.ObjectCode;
031    import org.kuali.kfs.fp.businessobject.CapitalAssetInformation;
032    import org.kuali.kfs.fp.businessobject.CapitalAssetInformationDetail;
033    import org.kuali.kfs.fp.document.AdvanceDepositDocument;
034    import org.kuali.kfs.fp.document.CashReceiptDocument;
035    import org.kuali.kfs.fp.document.CreditCardReceiptDocument;
036    import org.kuali.kfs.fp.document.DistributionOfIncomeAndExpenseDocument;
037    import org.kuali.kfs.fp.document.GeneralErrorCorrectionDocument;
038    import org.kuali.kfs.fp.document.InternalBillingDocument;
039    import org.kuali.kfs.fp.document.ProcurementCardDocument;
040    import org.kuali.kfs.fp.document.ServiceBillingDocument;
041    import org.kuali.kfs.fp.document.YearEndDistributionOfIncomeAndExpenseDocument;
042    import org.kuali.kfs.fp.document.YearEndGeneralErrorCorrectionDocument;
043    import org.kuali.kfs.integration.cab.CapitalAssetBuilderAssetTransactionType;
044    import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
045    import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
046    import org.kuali.kfs.integration.purap.CapitalAssetLocation;
047    import org.kuali.kfs.integration.purap.CapitalAssetSystem;
048    import org.kuali.kfs.integration.purap.ExternalPurApItem;
049    import org.kuali.kfs.integration.purap.ItemCapitalAsset;
050    import org.kuali.kfs.integration.purap.PurchasingAccountsPayableModuleService;
051    import org.kuali.kfs.module.cab.CabConstants;
052    import org.kuali.kfs.module.cab.CabKeyConstants;
053    import org.kuali.kfs.module.cab.CabParameterConstants;
054    import org.kuali.kfs.module.cab.CabPropertyConstants;
055    import org.kuali.kfs.module.cab.businessobject.AssetTransactionType;
056    import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry;
057    import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntryAsset;
058    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument;
059    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset;
060    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount;
061    import org.kuali.kfs.module.cab.document.service.PurApInfoService;
062    import org.kuali.kfs.module.cam.CamsConstants;
063    import org.kuali.kfs.module.cam.CamsKeyConstants;
064    import org.kuali.kfs.module.cam.CamsPropertyConstants;
065    import org.kuali.kfs.module.cam.CamsConstants.DocumentTypeName;
066    import org.kuali.kfs.module.cam.businessobject.Asset;
067    import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
068    import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail;
069    import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail;
070    import org.kuali.kfs.module.cam.businessobject.AssetType;
071    import org.kuali.kfs.module.cam.document.service.AssetService;
072    import org.kuali.kfs.module.purap.PurapConstants;
073    import org.kuali.kfs.module.purap.PurapKeyConstants;
074    import org.kuali.kfs.module.purap.PurapParameterConstants;
075    import org.kuali.kfs.module.purap.PurapPropertyConstants;
076    import org.kuali.kfs.module.purap.businessobject.AccountsPayableItem;
077    import org.kuali.kfs.module.purap.businessobject.AvailabilityMatrix;
078    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
079    import org.kuali.kfs.module.purap.businessobject.PurApItem;
080    import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
081    import org.kuali.kfs.module.purap.businessobject.PurchasingItem;
082    import org.kuali.kfs.module.purap.businessobject.PurchasingItemBase;
083    import org.kuali.kfs.module.purap.businessobject.PurchasingItemCapitalAssetBase;
084    import org.kuali.kfs.module.purap.document.AccountsPayableDocument;
085    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
086    import org.kuali.kfs.module.purap.document.PurchasingDocument;
087    import org.kuali.kfs.module.purap.document.RequisitionDocument;
088    import org.kuali.kfs.sys.KFSConstants;
089    import org.kuali.kfs.sys.KFSKeyConstants;
090    import org.kuali.kfs.sys.KFSPropertyConstants;
091    import org.kuali.kfs.sys.businessobject.AccountingLine;
092    import org.kuali.kfs.sys.businessobject.Building;
093    import org.kuali.kfs.sys.businessobject.Room;
094    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
095    import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
096    import org.kuali.kfs.sys.context.SpringContext;
097    import org.kuali.kfs.sys.document.AccountingDocument;
098    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
099    import org.kuali.rice.kns.bo.Campus;
100    import org.kuali.rice.kns.bo.DocumentHeader;
101    import org.kuali.rice.kns.bo.Parameter;
102    import org.kuali.rice.kns.datadictionary.AttributeDefinition;
103    import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
104    import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
105    import org.kuali.rice.kns.service.BusinessObjectService;
106    import org.kuali.rice.kns.service.DataDictionaryService;
107    import org.kuali.rice.kns.service.DictionaryValidationService;
108    import org.kuali.rice.kns.service.KualiConfigurationService;
109    import org.kuali.rice.kns.service.KualiModuleService;
110    import org.kuali.rice.kns.service.ParameterService;
111    import org.kuali.rice.kns.util.GlobalVariables;
112    import org.kuali.rice.kns.util.KualiDecimal;
113    import org.kuali.rice.kns.util.ObjectUtils;
114    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
115    
116    public class CapitalAssetBuilderModuleServiceImpl implements CapitalAssetBuilderModuleService {
117        private static Logger LOG = Logger.getLogger(CapitalAssetBuilderModuleService.class);
118    
119        protected static enum AccountCapitalObjectCode {
120            BOTH_NONCAP {
121                boolean validateAssetInfoAllowed(AccountingDocument accountingDocument, boolean isNewAssetBlank, boolean isUpdateAssetBlank) {
122                    boolean valid = true;
123                    // non capital object code, disallow the capital information entered.
124                    if (!isNewAssetBlank || !isUpdateAssetBlank) {
125                        // give error if data was entered
126                        GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, CabKeyConstants.CapitalAssetInformation.ERROR_ASSET_DO_NOT_ENTER_ANY_DATA);
127                        valid = false;
128                    }
129    
130                    return valid;
131                }
132    
133                boolean isDocumentTypeRestricted(AccountingDocument accountingDocument) {
134                    return false;
135                }
136            },
137            FROM_CAPITAL_TO_NONCAP {
138                boolean validateAssetInfoAllowed(AccountingDocument accountingDocument, boolean isNewAssetBlank, boolean isUpdateAssetBlank) {
139                    boolean valid = validateAssetInfoEntered(isNewAssetBlank, isUpdateAssetBlank);
140                    if (valid) {
141                        if (isDocumentTypeRestricted(accountingDocument)) {
142                            valid &= validateOnlyOneAssetInfoEntered(isNewAssetBlank, isUpdateAssetBlank);
143                        }
144                        // Capital on the FROM side and non-capital on the TO side, we only allow modify an asset.
145                        else if (!isNewAssetBlank || isUpdateAssetBlank) {
146                            GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, CabKeyConstants.CapitalAssetInformation.ERROR_ASSET_UPDATE_ALLOW_ONLY);
147                            valid = false;
148                        }
149                    }
150                    return valid;
151                }
152    
153                /**
154                 * For Internal Billing, when credit capital on Income line, allow modify asset or create new asset.
155                 * 
156                 * @see org.kuali.kfs.module.cab.service.impl.CapitalAssetBuilderModuleServiceImpl.AccountCapitalObjectCode#isDocumentTypeRestricted(org.kuali.kfs.sys.document.AccountingDocument)
157                 */
158                boolean isDocumentTypeRestricted(AccountingDocument accountingDocument) {
159                    if (accountingDocument instanceof InternalBillingDocument) {
160                        return true;
161                    }
162                    return false;
163                }
164            },
165            FROM_NONCAP_TO_CAPITAL {
166                boolean validateAssetInfoAllowed(AccountingDocument accountingDocument, boolean isNewAssetBlank, boolean isUpdateAssetBlank) {
167                    boolean valid = validateAssetInfoEntered(isNewAssetBlank, isUpdateAssetBlank);
168                    if (valid && isDocumentTypeRestricted(accountingDocument)) {
169                        valid &= validateOnlyOneAssetInfoEntered(isNewAssetBlank, isUpdateAssetBlank);
170                    }
171                    return valid;
172                }
173    
174                /**
175                 * The document is restricted for all FP doc types
176                 * 
177                 * @see org.kuali.kfs.module.cab.service.impl.CapitalAssetBuilderModuleServiceImpl.AccountCapitalObjectCode#isDocumentTypeRestricted(org.kuali.kfs.sys.document.AccountingDocument)
178                 */
179                boolean isDocumentTypeRestricted(AccountingDocument accountingDocument) {
180                    return true;
181                }
182            },
183            BOTH_CAPITAL {
184                boolean validateAssetInfoAllowed(AccountingDocument accountingDocument, boolean isNewAssetBlank, boolean isUpdateAssetBlank) {
185                    return validateAssetInfoEntered(isNewAssetBlank, isUpdateAssetBlank) && validateOnlyOneAssetInfoEntered(isNewAssetBlank, isUpdateAssetBlank);
186                }
187    
188                boolean isDocumentTypeRestricted(AccountingDocument accountingDocument) {
189                    return false;
190                }
191            };
192    
193            /**
194             * Validate Asset Information is not blank.
195             * 
196             * @param isNewAssetBlank
197             * @param isUpdateAssetBlank
198             * @return
199             */
200            protected static boolean validateAssetInfoEntered(boolean isNewAssetBlank, boolean isUpdateAssetBlank) {
201                boolean valid = true;
202                // can modify existing or create new. Required to enter one of each type.
203                if (isNewAssetBlank && isUpdateAssetBlank) {
204                    GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, CabKeyConstants.CapitalAssetInformation.ERROR_ASSET_REQUIRE_DATA_ENTRY);
205                    valid = false;
206                }
207                return valid;
208            }
209    
210            /**
211             * Validate only either one asset information entered.
212             * 
213             * @param isNewAssetBlank
214             * @param isUpdateAssetBlank
215             * @return
216             */
217            protected static boolean validateOnlyOneAssetInfoEntered(boolean isNewAssetBlank, boolean isUpdateAssetBlank) {
218                boolean valid = true;
219                if (!isNewAssetBlank && !isUpdateAssetBlank) {
220                    // Data exists on both crate new asset and update asset, give error
221                    GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, CabKeyConstants.CapitalAssetInformation.ERROR_ASSET_NEW_OR_UPDATE_ONLY);
222                    valid = false;
223                }
224                return valid;
225            }
226    
227            abstract boolean validateAssetInfoAllowed(AccountingDocument accoutingDocument, boolean isNewAssetBlank, boolean isUpdateAssetBlank);
228    
229            abstract boolean isDocumentTypeRestricted(AccountingDocument accountingDocument);
230        }
231    
232        /**
233         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#getAllAssetTransactionTypes()
234         */
235        public List<CapitalAssetBuilderAssetTransactionType> getAllAssetTransactionTypes() {
236            Class<? extends CapitalAssetBuilderAssetTransactionType> assetTransactionTypeClass = this.getKualiModuleService().getResponsibleModuleService(CapitalAssetBuilderAssetTransactionType.class).getExternalizableBusinessObjectImplementation(CapitalAssetBuilderAssetTransactionType.class);
237            Map<String, Object> searchKeys = new HashMap<String, Object>();
238            searchKeys.put("active", "Y");
239            return (List<CapitalAssetBuilderAssetTransactionType>) this.getBusinessObjectService().findMatching(assetTransactionTypeClass, searchKeys);
240        }
241    
242        /**
243         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#validatePurchasingAccountsPayableData(org.kuali.kfs.sys.document.AccountingDocument)
244         */
245        public boolean validatePurchasingData(AccountingDocument accountingDocument) {
246            Boolean valid = true;
247            PurchasingDocument purchasingDocument = (PurchasingDocument) accountingDocument;
248            String systemTypeCode = purchasingDocument.getCapitalAssetSystemTypeCode();
249            String capitalAssetSystemStateCode = purchasingDocument.getCapitalAssetSystemStateCode();
250            String documentType = (purchasingDocument instanceof RequisitionDocument) ? "REQUISITION" : "PURCHASE_ORDER";
251            for (PurApItem item : purchasingDocument.getItems()) {
252                List accountingLines = item.getSourceAccountingLines();
253                for (Iterator iterator = accountingLines.iterator(); iterator.hasNext();) {
254                    PurApAccountingLine accountingLine = (PurApAccountingLine) iterator.next();
255                    String coa = accountingLine.getChartOfAccountsCode();
256                     if (PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL.equals(systemTypeCode)) {
257                        valid &= validateIndividualCapitalAssetSystemFromPurchasing(capitalAssetSystemStateCode, purchasingDocument.getPurchasingCapitalAssetItems(), coa, documentType);
258                    }
259                    else if (PurapConstants.CapitalAssetSystemTypes.ONE_SYSTEM.equals(systemTypeCode)) {
260                        valid &=  validateOneSystemCapitalAssetSystemFromPurchasing(capitalAssetSystemStateCode, purchasingDocument.getPurchasingCapitalAssetSystems(), purchasingDocument.getPurchasingCapitalAssetItems(), coa, documentType);
261                    }
262                    else if (PurapConstants.CapitalAssetSystemTypes.MULTIPLE.equals(systemTypeCode)) {
263                        valid &= validateMultipleSystemsCapitalAssetSystemFromPurchasing(capitalAssetSystemStateCode, purchasingDocument.getPurchasingCapitalAssetSystems(), purchasingDocument.getPurchasingCapitalAssetItems(), coa, documentType);
264                    }
265                }
266            }
267            return valid;
268        }
269    
270        public boolean validateAccountsPayableData(AccountingDocument accountingDocument) {
271            AccountsPayableDocument apDocument = (AccountsPayableDocument) accountingDocument;
272            boolean valid = true;
273            for (PurApItem purApItem : apDocument.getItems()) {
274                AccountsPayableItem accountsPayableItem = (AccountsPayableItem) purApItem;
275                // only run on ap items that were line items (not additional charge items) and were cams items
276                if ((!accountsPayableItem.getItemType().isAdditionalChargeIndicator()) && StringUtils.isNotEmpty(accountsPayableItem.getCapitalAssetTransactionTypeCode())) {
277                    valid &= validateAccountsPayableItem(accountsPayableItem);
278                }
279            }
280            return valid;
281        }
282    
283        /**
284         * Perform the item level capital asset validation to determine if the given document is not allowed to become an Automatic
285         * Purchase Order (APO). The APO is not allowed if any accounting strings on the document are using an object level indicated as
286         * capital via a parameter setting.
287         */
288        public boolean doesAccountingLineFailAutomaticPurchaseOrderRules(AccountingLine accountingLine) {
289            PurApAccountingLine purapAccountingLine = (PurApAccountingLine) accountingLine;
290            purapAccountingLine.refreshNonUpdateableReferences();
291            return getParameterService().getParameterEvaluator(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, CabParameterConstants.CapitalAsset.CAPITAL_ASSET_OBJECT_LEVELS, purapAccountingLine.getObjectCode().getFinancialObjectLevelCode()).evaluationSucceeds();
292        }
293    
294        /**
295         * Perform the document level capital asset validation to determine if the given document is not allowed to become an Automatic
296         * Purchase Order (APO). The APO is not allowed if any capital asset items exist on the document.
297         */
298        public boolean doesDocumentFailAutomaticPurchaseOrderRules(AccountingDocument accountingDocument) {
299            PurchasingDocument purchasingDocument = (PurchasingDocument) accountingDocument;
300            return ObjectUtils.isNotNull(purchasingDocument.getPurchasingCapitalAssetItems()) && !purchasingDocument.getPurchasingCapitalAssetItems().isEmpty();
301        }
302    
303        public boolean validateAutomaticPurchaseOrderRule(AccountingDocument accountingDocument) {
304            PurchasingDocument purchasingDocument = (PurchasingDocument) accountingDocument;
305            for (PurApItem item : purchasingDocument.getItems()) {
306                if (doesItemNeedCapitalAsset(item.getItemTypeCode(), item.getSourceAccountingLines())) {
307                    // if the item needs capital asset, we cannot have an APO, so return false.
308                    return false;
309                }
310            }
311            return true;
312        }
313    
314        /**
315         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#doesItemNeedCapitalAsset(java.lang.String, java.util.List)
316         */
317        public boolean doesItemNeedCapitalAsset(String itemTypeCode, List accountingLines) {
318            if (PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE.equals(itemTypeCode)) {
319                // FIXME: Chris - this should be true but need to look to see where itemline number is referenced first
320                // return true;
321                return false;
322            }// else
323            for (Iterator iterator = accountingLines.iterator(); iterator.hasNext();) {
324                PurApAccountingLine accountingLine = (PurApAccountingLine) iterator.next();
325                accountingLine.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE);
326                if (ObjectUtils.isNotNull(accountingLine.getObjectCode()) && isCapitalAssetObjectCode(accountingLine.getObjectCode())) {
327                    return true;
328                }
329            }
330    
331            return false;
332        }
333    
334        /**
335         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#validateUpdateCAMSView(org.kuali.kfs.sys.document.AccountingDocument)
336         */
337        public boolean validateUpdateCAMSView(AccountingDocument accountingDocument) {
338            PurchasingDocument purchasingDocument = (PurchasingDocument) accountingDocument;
339            boolean valid = true;
340            for (PurApItem purapItem : purchasingDocument.getItems()) {
341                if (purapItem.getItemType().isLineItemIndicator()) {
342                    if (!doesItemNeedCapitalAsset(purapItem.getItemTypeCode(), purapItem.getSourceAccountingLines())) {
343                        PurchasingCapitalAssetItem camsItem = ((PurchasingItem) purapItem).getPurchasingCapitalAssetItem();
344                        if (camsItem != null && !camsItem.isEmpty()) {
345                            valid = false;
346                            GlobalVariables.getMessageMap().putError("newPurchasingItemCapitalAssetLine", PurapKeyConstants.ERROR_CAPITAL_ASSET_ITEM_NOT_CAMS_ELIGIBLE, "in line item # " + purapItem.getItemLineNumber());
347                        }
348                    }
349                }
350            }
351            return valid;
352        }
353    
354        /**
355         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#validateAddItemCapitalAssetBusinessRules(org.kuali.kfs.integration.purap.ItemCapitalAsset)
356         */
357        public boolean validateAddItemCapitalAssetBusinessRules(ItemCapitalAsset asset) {
358            boolean valid = true;
359            if (asset.getCapitalAssetNumber() == null) {
360                valid = false;
361            }
362            else {
363                valid = SpringContext.getBean(DictionaryValidationService.class).isBusinessObjectValid((PurchasingItemCapitalAssetBase) asset);
364            }
365            if (!valid) {
366                String propertyName = "newPurchasingItemCapitalAssetLine." + PurapPropertyConstants.CAPITAL_ASSET_NUMBER;
367                String errorKey = PurapKeyConstants.ERROR_CAPITAL_ASSET_ASSET_NUMBER_MUST_BE_LONG_NOT_NULL;
368                GlobalVariables.getMessageMap().putError(propertyName, errorKey);
369            }else{        
370                Map<String, String> params = new HashMap<String, String>();
371                params.put(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, asset.getCapitalAssetNumber().toString());
372                Asset retrievedAsset = (Asset) this.getBusinessObjectService().findByPrimaryKey(Asset.class, params);
373    
374                if (ObjectUtils.isNull(retrievedAsset)) {
375                    valid = false;
376                    String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, KFSPropertyConstants.CAPITAL_ASSET_NUMBER);
377                    GlobalVariables.getMessageMap().putError("newPurchasingItemCapitalAssetLine." + KFSPropertyConstants.CAPITAL_ASSET_NUMBER, KFSKeyConstants.ERROR_EXISTENCE, label);
378                }
379    
380            }
381            return valid;
382        }
383    
384        /**
385         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#warningObjectLevelCapital(org.kuali.kfs.sys.document.AccountingDocument)
386         */
387        public boolean warningObjectLevelCapital(AccountingDocument accountingDocument) {
388            org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument purapDocument = (org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) accountingDocument;
389            for (PurApItem item : purapDocument.getItems()) {
390                if (item.getItemType().isLineItemIndicator() && item.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
391                    List<PurApAccountingLine> accounts = item.getSourceAccountingLines();
392                    BigDecimal unitPrice = item.getItemUnitPrice();
393                    String itemIdentifier = item.getItemIdentifierString();
394                    for (PurApAccountingLine account : accounts) {
395                        ObjectCode objectCode = account.getObjectCode();
396                        if (!validateLevelCapitalAssetIndication(unitPrice, objectCode, itemIdentifier)) {
397                            // found an error
398                            return false;
399                        }
400                    }
401                }
402            }
403            // no need for error
404            return true;
405        }
406    
407    
408        /**
409         * Validates the capital asset field requirements based on system parameter and chart for individual system type. This also
410         * calls validations for quantity on locations equal quantity on line items, validates that the transaction type allows asset
411         * number and validates the non quantity driven allowed indicator.
412         * 
413         * @param systemState
414         * @param capitalAssetItems
415         * @param chartCode
416         * @param documentType
417         * @return
418         */
419        protected boolean validateIndividualCapitalAssetSystemFromPurchasing(String systemState, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
420            // For Individual Asset system type, the List of CapitalAssetSystems in the input parameter for
421            // validateAllFieldRequirementsByChart
422            // should be null. So we'll pass in a null here.
423            boolean valid = validateAllFieldRequirementsByChart(systemState, null, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL);
424            valid &= validateQuantityOnLocationsEqualsQuantityOnItem(capitalAssetItems, PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL, systemState);
425            valid &= validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers(capitalAssetItems);
426            valid &= validateNonQuantityDrivenAllowedIndicatorAndTradeIn(capitalAssetItems);
427            return valid;
428        }
429    
430        /**
431         * Validates the capital asset field requirements based on system parameter and chart for one system type. This also calls
432         * validations that the transaction type allows asset number and validates the non quantity driven allowed indicator.
433         * 
434         * @param systemState
435         * @param capitalAssetSystems
436         * @param capitalAssetItems
437         * @param chartCode
438         * @param documentType
439         * @return
440         */
441        protected boolean validateOneSystemCapitalAssetSystemFromPurchasing(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
442            boolean valid = validateAllFieldRequirementsByChart(systemState, capitalAssetSystems, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.ONE_SYSTEM);
443            String capitalAssetTransactionType = capitalAssetItems.get(0).getCapitalAssetTransactionTypeCode();
444            String prefix = "document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_SYSTEMS + "[0].";
445            valid &= validatePurchasingTransactionTypesAllowingAssetNumbers(capitalAssetSystems.get(0), capitalAssetTransactionType, prefix);
446            valid &= validateNonQuantityDrivenAllowedIndicatorAndTradeIn(capitalAssetItems);
447            return valid;
448        }
449    
450        /**
451         * Validates the capital asset field requirements based on system parameter and chart for multiple system type. This also calls
452         * validations that the transaction type allows asset number and validates the non quantity driven allowed indicator.
453         * 
454         * @param systemState
455         * @param capitalAssetSystems
456         * @param capitalAssetItems
457         * @param chartCode
458         * @param documentType
459         * @return
460         */
461        protected boolean validateMultipleSystemsCapitalAssetSystemFromPurchasing(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType) {
462            boolean valid = validateAllFieldRequirementsByChart(systemState, capitalAssetSystems, capitalAssetItems, chartCode, documentType, PurapConstants.CapitalAssetSystemTypes.MULTIPLE);
463            String capitalAssetTransactionType = capitalAssetItems.get(0).getCapitalAssetTransactionTypeCode();
464            String prefix = "document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_SYSTEMS + "[0].";
465            valid &= validatePurchasingTransactionTypesAllowingAssetNumbers(capitalAssetSystems.get(0), capitalAssetTransactionType, prefix);
466            valid &= validateNonQuantityDrivenAllowedIndicatorAndTradeIn(capitalAssetItems);
467            return valid;
468        }
469    
470        /**
471         * Validates all the field requirements by chart. It obtains a List of parameters where the parameter names are like
472         * "CHARTS_REQUIRING%" then loop through these parameters. If the system type is individual then invoke the
473         * validateFieldRequirementByChartForIndividualSystemType for further validation, otherwise invoke the
474         * validateFieldRequirementByChartForOneOrMultipleSystemType for further validation.
475         * 
476         * @param systemState
477         * @param capitalAssetSystems
478         * @param capitalAssetItems
479         * @param chartCode
480         * @param documentType
481         * @param systemType
482         * @return
483         */
484        protected boolean validateAllFieldRequirementsByChart(String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String documentType, String systemType) {
485            boolean valid = true;
486            List<Parameter> results = new ArrayList<Parameter>();
487            Map<String, String> criteria = new HashMap<String, String>();
488            criteria.put(CabPropertyConstants.Parameter.PARAMETER_NAMESPACE_CODE, CabConstants.Parameters.NAMESPACE);
489            criteria.put(CabPropertyConstants.Parameter.PARAMETER_DETAIL_TYPE_CODE, CabConstants.Parameters.DETAIL_TYPE_DOCUMENT);
490            criteria.put(CabPropertyConstants.Parameter.PARAMETER_NAME, "CHARTS_REQUIRING%" + documentType);
491            results.addAll(SpringContext.getBean(ParameterService.class).retrieveParametersGivenLookupCriteria(criteria));
492            for (Parameter parameter : results) {
493                if (ObjectUtils.isNotNull(parameter)) {
494                    if (systemType.equals(PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL)) {
495                        valid &= validateFieldRequirementByChartForIndividualSystemType(systemState, capitalAssetItems, chartCode, parameter.getParameterName(), parameter.getParameterValue());
496                    }
497                    else {
498                        valid &= validateFieldRequirementByChartForOneOrMultipleSystemType(systemType, systemState, capitalAssetSystems, capitalAssetItems, chartCode, parameter.getParameterName(), parameter.getParameterValue());
499                    }
500                }
501            }
502            return valid;
503        }
504    
505        /**
506         * Validates all the field requirements by chart. It obtains a List of parameters where the parameter names are like
507         * "CHARTS_REQUIRING%" then loop through these parameters. If any of the parameter's values is null, then return false
508         * 
509         * @param accountingDocument
510         * @return
511         */
512        public boolean validateAllFieldRequirementsByChart(AccountingDocument accountingDocument) {
513            PurchasingDocument purchasingDocument = (PurchasingDocument) accountingDocument;
514            
515            String documentType = (purchasingDocument instanceof RequisitionDocument) ? "REQUISITION" : "PURCHASE_ORDER";
516            boolean valid = true;
517     
518            for (PurApItem item : purchasingDocument.getItems()) {
519                String itemTypeCode = item.getItemTypeCode();
520                List accountingLines = item.getSourceAccountingLines();
521                for (Iterator iterator = accountingLines.iterator(); iterator.hasNext();) {
522                    PurApAccountingLine accountingLine = (PurApAccountingLine) iterator.next();
523                    String coa = accountingLine.getChartOfAccountsCode();
524                    List<Parameter> results = new ArrayList<Parameter>();
525                    Map<String, String> criteria = new HashMap<String, String>();
526                    criteria.put(CabPropertyConstants.Parameter.PARAMETER_NAMESPACE_CODE, CabConstants.Parameters.NAMESPACE);
527                    criteria.put(CabPropertyConstants.Parameter.PARAMETER_DETAIL_TYPE_CODE, CabConstants.Parameters.DETAIL_TYPE_DOCUMENT);
528                    criteria.put(CabPropertyConstants.Parameter.PARAMETER_NAME, "CHARTS_REQUIRING%" + documentType);
529                    criteria.put(CabPropertyConstants.Parameter.PARAMETER_VALUE, "%" + coa + "%");
530                    results.addAll(SpringContext.getBean(ParameterService.class).retrieveParametersGivenLookupCriteria(criteria));
531                    for (Parameter parameter : results) {
532                        if (ObjectUtils.isNotNull(parameter)) {
533                            if (parameter.getParameterValue() != null) {
534                                return false;
535                            }
536                        }
537                    }
538                }      
539            }
540            return valid;
541        }
542    
543        /**
544         * Validates for PURCHASING_OBJECT_SUB_TYPES parameter. If at least one object code of any accounting line entered is of this
545         * type, then return false.
546         * 
547         * @param accountingDocument
548         * @return
549         */
550        public boolean validatePurchasingObjectSubType(AccountingDocument accountingDocument) {
551            boolean valid = true;
552            PurchasingDocument purchasingDocument = (PurchasingDocument) accountingDocument;
553            for (PurApItem item : purchasingDocument.getItems()) {
554                String itemTypeCode = item.getItemTypeCode();
555                List accountingLines = item.getSourceAccountingLines();
556                for (Iterator iterator = accountingLines.iterator(); iterator.hasNext();) {
557                    PurApAccountingLine accountingLine = (PurApAccountingLine) iterator.next();
558                    accountingLine.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE);
559                    if (ObjectUtils.isNotNull(accountingLine.getObjectCode()) && isCapitalAssetObjectCode(accountingLine.getObjectCode())) {
560                        return false;
561                    }
562                }
563            }
564            return valid;
565        }
566    
567        /**
568         * Validates field requirement by chart for one or multiple system types.
569         * 
570         * @param systemType
571         * @param systemState
572         * @param capitalAssetSystems
573         * @param capitalAssetItems
574         * @param chartCode
575         * @param parameterName
576         * @param parameterValueString
577         * @return
578         */
579        protected boolean validateFieldRequirementByChartForOneOrMultipleSystemType(String systemType, String systemState, List<CapitalAssetSystem> capitalAssetSystems, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String parameterName, String parameterValueString) {
580            boolean valid = true;
581            boolean needValidation = (this.getParameterService().getParameterEvaluator(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, chartCode).evaluationSucceeds());
582    
583            if (needValidation) {
584                if (parameterName.startsWith("CHARTS_REQUIRING_LOCATIONS_ADDRESS")) {
585                    return validateCapitalAssetLocationAddressFieldsOneOrMultipleSystemType(capitalAssetSystems);
586                }
587                String mappedName = PurapConstants.CAMS_REQUIREDNESS_FIELDS.REQUIREDNESS_FIELDS_BY_PARAMETER_NAMES.get(parameterName);
588                if (mappedName != null) {
589                    // Check the availability matrix here, if this field doesn't exist according to the avail. matrix, then no need
590                    // to validate any further.
591                    String availableValue = getValueFromAvailabilityMatrix(mappedName, systemType, systemState);
592                    if (availableValue.equals(PurapConstants.CapitalAssetAvailability.NONE)) {
593                        return true;
594                    }
595    
596                    // capitalAssetTransactionType field is off the item
597                    if (mappedName.equals(PurapPropertyConstants.CAPITAL_ASSET_TRANSACTION_TYPE_CODE)) {
598                        String[] mappedNames = { PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS, mappedName };
599    
600                        for (PurchasingCapitalAssetItem item : capitalAssetItems) {
601                            StringBuffer keyBuffer = new StringBuffer("document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS + "[" + new Integer(item.getPurchasingItem().getItemLineNumber().intValue() - 1) + "].");
602                            valid &= validateFieldRequirementByChartHelper(item, ArrayUtils.subarray(mappedNames, 1, mappedNames.length), keyBuffer, item.getPurchasingItem().getItemLineNumber());
603                        }
604                    }
605                    // all the other fields are off the system.
606                    else {
607                        List<String> mappedNamesList = new ArrayList<String>();
608                        mappedNamesList.add(PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_SYSTEMS);
609                        if (mappedName.indexOf(".") < 0) {
610                            mappedNamesList.add(mappedName);
611                        }
612                        else {
613                            mappedNamesList.addAll(mappedNameSplitter(mappedName));
614                        }
615                        // For One system type, we would only have 1 CapitalAssetSystem, however, for multiple we may have more than
616                        // one systems in the future. Either way, this for loop should allow both the one system and multiple system
617                        // types to work fine.
618                        int count = 0;
619                        for (CapitalAssetSystem system : capitalAssetSystems) {
620                            StringBuffer keyBuffer = new StringBuffer("document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_SYSTEMS + "[" + new Integer(count) + "].");
621                            valid &= validateFieldRequirementByChartHelper(system, ArrayUtils.subarray(mappedNamesList.toArray(), 1, mappedNamesList.size()), keyBuffer, null);
622                            count++;
623                        }
624                    }
625                }
626            }
627            return valid;
628        }
629    
630        /**
631         * Validates the field requirement by chart for individual system type.
632         * 
633         * @param systemState
634         * @param capitalAssetItems
635         * @param chartCode
636         * @param parameterName
637         * @param parameterValueString
638         * @return
639         */
640        protected boolean validateFieldRequirementByChartForIndividualSystemType(String systemState, List<PurchasingCapitalAssetItem> capitalAssetItems, String chartCode, String parameterName, String parameterValueString) {
641            boolean valid = true;
642            boolean needValidation = (this.getParameterService().getParameterEvaluator(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, parameterName, chartCode).evaluationSucceeds());
643    
644            if (needValidation) {
645                if (parameterName.startsWith("CHARTS_REQUIRING_LOCATIONS_ADDRESS")) {
646                    return validateCapitalAssetLocationAddressFieldsForIndividualSystemType(capitalAssetItems);
647                }
648                String mappedName = PurapConstants.CAMS_REQUIREDNESS_FIELDS.REQUIREDNESS_FIELDS_BY_PARAMETER_NAMES.get(parameterName);
649                if (mappedName != null) {
650                    // Check the availability matrix here, if this field doesn't exist according to the avail. matrix, then no need
651                    // to validate any further.
652                    String availableValue = getValueFromAvailabilityMatrix(mappedName, PurapConstants.CapitalAssetSystemTypes.INDIVIDUAL, systemState);
653                    if (availableValue.equals(PurapConstants.CapitalAssetAvailability.NONE)) {
654                        return true;
655                    }
656    
657                    // capitalAssetTransactionType field is off the item
658                    List<String> mappedNamesList = new ArrayList<String>();
659    
660                    if (mappedName.equals(PurapPropertyConstants.CAPITAL_ASSET_TRANSACTION_TYPE_CODE)) {
661                        mappedNamesList.add(PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS);
662                        mappedNamesList.add(mappedName);
663    
664                    }
665                    // all the other fields are off the system which is off the item
666                    else {
667                        mappedNamesList.add(PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS);
668                        mappedNamesList.add(PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_SYSTEM);
669                        if (mappedName.indexOf(".") < 0) {
670                            mappedNamesList.add(mappedName);
671                        }
672                        else {
673                            mappedNamesList.addAll(mappedNameSplitter(mappedName));
674                        }
675                    }
676                    // For Individual system type, we'll always iterate through the item, then if the field is off the system, we'll get
677                    // it through
678                    // the purchasingCapitalAssetSystem of the item.
679                    for (PurchasingCapitalAssetItem item : capitalAssetItems) {
680                        StringBuffer keyBuffer = new StringBuffer("document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS + "[" + new Integer(item.getPurchasingItem().getItemLineNumber().intValue() - 1) + "].");
681                        valid &= validateFieldRequirementByChartHelper(item, ArrayUtils.subarray(mappedNamesList.toArray(), 1, mappedNamesList.size()), keyBuffer, item.getPurchasingItem().getItemLineNumber());
682                    }
683                }
684            }
685            return valid;
686        }
687    
688        /**
689         * Utility method to split a long String using the "." as the delimiter then add each of the array element into a List of String
690         * and return the List of String.
691         * 
692         * @param mappedName The String to be splitted.
693         * @return The List of String after being splitted, with the "." as delimiter.
694         */
695        protected List<String> mappedNameSplitter(String mappedName) {
696            List<String> result = new ArrayList<String>();
697            String[] mappedNamesArray = mappedName.split("\\.");
698            for (int i = 0; i < mappedNamesArray.length; i++) {
699                result.add(mappedNamesArray[i]);
700            }
701            return result;
702        }
703    
704        /**
705         * Validates the field requirement by chart recursively and give error messages when it returns false.
706         * 
707         * @param bean The object to be used to obtain the property value
708         * @param mappedNames The array of Strings which when combined together, they form the field property
709         * @param errorKey The error key to be used for adding error messages to the error map.
710         * @param itemNumber The Integer that represents the item number that we're currently iterating.
711         * @return true if it passes the validation.
712         */
713        protected boolean validateFieldRequirementByChartHelper(Object bean, Object[] mappedNames, StringBuffer errorKey, Integer itemNumber) {
714            boolean valid = true;
715            Object value = ObjectUtils.getPropertyValue(bean, (String) mappedNames[0]);
716            if (ObjectUtils.isNull(value)) {
717                errorKey.append(mappedNames[0]);
718                String fieldName = SpringContext.getBean(DataDictionaryService.class).getAttributeErrorLabel(bean.getClass(), (String) mappedNames[0]);
719                if (itemNumber != null) {
720                    fieldName = fieldName + " in Item " + itemNumber;
721                }
722                GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, fieldName);
723                return false;
724            }
725            else if (value instanceof Collection) {
726                if (((Collection) value).isEmpty()) {
727                    // if this collection doesn't contain anything, when it's supposed to contain some strings with values, return
728                    // false.
729                    errorKey.append(mappedNames[0]);
730                    String mappedNameStr = (String) mappedNames[0];
731                    String methodToInvoke = "get" + (mappedNameStr.substring(0, 1)).toUpperCase() + mappedNameStr.substring(1, mappedNameStr.length() - 1) + "Class";
732                    Class offendingClass;
733                    try {
734                        offendingClass = (Class) bean.getClass().getMethod(methodToInvoke, (Class[])null).invoke(bean, (Object[])null);
735                    }
736                    catch (Exception ex) {
737                        throw new RuntimeException(ex);
738                    }
739                    BusinessObjectEntry boe = SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(offendingClass.getSimpleName());
740                    List<AttributeDefinition> offendingAttributes = boe.getAttributes();
741                    AttributeDefinition offendingAttribute = offendingAttributes.get(0);
742                    String fieldName = SpringContext.getBean(DataDictionaryService.class).getAttributeShortLabel(offendingClass, offendingAttribute.getName());
743                    GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, fieldName);
744                    return false;
745                }
746                int count = 0;
747                for (Iterator iter = ((Collection) value).iterator(); iter.hasNext();) {
748                    errorKey.append(mappedNames[0] + "[" + count + "].");
749                    count++;
750                    valid &= validateFieldRequirementByChartHelper(iter.next(), ArrayUtils.subarray(mappedNames, 1, mappedNames.length), errorKey, itemNumber);
751                }
752                return valid;
753            }
754            else if (StringUtils.isBlank(value.toString())) {
755                errorKey.append(mappedNames[0]);
756                GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, (String) mappedNames[0]);
757                return false;
758            }
759            else if (mappedNames.length > 1) {
760                // this means we have not reached the end of a single field to be traversed yet, so continue with the recursion.
761                errorKey.append(mappedNames[0]).append(".");
762                valid &= validateFieldRequirementByChartHelper(value, ArrayUtils.subarray(mappedNames, 1, mappedNames.length), errorKey, itemNumber);
763                return valid;
764            }
765            else {
766                return true;
767            }
768        }
769    
770        protected String getValueFromAvailabilityMatrix(String fieldName, String systemType, String systemState) {
771            for (AvailabilityMatrix am : PurapConstants.CAMS_AVAILABILITY_MATRIX.MATRIX_LIST) {
772                if (am.fieldName.equals(fieldName) && am.systemState.equals(systemState) && am.systemType.equals(systemType)) {
773                    return am.availableValue;
774                }
775            }
776            // if we can't find any matching from availability matrix, return null for now.
777            return null;
778        }
779    
780        /**
781         * Validates that the total quantity on all locations equals to the quantity on the line item. This is only used for IND system
782         * type.
783         * 
784         * @param capitalAssetItems
785         * @return true if the total quantity on all locations equals to the quantity on the line item.
786         */
787        protected boolean validateQuantityOnLocationsEqualsQuantityOnItem(List<PurchasingCapitalAssetItem> capitalAssetItems, String systemType, String systemState) {
788            boolean valid = true;
789            String availableValue = getValueFromAvailabilityMatrix(PurapPropertyConstants.CAPITAL_ASSET_LOCATIONS + "." + PurapPropertyConstants.QUANTITY, systemType, systemState);
790            if (availableValue.equals(PurapConstants.CapitalAssetAvailability.NONE)) {
791                // If the location quantity isn't available on the document given the system type and system state, we don't need to
792                // validate this, just return true.
793                return true;
794            }
795            int count = 0;
796            for (PurchasingCapitalAssetItem item : capitalAssetItems) {
797                if (item.getPurchasingItem() != null && item.getPurchasingItem().getItemType().isQuantityBasedGeneralLedgerIndicator() && !item.getPurchasingCapitalAssetSystem().getCapitalAssetLocations().isEmpty()) {
798                    KualiDecimal total = new KualiDecimal(0);
799                    for (CapitalAssetLocation location : item.getPurchasingCapitalAssetSystem().getCapitalAssetLocations()) {
800                        if (ObjectUtils.isNotNull(location.getItemQuantity())) {
801                            total = total.add(location.getItemQuantity());
802                        }
803                    }
804                    if (!item.getPurchasingItem().getItemQuantity().equals(total)) {
805                        valid = false;
806                        String errorKey = PurapKeyConstants.ERROR_CAPITAL_ASSET_LOCATIONS_QUANTITY_MUST_EQUAL_ITEM_QUANTITY;
807                        String propertyName = "document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS + "[" + count + "]." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_SYSTEM + ".newPurchasingCapitalAssetLocationLine." + PurapPropertyConstants.QUANTITY;
808                        GlobalVariables.getMessageMap().putError(propertyName, errorKey, Integer.toString(count + 1));
809                    }
810                }
811                count++;
812            }
813    
814            return valid;
815        }
816    
817        /**
818         * Validates for the individual system type that for each of the items, the capitalAssetTransactionTypeCode matches the system
819         * parameter PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS, the method will return true but if it doesn't match the
820         * system parameter, then loop through each of the itemCapitalAssets. If there is any non-null capitalAssetNumber then return
821         * false.
822         * 
823         * @param capitalAssetItems the List of PurchasingCapitalAssetItems on the document to be validated
824         * @return false if the capital asset transaction type does not match the system parameter that allows asset numbers but the
825         *         itemCapitalAsset contains at least one asset numbers.
826         */
827        protected boolean validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers(List<PurchasingCapitalAssetItem> capitalAssetItems) {
828            boolean valid = true;
829            int count = 0;
830            for (PurchasingCapitalAssetItem capitalAssetItem : capitalAssetItems) {
831                String prefix = "document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS + "[" + count + "].";
832                valid &= validatePurchasingTransactionTypesAllowingAssetNumbers(capitalAssetItem.getPurchasingCapitalAssetSystem(), capitalAssetItem.getCapitalAssetTransactionTypeCode(), prefix);
833                count++;
834            }
835            return valid;
836        }
837    
838        /**
839         * Generic validation that if the capitalAssetTransactionTypeCode does not match the system parameter
840         * PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS and at least one of the itemCapitalAssets contain a non-null
841         * capitalAssetNumber then return false. This method is used by one system and multiple system types as well as being used as a
842         * helper method for validateIndividualSystemPurchasingTransactionTypesAllowingAssetNumbers method.
843         * 
844         * @param capitalAssetSystem the capitalAssetSystem containing a List of itemCapitalAssets to be validated.
845         * @param capitalAssetTransactionType the capitalAssetTransactionTypeCode containing asset numbers to be validated.
846         * @return false if the capital asset transaction type does not match the system parameter that allows asset numbers but the
847         *         itemCapitalAsset contains at least one asset numbers.
848         */
849        protected boolean validatePurchasingTransactionTypesAllowingAssetNumbers(CapitalAssetSystem capitalAssetSystem, String capitalAssetTransactionType, String prefix) {
850            boolean allowedAssetNumbers = (this.getParameterService().getParameterEvaluator(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, CabParameterConstants.CapitalAsset.PURCHASING_ASSET_TRANSACTION_TYPES_ALLOWING_ASSET_NUMBERS, capitalAssetTransactionType).evaluationSucceeds());
851            if (allowedAssetNumbers) {
852                // If this is a transaction type that allows asset numbers, we don't need to validate anymore, just return true here.
853                return true;
854            }
855            else {
856                for (ItemCapitalAsset asset : capitalAssetSystem.getItemCapitalAssets()) {
857                    if (asset.getCapitalAssetNumber() != null) {
858                        String propertyName = prefix + PurapPropertyConstants.CAPITAL_ASSET_TRANSACTION_TYPE_CODE;
859                        GlobalVariables.getMessageMap().putError(propertyName, PurapKeyConstants.ERROR_CAPITAL_ASSET_ASSET_NUMBERS_NOT_ALLOWED_TRANS_TYPE, capitalAssetTransactionType);
860                        return false;
861                    }
862                }
863            }
864            return true;
865        }
866    
867        /**
868         * TODO: rename this method removing trade in reference? Validates that if the non quantity drive allowed indicator on the
869         * capital asset transaction type is false and the item is of non quantity type
870         * 
871         * @param capitalAssetItems The List of PurchasingCapitalAssetItem to be validated.
872         * @return false if the indicator is false and there is at least one non quantity items on the list.
873         */
874        protected boolean validateNonQuantityDrivenAllowedIndicatorAndTradeIn(List<PurchasingCapitalAssetItem> capitalAssetItems) {
875            boolean valid = true;
876            int count = 0;
877            for (PurchasingCapitalAssetItem capitalAssetItem : capitalAssetItems) {
878                String prefix = "document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS + "[" + count + "].";
879                if (StringUtils.isNotBlank(capitalAssetItem.getCapitalAssetTransactionTypeCode())) {
880                    // ((PurchasingCapitalAssetItemBase)
881                    // capitalAssetItem).refreshReferenceObject(PurapPropertyConstants.CAPITAL_ASSET_TRANSACTION_TYPE);
882                    if (!capitalAssetItem.getCapitalAssetTransactionType().getCapitalAssetNonquantityDrivenAllowIndicator()) {
883                        if (!capitalAssetItem.getPurchasingItem().getItemType().isQuantityBasedGeneralLedgerIndicator()) {
884                            String propertyName = prefix + PurapPropertyConstants.CAPITAL_ASSET_TRANSACTION_TYPE_CODE;
885                            GlobalVariables.getMessageMap().putError(propertyName, PurapKeyConstants.ERROR_CAPITAL_ASSET_TRANS_TYPE_NOT_ALLOWING_NON_QUANTITY_ITEMS, capitalAssetItem.getCapitalAssetTransactionTypeCode());
886                            valid &= false;
887                        }
888                    }
889                }
890                count++;
891            }
892            return valid;
893        }
894    
895        /**
896         * Wrapper to do Capital Asset validations, generating errors instead of warnings. Makes sure that the given item's data
897         * relevant to its later possible classification as a Capital Asset is internally consistent, by marshaling and calling the
898         * methods marked as Capital Asset validations. This implementation assumes that all object codes are valid (real) object codes.
899         * 
900         * @param recurringPaymentType The item's document's RecurringPaymentType
901         * @param item A PurchasingItemBase object
902         * @param apoCheck True if this check is for APO purposes
903         * @return True if the item passes all Capital Asset validations
904         */
905        public boolean validateItemCapitalAssetWithErrors(String recurringPaymentTypeCode, ExternalPurApItem item, boolean apoCheck) {
906            PurchasingItemBase purchasingItem = (PurchasingItemBase) item;
907            List<String> previousErrorPath = GlobalVariables.getMessageMap().getErrorPath();
908            GlobalVariables.getMessageMap().clearErrorPath();
909            GlobalVariables.getMessageMap().addToErrorPath(PurapConstants.CAPITAL_ASSET_TAB_ERRORS);
910            boolean result = validatePurchasingItemCapitalAsset(recurringPaymentTypeCode, purchasingItem);
911            GlobalVariables.getMessageMap().clearErrorPath();
912            return result;
913        }
914    
915        /**
916         * Makes sure that the given item's data relevant to its later possible classification as a Capital Asset is internally
917         * consistent, by marshaling and calling the methods marked as Capital Asset validations. This implementation assumes that all
918         * object codes are valid (real) object codes.
919         * 
920         * @param recurringPaymentType The item's document's RecurringPaymentType
921         * @param item A PurchasingItemBase object
922         * @param warn A boolean which should be set to true if warnings are to be set on the calling document for most of the
923         *        validations, rather than errors.
924         * @return True if the item passes all Capital Asset validations
925         */
926        protected boolean validatePurchasingItemCapitalAsset(String recurringPaymentTypeCode, PurchasingItem item) {
927            boolean valid = true;
928            String capitalAssetTransactionTypeCode = "";
929            AssetTransactionType capitalAssetTransactionType = null;
930            String itemIdentifier = item.getItemIdentifierString();
931    
932            if (item.getPurchasingCapitalAssetItem() != null) {
933                capitalAssetTransactionTypeCode = item.getPurchasingCapitalAssetItem().getCapitalAssetTransactionTypeCode();
934                // ((PurchasingCapitalAssetItemBase)
935                // item.getPurchasingCapitalAssetItem()).refreshReferenceObject(PurapPropertyConstants.CAPITAL_ASSET_TRANSACTION_TYPE);
936                capitalAssetTransactionType = (AssetTransactionType) item.getPurchasingCapitalAssetItem().getCapitalAssetTransactionType();
937            }
938    
939            // These checks do not depend on Accounting Line information, but do depend on transaction type.
940            if (!StringUtils.isEmpty(capitalAssetTransactionTypeCode)) {
941                valid &= validateCapitalAssetTransactionTypeVersusRecurrence(capitalAssetTransactionType, recurringPaymentTypeCode, itemIdentifier);
942            }
943            return valid &= validatePurapItemCapitalAsset(item, capitalAssetTransactionType);
944        }
945    
946        /**
947         * This method validated purapItem giving a transtype
948         * 
949         * @param recurringPaymentType
950         * @param item
951         * @param warn
952         * @return
953         */
954        protected boolean validatePurapItemCapitalAsset(PurApItem item, AssetTransactionType capitalAssetTransactionType) {
955            boolean valid = true;
956            String itemIdentifier = item.getItemIdentifierString();
957            boolean quantityBased = item.getItemType().isQuantityBasedGeneralLedgerIndicator();
958            BigDecimal itemUnitPrice = item.getItemUnitPrice();
959            HashSet<String> capitalOrExpenseSet = new HashSet<String>(); // For the first validation on every accounting line.
960    
961    
962            // Do the checks that depend on Accounting Line information.
963            for (PurApAccountingLine accountingLine : item.getSourceAccountingLines()) {
964                // Because of ObjectCodeCurrent, we had to refresh this.
965                accountingLine.refreshReferenceObject(KFSPropertyConstants.OBJECT_CODE);
966                ObjectCode objectCode = accountingLine.getObjectCode();
967    
968                if (ObjectUtils.isNotNull(objectCode)) {
969                    String capitalOrExpense = objectCodeCapitalOrExpense(objectCode);
970                    capitalOrExpenseSet.add(capitalOrExpense); // HashSets accumulate distinct values (and nulls) only.
971    
972                    valid &= validateAccountingLinesNotCapitalAndExpense(capitalOrExpenseSet, itemIdentifier, objectCode);
973    
974    
975                    // Do the checks involving capital asset transaction type.
976                    if (capitalAssetTransactionType != null) {
977                        valid &= validateObjectCodeVersusTransactionType(objectCode, capitalAssetTransactionType, itemIdentifier, quantityBased);
978                    }
979                }
980            }
981            return valid;
982        }
983    
984        /**
985         * Capital Asset validation: An item cannot have among its associated accounting lines both object codes that indicate it is a
986         * Capital Asset, and object codes that indicate that the item is not a Capital Asset. Whether an object code indicates that the
987         * item is a Capital Asset is determined by whether its level is among a specific set of levels that are deemed acceptable for
988         * such items.
989         * 
990         * @param capitalOrExpenseSet A HashSet containing the distinct values of either "Capital" or "Expense" that have been added to
991         *        it.
992         * @param warn A boolean which should be set to true if warnings are to be set on the calling document
993         * @param itemIdentifier A String identifying the item for error display
994         * @param objectCode An ObjectCode, for error display
995         * @return True if the given HashSet contains at most one of either "Capital" or "Expense"
996         */
997        protected boolean validateAccountingLinesNotCapitalAndExpense(HashSet<String> capitalOrExpenseSet, String itemIdentifier, ObjectCode objectCode) {
998            boolean valid = true;
999            // If the set contains more than one distinct string, fail.
1000            if (capitalOrExpenseSet.size() > 1) {
1001                GlobalVariables.getMessageMap().putError(KFSConstants.FINANCIAL_OBJECT_LEVEL_CODE_PROPERTY_NAME, CabKeyConstants.ERROR_ITEM_CAPITAL_AND_EXPENSE, itemIdentifier, objectCode.getFinancialObjectCodeName());
1002                valid &= false;
1003            }
1004            return valid;
1005        }
1006    
1007        protected boolean validateLevelCapitalAssetIndication(BigDecimal unitPrice, ObjectCode objectCode, String itemIdentifier) {
1008    
1009            String capitalAssetPriceThresholdParam = this.getParameterService().getParameterValue(AssetGlobal.class, CabParameterConstants.CapitalAsset.CAPITALIZATION_LIMIT_AMOUNT);
1010            BigDecimal priceThreshold = null;
1011            try {
1012                priceThreshold = new BigDecimal(capitalAssetPriceThresholdParam);
1013            }
1014            catch (NumberFormatException nfe) {
1015                throw new RuntimeException("the parameter for CAPITAL_ASSET_OBJECT_LEVELS came was not able to be converted to a number.", nfe);
1016            }
1017            if (unitPrice.compareTo(priceThreshold) >= 0) {
1018                List<String> possibleCAMSObjectLevels = this.getParameterService().getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, CabParameterConstants.CapitalAsset.POSSIBLE_CAPITAL_ASSET_OBJECT_LEVELS);
1019                if (possibleCAMSObjectLevels.contains(objectCode.getFinancialObjectLevelCode())) {
1020    
1021                    String warning = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(CabKeyConstants.WARNING_ABOVE_THRESHOLD_SUGESTS_CAPITAL_ASSET_LEVEL);
1022                    warning = StringUtils.replace(warning, "{0}", itemIdentifier);
1023                    warning = StringUtils.replace(warning, "{1}", priceThreshold.toString());
1024                    GlobalVariables.getMessageList().add(warning);
1025    
1026                    return false;
1027                }
1028            }
1029            return true;
1030        }
1031    
1032        /**
1033         * Capital Asset validation: If the item has a transaction type, check that the transaction type is acceptable for the object
1034         * code sub-types of all the object codes on the associated accounting lines.
1035         * 
1036         * @param objectCode
1037         * @param capitalAssetTransactionType
1038         * @param warn A boolean which should be set to true if warnings are to be set on the calling document
1039         * @param itemIdentifier
1040         * @return
1041         */
1042        protected boolean validateObjectCodeVersusTransactionType(ObjectCode objectCode, CapitalAssetBuilderAssetTransactionType capitalAssetTransactionType, String itemIdentifier, boolean quantityBasedItem) {
1043            boolean valid = true;
1044            String[] objectCodeSubTypes = {};
1045    
1046    
1047            if (quantityBasedItem) {
1048                String capitalAssetQuantitySubtypeRequiredText = capitalAssetTransactionType.getCapitalAssetQuantitySubtypeRequiredText();
1049                if (capitalAssetQuantitySubtypeRequiredText != null) {
1050                    objectCodeSubTypes = StringUtils.split(capitalAssetQuantitySubtypeRequiredText, ";");
1051                }
1052            }
1053            else {
1054                String capitalAssetNonquantitySubtypeRequiredText = capitalAssetTransactionType.getCapitalAssetNonquantitySubtypeRequiredText();
1055                if (capitalAssetNonquantitySubtypeRequiredText != null) {
1056                    objectCodeSubTypes = StringUtils.split(capitalAssetNonquantitySubtypeRequiredText, ";");
1057                }
1058            }
1059    
1060            boolean found = false;
1061            for (String subType : objectCodeSubTypes) {
1062                if (StringUtils.equals(subType, objectCode.getFinancialObjectSubTypeCode())) {
1063                    found = true;
1064                    break;
1065                }
1066            }
1067    
1068            if (!found) {
1069                GlobalVariables.getMessageMap().putError(PurapPropertyConstants.ITEM_CAPITAL_ASSET_TRANSACTION_TYPE, CabKeyConstants.ERROR_ITEM_TRAN_TYPE_OBJECT_CODE_SUBTYPE, itemIdentifier, capitalAssetTransactionType.getCapitalAssetTransactionTypeDescription(), objectCode.getFinancialObjectCodeName(), objectCode.getFinancialObjectSubType().getFinancialObjectSubTypeName());
1070    
1071                valid &= false;
1072            }
1073            return valid;
1074        }
1075    
1076        /**
1077         * Capital Asset validation: If the item has a transaction type, check that if the document specifies that recurring payments
1078         * are to be made, that the transaction type is one that is appropriate for this situation, and that if the document does not
1079         * specify that recurring payments are to be made, that the transaction type is one that is appropriate for that situation.
1080         * 
1081         * @param capitalAssetTransactionType
1082         * @param recurringPaymentType
1083         * @param warn A boolean which should be set to true if warnings are to be set on the calling document
1084         * @param itemIdentifier
1085         * @return
1086         */
1087        protected boolean validateCapitalAssetTransactionTypeVersusRecurrence(CapitalAssetBuilderAssetTransactionType capitalAssetTransactionType, String recurringPaymentTypeCode, String itemIdentifier) {
1088            boolean valid = true;
1089    
1090            // If there is a tran type ...
1091            if ((capitalAssetTransactionType != null) && (capitalAssetTransactionType.getCapitalAssetTransactionTypeCode() != null)) {
1092                String recurringTransactionTypeCodes = this.getParameterService().getParameterValue(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, CabParameterConstants.CapitalAsset.RECURRING_CAMS_TRAN_TYPES);
1093    
1094    
1095                if (StringUtils.isNotEmpty(recurringPaymentTypeCode)) { // If there is a recurring payment type ...
1096                    if (!StringUtils.contains(recurringTransactionTypeCodes, capitalAssetTransactionType.getCapitalAssetTransactionTypeCode())) {
1097                        // There should be a recurring tran type code.
1098                        GlobalVariables.getMessageMap().putError(PurapPropertyConstants.ITEM_CAPITAL_ASSET_TRANSACTION_TYPE, CabKeyConstants.ERROR_ITEM_WRONG_TRAN_TYPE, itemIdentifier, capitalAssetTransactionType.getCapitalAssetTransactionTypeDescription(), CabConstants.ValidationStrings.RECURRING);
1099                        valid &= false;
1100                    }
1101                }
1102                else { // If the payment type is not recurring ...
1103                    // There should not be a recurring transaction type code.
1104                    if (StringUtils.contains(recurringTransactionTypeCodes, capitalAssetTransactionType.getCapitalAssetTransactionTypeCode())) {
1105                        GlobalVariables.getMessageMap().putError(PurapPropertyConstants.ITEM_CAPITAL_ASSET_TRANSACTION_TYPE, CabKeyConstants.ERROR_ITEM_WRONG_TRAN_TYPE, itemIdentifier, capitalAssetTransactionType.getCapitalAssetTransactionTypeDescription(), CabConstants.ValidationStrings.NON_RECURRING);
1106    
1107                        valid &= false;
1108                    }
1109                }
1110            }
1111            else { // If there is no transaction type ...
1112                if (StringUtils.isNotEmpty(recurringPaymentTypeCode)) { // If there is a recurring payment type ...
1113                    GlobalVariables.getMessageMap().putError(PurapPropertyConstants.ITEM_CAPITAL_ASSET_TRANSACTION_TYPE, CabKeyConstants.ERROR_ITEM_NO_TRAN_TYPE, itemIdentifier, CabConstants.ValidationStrings.RECURRING);
1114                    valid &= false;
1115                }
1116                else { // If the payment type is not recurring ...
1117                    GlobalVariables.getMessageMap().putError(PurapPropertyConstants.ITEM_CAPITAL_ASSET_TRANSACTION_TYPE, CabKeyConstants.ERROR_ITEM_NO_TRAN_TYPE, itemIdentifier, CabConstants.ValidationStrings.NON_RECURRING);
1118                    valid &= false;
1119                }
1120            }
1121    
1122            return valid;
1123        }
1124    
1125        /**
1126         * Utility wrapping isCapitalAssetObjectCode for the use of processItemCapitalAssetValidation.
1127         * 
1128         * @param oc An ObjectCode
1129         * @return A String indicating that the given object code is either Capital or Expense
1130         */
1131        protected String objectCodeCapitalOrExpense(ObjectCode oc) {
1132            String capital = CabConstants.ValidationStrings.CAPITAL;
1133            String expense = CabConstants.ValidationStrings.EXPENSE;
1134            return (isCapitalAssetObjectCode(oc) ? capital : expense);
1135        }
1136    
1137        /**
1138         * Predicate to determine whether the given object code is of a specified object sub type required for purap cams.
1139         * 
1140         * @param oc An ObjectCode
1141         * @return True if the ObjectSubType is the one designated for capital assets.
1142         */
1143        protected boolean isCapitalAssetObjectCode(ObjectCode oc) {
1144            String capitalAssetObjectSubType = this.getParameterService().getParameterValue(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, PurapParameterConstants.CapitalAsset.PURCHASING_OBJECT_SUB_TYPES);
1145            return (StringUtils.containsIgnoreCase(capitalAssetObjectSubType, oc.getFinancialObjectSubTypeCode()) ? true : false);
1146        }
1147    
1148        /**
1149         * Not documented
1150         * @param capitalAssetSystems
1151         * @return
1152         */
1153        protected boolean validateCapitalAssetLocationAddressFieldsOneOrMultipleSystemType(List<CapitalAssetSystem> capitalAssetSystems) {
1154            boolean valid = true;
1155            boolean ignoreRoom = false;
1156            int systemCount = 0;
1157            for (CapitalAssetSystem system : capitalAssetSystems) {
1158                List<CapitalAssetLocation> capitalAssetLocations = system.getCapitalAssetLocations();
1159                StringBuffer errorKey = new StringBuffer("document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_SYSTEMS + "[" + new Integer(systemCount++) + "].");
1160                errorKey.append("capitalAssetLocations");
1161                int locationCount = 0;
1162                for (CapitalAssetLocation location : capitalAssetLocations) {
1163                    errorKey.append("[" + locationCount++ + "].");
1164                    AssetType assetType = getAssetType(system.getCapitalAssetTypeCode());                
1165                    ignoreRoom = ObjectUtils.isNull(assetType) ? false : assetType.isMovingIndicator();
1166                    valid &= validateCapitalAssetLocationAddressFields(location, ignoreRoom, errorKey);
1167                }
1168            }
1169            return valid;
1170        }
1171    
1172        /**
1173         * Not Documented
1174         * @param capitalAssetItems
1175         * @return
1176         */
1177        protected boolean validateCapitalAssetLocationAddressFieldsForIndividualSystemType(List<PurchasingCapitalAssetItem> capitalAssetItems) {
1178            boolean valid = true;
1179            boolean ignoreRoom = false;
1180            for (PurchasingCapitalAssetItem item : capitalAssetItems) {
1181                CapitalAssetSystem system = item.getPurchasingCapitalAssetSystem();
1182                List<CapitalAssetLocation> capitalAssetLocations = system.getCapitalAssetLocations();
1183                StringBuffer errorKey = new StringBuffer("document." + PurapPropertyConstants.PURCHASING_CAPITAL_ASSET_ITEMS + "[" + new Integer(item.getPurchasingItem().getItemLineNumber().intValue() - 1) + "].");
1184                errorKey.append("purchasingCapitalAssetSystem.capitalAssetLocations");
1185                int i = 0;
1186                for (CapitalAssetLocation location : capitalAssetLocations) {
1187                    errorKey.append("[" + i++ + "].");
1188                    AssetType assetType = getAssetType(system.getCapitalAssetTypeCode());                
1189                    ignoreRoom = ObjectUtils.isNull(assetType) ? false : assetType.isMovingIndicator();
1190                    valid &= validateCapitalAssetLocationAddressFields(location, ignoreRoom, errorKey);
1191                }
1192            }
1193            return valid;
1194        }
1195    
1196        /**
1197         * Not documented
1198         * @param location
1199         * @param ignoreRoom Is used to identify if room number should be validated.  If true then room is ignored in this validation.
1200         * @param errorKey
1201         * @return
1202         */
1203        protected boolean validateCapitalAssetLocationAddressFields(CapitalAssetLocation location, boolean ignoreRoom, StringBuffer errorKey) {
1204            boolean valid = true;
1205            if (StringUtils.isBlank(location.getCapitalAssetLine1Address())) {
1206                GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_ADDRESS_LINE1);
1207                valid &= false;
1208            }
1209            if (StringUtils.isBlank(location.getCapitalAssetCityName())) {
1210                GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_CITY);
1211                valid &= false;
1212            }
1213            if (StringUtils.isBlank(location.getCapitalAssetCountryCode())) {
1214                GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_COUNTRY);
1215                valid &= false;
1216            }
1217            else if (location.getCapitalAssetCountryCode().equals(KFSConstants.COUNTRY_CODE_UNITED_STATES)) {
1218                if (StringUtils.isBlank(location.getCapitalAssetStateCode())) {
1219                    GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_STATE);
1220                    valid &= false;
1221                }
1222                if (StringUtils.isBlank(location.getCapitalAssetPostalCode())) {
1223                    GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_POSTAL_CODE);
1224                    valid &= false;
1225                }
1226            }
1227            if (!location.isOffCampusIndicator()) {
1228                if (StringUtils.isBlank(location.getCampusCode())) {
1229                    GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_CAMPUS);
1230                    valid &= false;
1231                }
1232                if (StringUtils.isBlank(location.getBuildingCode())) {
1233                    GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_BUILDING);
1234                    valid &= false;
1235                }
1236                
1237                if (StringUtils.isBlank(location.getBuildingRoomNumber()) && !ignoreRoom) {
1238                    GlobalVariables.getMessageMap().putError(errorKey.toString(), KFSKeyConstants.ERROR_REQUIRED, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_ROOM);
1239                    valid &= false;
1240                }
1241                
1242                if (!StringUtils.isBlank(location.getBuildingRoomNumber()) && ignoreRoom) {
1243                    GlobalVariables.getMessageMap().putError(errorKey.toString(), CamsKeyConstants.AssetLocation.ERROR_ASSET_LOCATION_ROOM_NUMBER_NONMOVEABLE, PurapPropertyConstants.CAPITAL_ASSET_LOCATION_ROOM);
1244                    valid &= false;
1245                }
1246            }
1247            return valid;
1248        }
1249    
1250        protected boolean validateAccountsPayableItem(AccountsPayableItem apItem) {
1251            boolean valid = true;
1252            valid &= validatePurapItemCapitalAsset(apItem, (AssetTransactionType) apItem.getCapitalAssetTransactionType());
1253            return valid;
1254        }
1255    
1256        // end of methods for purap
1257    
1258        /**
1259         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#validateFinancialProcessingData(org.kuali.kfs.sys.document.AccountingDocument,
1260         *      org.kuali.kfs.fp.businessobject.CapitalAssetInformation)
1261         * @param accountingDocument and capitalAssetInformation
1262         * @return True if the FinancialProcessingData is valid.
1263         */
1264        public boolean validateFinancialProcessingData(AccountingDocument accountingDocument, CapitalAssetInformation capitalAssetInformation) {
1265            boolean valid = true;
1266    
1267            // Check if we need to collect cams data
1268            AccountCapitalObjectCode accountCapitalObjectCode = this.getCapitalAssetObjectSubTypeLinesFlag(accountingDocument);
1269            boolean isNewAssetBlank = isNewAssetBlank(capitalAssetInformation);
1270            boolean isUpdateAssetBlank = isUpdateAssetBlank(capitalAssetInformation);
1271    
1272            // validate asset information provided by the user are allowed
1273            valid = accountCapitalObjectCode.validateAssetInfoAllowed(accountingDocument, isNewAssetBlank, isUpdateAssetBlank);
1274    
1275            if (valid) {
1276                // non-capital object code, no further check needed for asset
1277                if (AccountCapitalObjectCode.BOTH_NONCAP.equals(accountCapitalObjectCode)) {
1278                    return true;
1279                }
1280                else {
1281                    if (!isUpdateAssetBlank) {
1282                        // Validate update Asset information
1283                        valid &= validateUpdateCapitalAssetField(capitalAssetInformation, accountingDocument);
1284                    }
1285                    else {
1286                        // Validate New Asset information
1287                        valid &= checkNewCapitalAssetFieldsExist(capitalAssetInformation, accountingDocument);
1288                        if (valid) {
1289                            valid = validateNewCapitalAssetFields(capitalAssetInformation);
1290                        }
1291                    }
1292                }
1293            }
1294            return valid;
1295        }
1296    
1297        /**
1298         * Get the Capital Asset Object Code from the accounting lines.
1299         * 
1300         * @param accountingDocument
1301         * @return getCapitalAssetObjectSubTypeLinesFlag = AccountCapitalObjectCode.NON_CAPITAL ==> no assetObjectSubType code
1302         *         AccountCapitalObjectCode.FROM_CAPITAL ==> assetObjectSubType code on source lines
1303         *         AccountCapitalObjectCode.FROM_CAPITAL ==> assetObjectSubType code on target lines
1304         *         AccountCapitalObjectCode.BOTH_CAPITAL ==> assetObjectSubType code on both source and target lines
1305         */
1306        protected AccountCapitalObjectCode getCapitalAssetObjectSubTypeLinesFlag(AccountingDocument accountingDocument) {
1307            List<String> financialProcessingCapitalObjectSubTypes = this.getParameterService().getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, CabParameterConstants.CapitalAsset.FINANCIAL_PROCESSING_CAPITAL_OBJECT_SUB_TYPES);
1308            AccountCapitalObjectCode capitalAssetObjectSubTypeLinesFlag = AccountCapitalObjectCode.BOTH_NONCAP;
1309    
1310            // Check if SourceAccountingLine has objectSub type code
1311            List<SourceAccountingLine> sAccountingLines = accountingDocument.getSourceAccountingLines();
1312            for (SourceAccountingLine sourceAccountingLine : sAccountingLines) {
1313                ObjectCode objectCode = sourceAccountingLine.getObjectCode();
1314    
1315                if (ObjectUtils.isNotNull(objectCode)) {
1316                    String objectSubTypeCode = objectCode.getFinancialObjectSubTypeCode();
1317                    if (financialProcessingCapitalObjectSubTypes.contains(objectSubTypeCode)) {
1318                        capitalAssetObjectSubTypeLinesFlag = AccountCapitalObjectCode.FROM_CAPITAL_TO_NONCAP;
1319                        break;
1320                    }
1321                }
1322            }
1323    
1324            // Check if TargetAccountingLine has objectSub type code
1325            List<TargetAccountingLine> tAccountingLines = accountingDocument.getTargetAccountingLines();
1326            for (TargetAccountingLine targetAccountingLine : tAccountingLines) {
1327                ObjectCode objectCode = targetAccountingLine.getObjectCode();
1328    
1329                if (ObjectUtils.isNotNull(objectCode)) {
1330                    String objectSubTypeCode = objectCode.getFinancialObjectSubTypeCode();
1331                    if (financialProcessingCapitalObjectSubTypes.contains(objectSubTypeCode)) {
1332                        capitalAssetObjectSubTypeLinesFlag = capitalAssetObjectSubTypeLinesFlag == AccountCapitalObjectCode.FROM_CAPITAL_TO_NONCAP ? AccountCapitalObjectCode.BOTH_CAPITAL : AccountCapitalObjectCode.FROM_NONCAP_TO_CAPITAL;
1333                        break;
1334                    }
1335                }
1336            }
1337    
1338            return capitalAssetObjectSubTypeLinesFlag;
1339        }
1340    
1341        /**
1342         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#hasCapitalAssetObjectSubType(org.kuali.kfs.sys.document.AccountingDocument)
1343         */
1344        public boolean hasCapitalAssetObjectSubType(AccountingDocument accountingDocument) {
1345            final AccountCapitalObjectCode accountCapitalObjectCode = getCapitalAssetObjectSubTypeLinesFlag(accountingDocument);
1346            return accountCapitalObjectCode != null && !accountCapitalObjectCode.equals(AccountCapitalObjectCode.BOTH_NONCAP);
1347        }
1348    
1349    
1350        /**
1351         * To check if data exists on create new asset
1352         * 
1353         * @param capitalAssetInformation
1354         * @return boolean false if the new asset is not blank
1355         */
1356        protected boolean isNewAssetBlank(CapitalAssetInformation capitalAssetInformation) {
1357            boolean isBlank = true;
1358    
1359            if (ObjectUtils.isNotNull(capitalAssetInformation.getCapitalAssetTypeCode()) || ObjectUtils.isNotNull(capitalAssetInformation.getVendorName()) || ObjectUtils.isNotNull(capitalAssetInformation.getCapitalAssetQuantity()) || ObjectUtils.isNotNull(capitalAssetInformation.getCapitalAssetManufacturerName()) || ObjectUtils.isNotNull(capitalAssetInformation.getCapitalAssetManufacturerModelNumber()) || ObjectUtils.isNotNull(capitalAssetInformation.getCapitalAssetDescription())) {
1360                isBlank = false;
1361            }
1362            return isBlank;
1363        }
1364    
1365        /**
1366         * To check if data exists on update asset
1367         * 
1368         * @param capitalAssetInformation
1369         * @return boolean false if the update asset is not blank
1370         */
1371        protected boolean isUpdateAssetBlank(CapitalAssetInformation capitalAssetInformation) {
1372            boolean isBlank = true;
1373    
1374            if (ObjectUtils.isNotNull(capitalAssetInformation.getCapitalAssetNumber())) {
1375                isBlank = false;
1376            }
1377            return isBlank;
1378        }
1379    
1380        /**
1381         * To validate if the asset is active
1382         * 
1383         * @param capitalAssetManagementAsset the asset to be validated
1384         * @return boolean false if the asset is not active
1385         */
1386        protected boolean validateUpdateCapitalAssetField(CapitalAssetInformation capitalAssetInformation, AccountingDocument accountingDocument) {
1387            boolean valid = true;
1388    
1389            Map<String, String> params = new HashMap<String, String>();
1390            params.put(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, capitalAssetInformation.getCapitalAssetNumber().toString());
1391            Asset asset = (Asset) this.getBusinessObjectService().findByPrimaryKey(Asset.class, params);
1392    
1393            List<Long> assetNumbers = new ArrayList<Long>();
1394            assetNumbers.add(capitalAssetInformation.getCapitalAssetNumber());
1395    
1396            if (ObjectUtils.isNull(asset)) {
1397                valid = false;
1398                String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, KFSPropertyConstants.CAPITAL_ASSET_NUMBER);
1399                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, KFSKeyConstants.ERROR_EXISTENCE, label);
1400            }
1401            else if (!(this.getAssetService().isCapitalAsset(asset) && !this.getAssetService().isAssetRetired(asset))) {
1402                // check asset status must be capital asset active.
1403                valid = false;
1404                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_NUMBER, CabKeyConstants.CapitalAssetInformation.ERROR_ASSET_ACTIVE_CAPITAL_ASSET_REQUIRED);
1405            }
1406            else {
1407                String documentNumber = accountingDocument.getDocumentNumber();
1408                String documentType = getDocumentTypeName(accountingDocument);
1409    
1410                if (getCapitalAssetManagementModuleService().isFpDocumentEligibleForAssetLock(accountingDocument, documentType) && getCapitalAssetManagementModuleService().isAssetLocked(assetNumbers, documentType, documentNumber)) {
1411                    valid = false;
1412                }
1413            }
1414            return valid;
1415        }
1416    
1417        /**
1418         * Check if all required fields exist on new asset
1419         * 
1420         * @param capitalAssetInformation the fields of add asset to be checked
1421         * @return boolean false if a required field is missing
1422         */
1423        protected boolean checkNewCapitalAssetFieldsExist(CapitalAssetInformation capitalAssetInformation, AccountingDocument accountingDocument) {
1424            boolean valid = true;
1425    
1426            if (StringUtils.isBlank(capitalAssetInformation.getCapitalAssetTypeCode())) {
1427                String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, KFSPropertyConstants.CAPITAL_ASSET_TYPE_CODE);
1428                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_TYPE_CODE, KFSKeyConstants.ERROR_REQUIRED, label);
1429                valid = false;
1430            }
1431    
1432            if (capitalAssetInformation.getCapitalAssetQuantity() == null || capitalAssetInformation.getCapitalAssetQuantity() <= 0) {
1433                String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, KFSPropertyConstants.CAPITAL_ASSET_QUANTITY);
1434                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_QUANTITY, KFSKeyConstants.ERROR_REQUIRED, label);
1435                valid = false;
1436            }
1437    
1438            // skip vendor name required validation for procurement card document
1439            if (!(accountingDocument instanceof ProcurementCardDocument) && StringUtils.isBlank(capitalAssetInformation.getVendorName())) {
1440                String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, KFSPropertyConstants.VENDOR_NAME);
1441                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.VENDOR_NAME, KFSKeyConstants.ERROR_REQUIRED, label);
1442                valid = false;
1443            }
1444    
1445            if (StringUtils.isBlank(capitalAssetInformation.getCapitalAssetManufacturerName())) {
1446                String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, KFSPropertyConstants.CAPITAL_ASSET_MANUFACTURE_NAME);
1447                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_MANUFACTURE_NAME, KFSKeyConstants.ERROR_REQUIRED, label);
1448                valid = false;
1449            }
1450    
1451            if (StringUtils.isBlank(capitalAssetInformation.getCapitalAssetDescription())) {
1452                String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, CamsPropertyConstants.Asset.CAPITAL_ASSET_DESCRIPTION);
1453                GlobalVariables.getMessageMap().putError(CamsPropertyConstants.Asset.CAPITAL_ASSET_DESCRIPTION, KFSKeyConstants.ERROR_REQUIRED, label);
1454                valid = false;
1455            }
1456    
1457            int index = 0;
1458            List<CapitalAssetInformationDetail> capitalAssetInformationDetails = capitalAssetInformation.getCapitalAssetInformationDetails();
1459            for (CapitalAssetInformationDetail dtl : capitalAssetInformationDetails) {
1460                String errorPathPrefix = KFSPropertyConstants.DOCUMENT + "." + KFSPropertyConstants.CAPITAL_ASSET_INFORMATION + "." + KFSPropertyConstants.CAPITAL_ASSET_INFORMATION_DETAILS;
1461    
1462                if (StringUtils.isBlank(dtl.getCampusCode())) {
1463                    String label = this.getDataDictionaryService().getAttributeLabel(Campus.class, KFSPropertyConstants.CAMPUS_CODE);
1464                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.CAMPUS_CODE, KFSKeyConstants.ERROR_REQUIRED, label);
1465                    valid = false;
1466                }
1467    
1468                if (StringUtils.isBlank(dtl.getBuildingCode())) {
1469                    String label = this.getDataDictionaryService().getAttributeLabel(Building.class, KFSPropertyConstants.BUILDING_CODE);
1470                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.BUILDING_CODE, KFSKeyConstants.ERROR_REQUIRED, label);
1471                    valid = false;
1472                }
1473    
1474                // Room is not required for non-moveable
1475                AssetType assetType = getAssetType(capitalAssetInformation.getCapitalAssetTypeCode());
1476                if (ObjectUtils.isNull(assetType) || assetType.isMovingIndicator()) {
1477                    if (StringUtils.isBlank(dtl.getBuildingRoomNumber())) {
1478                        String label = this.getDataDictionaryService().getAttributeLabel(Room.class, KFSPropertyConstants.BUILDING_ROOM_NUMBER);
1479                        GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.BUILDING_ROOM_NUMBER, KFSKeyConstants.ERROR_REQUIRED, label);
1480                        valid = false;
1481                    }
1482                }
1483                
1484                // Room number not allowed for non-moveable assets
1485                if (ObjectUtils.isNotNull(assetType) && !assetType.isMovingIndicator()) {
1486                    if (StringUtils.isNotBlank(dtl.getBuildingRoomNumber())) {
1487                        String label = this.getDataDictionaryService().getAttributeLabel(Room.class, KFSPropertyConstants.BUILDING_ROOM_NUMBER);
1488                        GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.BUILDING_ROOM_NUMBER, CamsKeyConstants.AssetLocation.ERROR_ASSET_LOCATION_ROOM_NUMBER_NONMOVEABLE, label);
1489                        valid = false;
1490                    }
1491                }
1492                
1493                
1494    
1495                index++;
1496            }
1497    
1498            return valid;
1499        }
1500    
1501        /**
1502         * To validate new asset information
1503         * 
1504         * @param capitalAssetInformation the information of add asset to be validated
1505         * @return boolean false if data is incorrect
1506         */
1507        protected boolean validateNewCapitalAssetFields(CapitalAssetInformation capitalAssetInformation) {
1508            boolean valid = true;
1509    
1510            if (!isAssetTypeExisting(capitalAssetInformation.getCapitalAssetTypeCode().toString())) {
1511                valid = false;
1512                String label = this.getDataDictionaryService().getAttributeLabel(CapitalAssetInformation.class, KFSPropertyConstants.CAPITAL_ASSET_TYPE_CODE);
1513                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_TYPE_CODE, KFSKeyConstants.ERROR_EXISTENCE, label);
1514            }
1515    
1516            valid &= validateTotalNumberOfAssetTagLines(capitalAssetInformation);
1517    
1518            int index = 0;
1519            List<CapitalAssetInformationDetail> capitalAssetInformationDetails = capitalAssetInformation.getCapitalAssetInformationDetails();
1520            for (CapitalAssetInformationDetail dtl : capitalAssetInformationDetails) {
1521                // We have to explicitly call this DD service to uppercase each field. This may not be the best place and maybe form
1522                // populate is a better place but we CAMS team don't own FP document. This is the best we can do for now.
1523                SpringContext.getBean(BusinessObjectDictionaryService.class).performForceUppercase(dtl);
1524                String errorPathPrefix = KFSPropertyConstants.DOCUMENT + "." + KFSPropertyConstants.CAPITAL_ASSET_INFORMATION + "." + KFSPropertyConstants.CAPITAL_ASSET_INFORMATION_DETAILS;
1525    
1526                if (StringUtils.isNotBlank(dtl.getCapitalAssetTagNumber()) && !dtl.getCapitalAssetTagNumber().equalsIgnoreCase(CamsConstants.Asset.NON_TAGGABLE_ASSET)) {
1527                    if (isTagNumberDuplicate(capitalAssetInformationDetails, dtl)) {
1528                        GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.CAPITAL_ASSET_TAG_NUMBER, CamsKeyConstants.AssetGlobal.ERROR_CAMPUS_TAG_NUMBER_DUPLICATE, dtl.getCapitalAssetTagNumber());
1529                        valid = false;
1530                    }
1531                }
1532    
1533                Map<String, Object> criteria = new HashMap<String, Object>();
1534                criteria.put(KFSPropertyConstants.CAMPUS_CODE, dtl.getCampusCode());
1535                Campus campus = (Campus) SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(Campus.class).getExternalizableBusinessObject(Campus.class, criteria);
1536                if (ObjectUtils.isNull(campus)) {
1537                    valid = false;
1538                    String label = this.getDataDictionaryService().getAttributeLabel(Campus.class, KFSPropertyConstants.CAMPUS_CODE);
1539                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.CAMPUS_CODE, KFSKeyConstants.ERROR_EXISTENCE, label);
1540                }
1541                
1542                Map<String, String> params;
1543                params = new HashMap<String, String>();
1544                params.put(KFSPropertyConstants.CAMPUS_CODE, dtl.getCampusCode());
1545                params.put(KFSPropertyConstants.BUILDING_CODE, dtl.getBuildingCode());
1546                Building building = (Building) this.getBusinessObjectService().findByPrimaryKey(Building.class, params);
1547                if (ObjectUtils.isNull(building)) {
1548                    valid = false;
1549                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.BUILDING_CODE, CamsKeyConstants.AssetLocationGlobal.ERROR_INVALID_BUILDING_CODE, dtl.getBuildingCode(), dtl.getCampusCode());
1550                }
1551    
1552                params = new HashMap<String, String>();
1553                params.put(KFSPropertyConstants.CAMPUS_CODE, dtl.getCampusCode());
1554                params.put(KFSPropertyConstants.BUILDING_CODE, dtl.getBuildingCode());
1555                params.put(KFSPropertyConstants.BUILDING_ROOM_NUMBER, dtl.getBuildingRoomNumber());
1556                Room room = (Room) this.getBusinessObjectService().findByPrimaryKey(Room.class, params);
1557                AssetType assetType = getAssetType(capitalAssetInformation.getCapitalAssetTypeCode());
1558                if (ObjectUtils.isNull(room) && (ObjectUtils.isNull(assetType) || assetType.isMovingIndicator())) {
1559                    valid = false;
1560                    GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix + "[" + index + "]" + "." + KFSPropertyConstants.BUILDING_ROOM_NUMBER, CamsKeyConstants.AssetLocationGlobal.ERROR_INVALID_ROOM_NUMBER, dtl.getBuildingRoomNumber(), dtl.getBuildingCode(), dtl.getCampusCode());
1561                }
1562                index++;
1563            }
1564    
1565            return valid;
1566        }
1567    
1568        /**
1569         * Check assetTypeCode existence.
1570         * 
1571         * @param assetTypeCode
1572         * @return
1573         */
1574        public boolean isAssetTypeExisting(String assetTypeCode) {
1575            AssetType assetType = getAssetType(assetTypeCode);
1576            return ObjectUtils.isNotNull(assetType);
1577        }
1578    
1579        /**
1580         * Retrieve the Asset type from the provided assetType Code
1581         * 
1582         * @param assetTypeCode
1583         * @return {@link AssetType}
1584         */
1585        protected AssetType getAssetType(String assetTypeCode) {
1586            Map<String, String> params = new HashMap<String, String>();
1587            params.put(KFSPropertyConstants.CAPITAL_ASSET_TYPE_CODE, assetTypeCode);
1588            AssetType assetType = (AssetType) this.getBusinessObjectService().findByPrimaryKey(AssetType.class, params);
1589            return assetType;
1590        }
1591    
1592        /**
1593         * Validate asset quantity the user entered matching the number of asset tag lines.
1594         * 
1595         * @param capitalAssetInformation
1596         * @return
1597         */
1598        protected boolean validateTotalNumberOfAssetTagLines(CapitalAssetInformation capitalAssetInformation) {
1599            boolean valid = true;
1600            Integer userInputAssetQuantity = capitalAssetInformation.getCapitalAssetQuantity();
1601            if (userInputAssetQuantity != null && (ObjectUtils.isNull(capitalAssetInformation.getCapitalAssetInformationDetails()) || capitalAssetInformation.getCapitalAssetInformationDetails().isEmpty())) {
1602                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_QUANTITY, CabKeyConstants.CapitalAssetInformation.ERROR_ASSET_TAG_LINE_REQUIRED);
1603                valid = false;
1604            }
1605            else if (userInputAssetQuantity != null && userInputAssetQuantity.intValue() != capitalAssetInformation.getCapitalAssetInformationDetails().size()) {
1606                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CAPITAL_ASSET_QUANTITY, CabKeyConstants.CapitalAssetInformation.ERROR_ASSET_QUANTITY_NOT_MATCHING_TAG_LINES);
1607                valid = false;
1608            }
1609    
1610            return valid;
1611        }
1612    
1613        /**
1614         * To check if the tag number is duplicate or in use
1615         * 
1616         * @param capitalAssetInformation, capitalAssetInformationDetail
1617         * @return boolean false if data is duplicate or in use
1618         */
1619        protected boolean isTagNumberDuplicate(List<CapitalAssetInformationDetail> capitalAssetInformationDetails, CapitalAssetInformationDetail dtl) {
1620            boolean duplicateTag = false;
1621            int tagCounter = 0;
1622            if (!this.getAssetService().findActiveAssetsMatchingTagNumber(dtl.getCapitalAssetTagNumber()).isEmpty()) {
1623                // Tag number is already in use
1624                duplicateTag = true;
1625            }
1626            else {
1627                for (CapitalAssetInformationDetail capitalAssetInfoDetl : capitalAssetInformationDetails) {
1628                    if (capitalAssetInfoDetl.getCapitalAssetTagNumber().equalsIgnoreCase(dtl.getCapitalAssetTagNumber().toString())) {
1629                        tagCounter++;
1630                    }
1631                }
1632                if (tagCounter > 1) {
1633                    // Tag number already exists in the collection
1634                    duplicateTag = true;
1635                }
1636            }
1637            return duplicateTag;
1638        }
1639    
1640        /**
1641         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#notifyRouteStatusChange(java.lang.String,
1642         *      java.lang.String)
1643         */
1644        public void notifyRouteStatusChange(DocumentHeader documentHeader) {
1645    
1646            KualiWorkflowDocument workflowDocument = documentHeader.getWorkflowDocument();
1647            String documentNumber = documentHeader.getDocumentNumber();
1648    
1649            String documentType = workflowDocument.getDocumentType();
1650    
1651            if (workflowDocument.stateIsCanceled() || workflowDocument.stateIsDisapproved()) {
1652                // release CAB line items
1653                activateCabPOLines(documentNumber);
1654                activateCabGlLines(documentNumber);
1655            }
1656            if (workflowDocument.stateIsProcessed()) {
1657                // update CAB GL lines if fully processed
1658                updatePOLinesStatusAsProcessed(documentNumber);
1659                updateGlLinesStatusAsProcessed(documentNumber);
1660                // report asset numbers to PO
1661                Integer poId = getPurchaseOrderIdentifier(documentNumber);
1662                if (poId != null) {
1663                    List<Long> assetNumbers = null;
1664                    if (DocumentTypeName.ASSET_ADD_GLOBAL.equalsIgnoreCase(documentType)) {
1665                        assetNumbers = getAssetNumbersFromAssetGlobal(documentNumber);
1666                    }
1667                    else if (DocumentTypeName.ASSET_PAYMENT.equalsIgnoreCase(documentType)) {
1668                        assetNumbers = getAssetNumbersFromAssetPayment(documentNumber);
1669                    }
1670    
1671                    if (!assetNumbers.isEmpty()) {
1672                        String noteText = buildNoteTextForPurApDoc(documentType, assetNumbers);
1673                        SpringContext.getBean(PurchasingAccountsPayableModuleService.class).addAssignedAssetNumbers(poId, workflowDocument.getInitiatorPrincipalId(), noteText);
1674                    }
1675                }
1676            }
1677    
1678        }
1679    
1680        /**
1681         * update cab non-PO lines status code for item/account/glEntry to 'P' as fully processed when possible
1682         * 
1683         * @param documentNumber
1684         */
1685        protected void updateGlLinesStatusAsProcessed(String documentNumber) {
1686            Map<String, String> fieldValues = new HashMap<String, String>();
1687            fieldValues.put(CabPropertyConstants.PurchasingAccountsPayableItemAsset.CAMS_DOCUMENT_NUMBER, documentNumber);
1688            Collection<GeneralLedgerEntryAsset> matchingGlAssets = this.getBusinessObjectService().findMatching(GeneralLedgerEntryAsset.class, fieldValues);
1689            if (matchingGlAssets != null && !matchingGlAssets.isEmpty()) {
1690                for (GeneralLedgerEntryAsset generalLedgerEntryAsset : matchingGlAssets) {
1691                    GeneralLedgerEntry generalLedgerEntry = generalLedgerEntryAsset.getGeneralLedgerEntry();
1692    
1693                    // update gl status as processed
1694                    generalLedgerEntry.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS);
1695                    this.getBusinessObjectService().save(generalLedgerEntry);
1696    
1697                    // release asset lock
1698                    if (isFpDocumentFullyProcessed(generalLedgerEntry)) {
1699                        getCapitalAssetManagementModuleService().deleteAssetLocks(generalLedgerEntry.getDocumentNumber(), null);
1700                    }
1701                }
1702            }
1703        }
1704    
1705        /**
1706         * Check all generalLedgerEntries from the same FP document are fully processed.
1707         * 
1708         * @param generalLedgerEntry
1709         * @return
1710         */
1711        protected boolean isFpDocumentFullyProcessed(GeneralLedgerEntry generalLedgerEntry) {
1712            Map<String, String> fieldValues = new HashMap<String, String>();
1713            fieldValues.put(CabPropertyConstants.GeneralLedgerEntry.DOCUMENT_NUMBER, generalLedgerEntry.getDocumentNumber());
1714            Collection<GeneralLedgerEntry> matchingGlEntries = this.getBusinessObjectService().findMatching(GeneralLedgerEntry.class, fieldValues);
1715    
1716            for (GeneralLedgerEntry glEntry : matchingGlEntries) {
1717                if (!CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equals(glEntry.getActivityStatusCode())) {
1718                    return false;
1719                }
1720            }
1721            return true;
1722        }
1723    
1724        /**
1725         * update CAB PO lines status code for item/account/glEntry to 'P' as fully processed when possible
1726         * 
1727         * @param documentNumber
1728         */
1729        protected void updatePOLinesStatusAsProcessed(String documentNumber) {
1730            Map<String, String> fieldValues = new HashMap<String, String>();
1731            fieldValues.put(CabPropertyConstants.PurchasingAccountsPayableItemAsset.CAMS_DOCUMENT_NUMBER, documentNumber);
1732            Collection<PurchasingAccountsPayableItemAsset> matchingAssets = this.getBusinessObjectService().findMatching(PurchasingAccountsPayableItemAsset.class, fieldValues);
1733            if (matchingAssets != null && !matchingAssets.isEmpty()) {
1734                // Map<Long, GeneralLedgerEntry> updateGlLines = new HashMap<Long, GeneralLedgerEntry>();
1735                // update item and account status code to 'P' as fully processed
1736                for (PurchasingAccountsPayableItemAsset itemAsset : matchingAssets) {
1737    
1738                    itemAsset.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS);
1739                    for (PurchasingAccountsPayableLineAssetAccount assetAccount : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
1740                        assetAccount.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS);
1741                        GeneralLedgerEntry generalLedgerEntry = assetAccount.getGeneralLedgerEntry();
1742    
1743                        // updateGlLines.put(generalLedgerEntry.getGeneralLedgerAccountIdentifier(), generalLedgerEntry);
1744    
1745                        if (isGlEntryFullyProcessed(generalLedgerEntry)) {
1746                            generalLedgerEntry.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS);
1747                            this.getBusinessObjectService().save(generalLedgerEntry);
1748                        }
1749                    }
1750    
1751                    // update cab document status code to 'P' as all its items fully processed
1752                    PurchasingAccountsPayableDocument purapDocument = itemAsset.getPurchasingAccountsPayableDocument();
1753                    if (isDocumentFullyProcessed(purapDocument)) {
1754                        purapDocument.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS);
1755                    }
1756    
1757                    this.getBusinessObjectService().save(purapDocument);
1758    
1759                    String lockingInformation = null;
1760                    PurchaseOrderDocument poDocument = getPurApInfoService().getCurrentDocumentForPurchaseOrderIdentifier(purapDocument.getPurchaseOrderIdentifier());
1761                    // Only individual system will lock on item line number. other system will using preq/cm doc nbr as the locking
1762                    // key
1763                    if (PurapConstants.CapitalAssetTabStrings.INDIVIDUAL_ASSETS.equalsIgnoreCase(poDocument.getCapitalAssetSystemTypeCode())) {
1764                        lockingInformation = itemAsset.getAccountsPayableLineItemIdentifier().toString();
1765                    }
1766                    // release the asset lock no matter if it's Asset global or Asset Payment since the CAB user can create Asset global
1767                    // doc even if Purap Asset numbers existing.
1768                    if (isAccountsPayableItemLineFullyProcessed(purapDocument, lockingInformation)) {
1769                        getCapitalAssetManagementModuleService().deleteAssetLocks(purapDocument.getDocumentNumber(), lockingInformation);
1770                    }
1771    
1772                }
1773            }
1774    
1775        }
1776    
1777        /**
1778         * If lockingInformation is not empty, check all item lines from the same PurAp item are fully processed. If lockingInformation
1779         * is empty, we check all items from the same PREQ/CM document processed as fully processed.
1780         * 
1781         * @param itemAsset
1782         * @return
1783         */
1784        protected boolean isAccountsPayableItemLineFullyProcessed(PurchasingAccountsPayableDocument purapDocument, String lockingInformation) {
1785            for (PurchasingAccountsPayableItemAsset item : purapDocument.getPurchasingAccountsPayableItemAssets()) {
1786                if ((StringUtils.isBlank(lockingInformation) && !CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equals(item.getActivityStatusCode())) || (StringUtils.isNotBlank(lockingInformation) && item.getAccountsPayableLineItemIdentifier().equals(lockingInformation) && !CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equals(item.getActivityStatusCode()))) {
1787                    return false;
1788                }
1789            }
1790            return true;
1791        }
1792    
1793        /**
1794         * Check if Gl Entry related accounts are fully processed
1795         * 
1796         * @param glEntry
1797         * @return
1798         */
1799        protected boolean isGlEntryFullyProcessed(GeneralLedgerEntry glEntry) {
1800            for (PurchasingAccountsPayableLineAssetAccount account : glEntry.getPurApLineAssetAccounts()) {
1801                if (!CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equalsIgnoreCase(account.getActivityStatusCode())) {
1802                    return false;
1803                }
1804            }
1805            return true;
1806        }
1807    
1808        /**
1809         * Check if PurApDocument related items are fully processed.
1810         * 
1811         * @param purapDocument
1812         * @return
1813         */
1814        protected boolean isDocumentFullyProcessed(PurchasingAccountsPayableDocument purapDocument) {
1815            for (PurchasingAccountsPayableItemAsset item : purapDocument.getPurchasingAccountsPayableItemAssets()) {
1816                if (!CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equalsIgnoreCase(item.getActivityStatusCode())) {
1817                    return false;
1818                }
1819            }
1820            return true;
1821        }
1822    
1823        /**
1824         * Build the appropriate note text being set to the purchase order document.
1825         * 
1826         * @param documentType
1827         * @param assetNumbers
1828         * @return
1829         */
1830        protected String buildNoteTextForPurApDoc(String documentType, List<Long> assetNumbers) {
1831            StringBuffer noteText = new StringBuffer();
1832    
1833            if (DocumentTypeName.ASSET_ADD_GLOBAL.equalsIgnoreCase(documentType)) {
1834                noteText.append("Asset Numbers have been created for this document: ");
1835            }
1836            else {
1837                noteText.append("Existing Asset Numbers have been applied for this document: ");
1838            }
1839    
1840            for (int i = 0; i < assetNumbers.size(); i++) {
1841                noteText.append(assetNumbers.get(i).toString());
1842                if (i < assetNumbers.size() - 1) {
1843                    noteText.append(", ");
1844                }
1845            }
1846    
1847            return noteText.toString();
1848        }
1849    
1850        /**
1851         * Acquire asset numbers from CAMS asset payment document.
1852         * 
1853         * @param documentNumber
1854         * @param assetNumbers
1855         */
1856        protected List<Long> getAssetNumbersFromAssetGlobal(String documentNumber) {
1857            List<Long> assetNumbers = new ArrayList<Long>();
1858            Map<String, String> fieldValues = new HashMap<String, String>();
1859            fieldValues.put(CamsPropertyConstants.AssetGlobalDetail.DOCUMENT_NUMBER, documentNumber);
1860            Collection<AssetGlobalDetail> assetGlobalDetails = this.getBusinessObjectService().findMatching(AssetGlobalDetail.class, fieldValues);
1861            for (AssetGlobalDetail detail : assetGlobalDetails) {
1862                assetNumbers.add(detail.getCapitalAssetNumber());
1863            }
1864            return assetNumbers;
1865        }
1866    
1867        /**
1868         * Acquire asset numbers from CAMS asset global document.
1869         * 
1870         * @param documentNumber
1871         * @param assetNumbers
1872         */
1873        protected List<Long> getAssetNumbersFromAssetPayment(String documentNumber) {
1874            List<Long> assetNumbers = new ArrayList<Long>();
1875            Map<String, String> fieldValues = new HashMap<String, String>();
1876            fieldValues.put(CamsPropertyConstants.DOCUMENT_NUMBER, documentNumber);
1877            Collection<AssetPaymentAssetDetail> paymentAssetDetails = this.getBusinessObjectService().findMatching(AssetPaymentAssetDetail.class, fieldValues);
1878            for (AssetPaymentAssetDetail detail : paymentAssetDetails) {
1879                if (ObjectUtils.isNotNull(detail.getAsset())) {
1880                    assetNumbers.add(detail.getCapitalAssetNumber());
1881                }
1882            }
1883    
1884            return assetNumbers;
1885        }
1886    
1887        /**
1888         * Query PurchasingAccountsPayableItemAsset and return the purchaseOrderIdentifier if the given documentNumber is initiated from
1889         * the PurAp line.
1890         * 
1891         * @param documentNumber
1892         * @return
1893         */
1894        protected Integer getPurchaseOrderIdentifier(String camsDocumentNumber) {
1895            Map<String, String> fieldValues = new HashMap<String, String>();
1896            fieldValues.put(CabPropertyConstants.PurchasingAccountsPayableItemAsset.CAMS_DOCUMENT_NUMBER, camsDocumentNumber);
1897            Collection<PurchasingAccountsPayableItemAsset> matchingItems = this.getBusinessObjectService().findMatching(PurchasingAccountsPayableItemAsset.class, fieldValues);
1898    
1899            for (PurchasingAccountsPayableItemAsset item : matchingItems) {
1900                if (ObjectUtils.isNull(item.getPurchasingAccountsPayableDocument())) {
1901                    item.refreshReferenceObject(CabPropertyConstants.PurchasingAccountsPayableItemAsset.PURCHASING_ACCOUNTS_PAYABLE_DOCUMENT);
1902                }
1903                return item.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier();
1904            }
1905            return null;
1906        }
1907    
1908        /**
1909         * @see org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService#getCurrentPurchaseOrderDocumentNumber(java.lang.String)
1910         */
1911        public String getCurrentPurchaseOrderDocumentNumber(String camsDocumentNumber) {
1912            Integer poId = getPurchaseOrderIdentifier(camsDocumentNumber);
1913            if (poId != null) {
1914                PurchaseOrderDocument poDocument = getPurApInfoService().getCurrentDocumentForPurchaseOrderIdentifier(poId);
1915                if (ObjectUtils.isNotNull(poDocument)) {
1916                    return poDocument.getDocumentNumber();
1917                }
1918            }
1919            return null;
1920        }
1921    
1922        /**
1923         * Activates CAB GL Lines
1924         * 
1925         * @param documentNumber String
1926         */
1927        protected void activateCabGlLines(String documentNumber) {
1928            Map<String, String> fieldValues = new HashMap<String, String>();
1929            fieldValues.put(CabPropertyConstants.PurchasingAccountsPayableItemAsset.CAMS_DOCUMENT_NUMBER, documentNumber);
1930            Collection<GeneralLedgerEntryAsset> matchingGlAssets = this.getBusinessObjectService().findMatching(GeneralLedgerEntryAsset.class, fieldValues);
1931            if (matchingGlAssets != null && !matchingGlAssets.isEmpty()) {
1932                for (GeneralLedgerEntryAsset generalLedgerEntryAsset : matchingGlAssets) {
1933                    GeneralLedgerEntry generalLedgerEntry = generalLedgerEntryAsset.getGeneralLedgerEntry();
1934                    generalLedgerEntry.setTransactionLedgerSubmitAmount(KualiDecimal.ZERO);
1935                    generalLedgerEntry.setActivityStatusCode(CabConstants.ActivityStatusCode.NEW);
1936                    this.getBusinessObjectService().save(generalLedgerEntry);
1937                    this.getBusinessObjectService().delete(generalLedgerEntryAsset);
1938                }
1939            }
1940        }
1941    
1942        /**
1943         * Activates PO Lines
1944         * 
1945         * @param documentNumber String
1946         */
1947        protected void activateCabPOLines(String documentNumber) {
1948            Map<String, String> fieldValues = new HashMap<String, String>();
1949            fieldValues.put(CabPropertyConstants.PurchasingAccountsPayableItemAsset.CAMS_DOCUMENT_NUMBER, documentNumber);
1950            Collection<PurchasingAccountsPayableItemAsset> matchingPoAssets = this.getBusinessObjectService().findMatching(PurchasingAccountsPayableItemAsset.class, fieldValues);
1951    
1952            if (matchingPoAssets != null && !matchingPoAssets.isEmpty()) {
1953                for (PurchasingAccountsPayableItemAsset itemAsset : matchingPoAssets) {
1954                    PurchasingAccountsPayableDocument purapDocument = itemAsset.getPurchasingAccountsPayableDocument();
1955                    purapDocument.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
1956                    this.getBusinessObjectService().save(purapDocument);
1957                    itemAsset.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
1958                    this.getBusinessObjectService().save(itemAsset);
1959                    List<PurchasingAccountsPayableLineAssetAccount> lineAssetAccounts = itemAsset.getPurchasingAccountsPayableLineAssetAccounts();
1960                    for (PurchasingAccountsPayableLineAssetAccount assetAccount : lineAssetAccounts) {
1961                        assetAccount.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
1962                        this.getBusinessObjectService().save(assetAccount);
1963                        GeneralLedgerEntry generalLedgerEntry = assetAccount.getGeneralLedgerEntry();
1964                        KualiDecimal submitAmount = generalLedgerEntry.getTransactionLedgerSubmitAmount();
1965                        if (submitAmount == null) {
1966                            submitAmount = KualiDecimal.ZERO;
1967                        }
1968                        submitAmount = submitAmount.subtract(assetAccount.getItemAccountTotalAmount());
1969                        generalLedgerEntry.setTransactionLedgerSubmitAmount(submitAmount);
1970                        generalLedgerEntry.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
1971                        this.getBusinessObjectService().save(generalLedgerEntry);
1972                    }
1973                }
1974            }
1975        }
1976    
1977    
1978        /**
1979         * gets the document type based on the instance of a class
1980         * 
1981         * @param accountingDocument
1982         * @return
1983         */
1984        protected String getDocumentTypeName(AccountingDocument accountingDocument) {
1985            String documentTypeName = null;
1986            if (accountingDocument instanceof YearEndGeneralErrorCorrectionDocument)
1987                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.YEAR_END_GENERAL_ERROR_CORRECTION;
1988            else if (accountingDocument instanceof YearEndDistributionOfIncomeAndExpenseDocument)
1989                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.YEAR_END_DISTRIBUTION_OF_INCOME_AND_EXPENSE;
1990            else if (accountingDocument instanceof ServiceBillingDocument)
1991                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.SERVICE_BILLING;
1992            else if (accountingDocument instanceof GeneralErrorCorrectionDocument)
1993                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.GENERAL_ERROR_CORRECTION;
1994            else if (accountingDocument instanceof CashReceiptDocument)
1995                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.CASH_RECEIPT;
1996            else if (accountingDocument instanceof AdvanceDepositDocument)
1997                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.ADVANCE_DEPOSIT;
1998            else if (accountingDocument instanceof CreditCardReceiptDocument)
1999                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.CREDIT_CARD_RECEIPT;
2000            else if (accountingDocument instanceof DistributionOfIncomeAndExpenseDocument)
2001                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE;
2002            else if (accountingDocument instanceof InternalBillingDocument)
2003                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.INTERNAL_BILLING;
2004            else if (accountingDocument instanceof ProcurementCardDocument)
2005                documentTypeName = KFSConstants.FinancialDocumentTypeCodes.PROCUREMENT_CARD;
2006            else
2007                throw new RuntimeException("Invalid FP document type.");
2008    
2009            return documentTypeName;
2010        }
2011    
2012        public ParameterService getParameterService() {
2013            return SpringContext.getBean(ParameterService.class);
2014        }
2015    
2016        public BusinessObjectService getBusinessObjectService() {
2017            return SpringContext.getBean(BusinessObjectService.class);
2018        }
2019    
2020        public DataDictionaryService getDataDictionaryService() {
2021            return SpringContext.getBean(DataDictionaryService.class);
2022        }
2023    
2024        public AssetService getAssetService() {
2025            return SpringContext.getBean(AssetService.class);
2026        }
2027    
2028        public KualiModuleService getKualiModuleService() {
2029            return SpringContext.getBean(KualiModuleService.class);
2030        }
2031    
2032        public CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
2033            return SpringContext.getBean(CapitalAssetManagementModuleService.class);
2034        }
2035    
2036        protected PurApInfoService getPurApInfoService() {
2037            return SpringContext.getBean(PurApInfoService.class);
2038        }
2039    
2040    }