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 }