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.document.service.impl;
017
018 import java.util.ArrayList;
019 import java.util.Collections;
020 import java.util.HashMap;
021 import java.util.HashSet;
022 import java.util.Iterator;
023 import java.util.List;
024 import java.util.Map;
025 import java.util.Set;
026
027 import org.apache.commons.lang.StringUtils;
028 import org.apache.log4j.Logger;
029 import org.kuali.kfs.coa.businessobject.ObjectCode;
030 import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
031 import org.kuali.kfs.module.cab.CabConstants;
032 import org.kuali.kfs.module.cab.CabPropertyConstants;
033 import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry;
034 import org.kuali.kfs.module.cab.businessobject.Pretag;
035 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableActionHistory;
036 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument;
037 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset;
038 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount;
039 import org.kuali.kfs.module.cab.dataaccess.PurApLineDao;
040 import org.kuali.kfs.module.cab.document.service.PurApInfoService;
041 import org.kuali.kfs.module.cab.document.service.PurApLineService;
042 import org.kuali.kfs.module.cab.document.web.PurApLineSession;
043 import org.kuali.kfs.module.cam.CamsConstants;
044 import org.kuali.kfs.module.cam.document.service.AssetService;
045 import org.kuali.kfs.sys.context.SpringContext;
046 import org.kuali.rice.kns.service.BusinessObjectService;
047 import org.kuali.rice.kns.util.KualiDecimal;
048 import org.kuali.rice.kns.util.ObjectUtils;
049 import org.kuali.rice.kns.util.TypedArrayList;
050 import org.springframework.transaction.annotation.Transactional;
051
052
053 /**
054 * This class provides default implementations of {@link PurApLineService}
055 */
056 @Transactional
057 public class PurApLineServiceImpl implements PurApLineService {
058 private static final Logger LOG = Logger.getLogger(PurApLineServiceImpl.class);
059 private BusinessObjectService businessObjectService;
060 private PurApLineDao purApLineDao;
061 private PurApInfoService purApInfoService;
062 private CapitalAssetManagementModuleService capitalAssetManagementModuleService;
063
064 /**
065 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#mergeLinesHasDifferentObjectSubTypes(java.util.List)
066 */
067 public boolean mergeLinesHasDifferentObjectSubTypes(List<PurchasingAccountsPayableItemAsset> mergeLines) {
068 boolean invalid = false;
069 List<String> objectSubTypeList = new ArrayList<String>();
070
071 // collect all objectSubTypes from item lines
072 for (PurchasingAccountsPayableItemAsset itemAsset : mergeLines) {
073 for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
074 account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT);
075 ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject();
076 if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) {
077 objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode());
078 }
079 }
080 }
081
082 // check if different objectSubTypes exist
083 if (!getAssetService().isObjectSubTypeCompatible(objectSubTypeList)) {
084 invalid = true;
085 }
086 return invalid;
087 }
088
089 /**
090 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#allocateLinesHasDifferentObjectSubTypes(java.util.List,
091 * org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset)
092 */
093 public boolean allocateLinesHasDifferentObjectSubTypes(List<PurchasingAccountsPayableItemAsset> targetLines, PurchasingAccountsPayableItemAsset sourceLine) {
094 boolean invalid = false;
095 List<String> objectSubTypeList = new ArrayList<String>();
096
097 // collect objectSubTypes from target item lines
098 for (PurchasingAccountsPayableItemAsset itemAsset : targetLines) {
099 for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
100 account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT);
101 ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject();
102 if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) {
103 objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode());
104 }
105 }
106 }
107
108 // collect objectSubTypes from source item line
109 if (ObjectUtils.isNotNull(sourceLine)) {
110 for (PurchasingAccountsPayableLineAssetAccount account : sourceLine.getPurchasingAccountsPayableLineAssetAccounts()) {
111 account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT);
112 ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject();
113 if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) {
114 objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode());
115 }
116 }
117 }
118
119 // check if different objectSubTypes exist
120 if (!getAssetService().isObjectSubTypeCompatible(objectSubTypeList)) {
121 invalid = true;
122 }
123 return invalid;
124 }
125
126 /**
127 * @see org.kuali.kfs.module.cab.document.service.PurApLineDocumentService#inActivateDocument(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument)
128 */
129 public void conditionallyUpdateDocumentStatusAsProcessed(PurchasingAccountsPayableDocument selectedDoc) {
130 for (PurchasingAccountsPayableItemAsset item : selectedDoc.getPurchasingAccountsPayableItemAssets()) {
131 if (item.isActive()) {
132 return;
133 }
134 }
135 // set as processed when allocate/merge and all its items are in CAMs
136 selectedDoc.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS);
137 }
138
139 /**
140 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#resetSelectedValue(java.util.List)
141 */
142 public void resetSelectedValue(List<PurchasingAccountsPayableDocument> purApDocs) {
143 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
144 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
145 item.setSelectedValue(false);
146 }
147 }
148 }
149
150 /**
151 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processAllocate(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset,
152 * java.util.List, org.kuali.kfs.module.cab.document.web.PurApLineSession, java.util.List)
153 */
154 public boolean processAllocate(PurchasingAccountsPayableItemAsset allocateSourceLine,
155 List<PurchasingAccountsPayableItemAsset> allocateTargetLines,
156 List<PurchasingAccountsPayableActionHistory> actionsTakeHistory,
157 List<PurchasingAccountsPayableDocument> purApDocs,
158 boolean initiateFromBatch) {
159
160 boolean allocatedIndicator = true;
161 // indicator of additional charge allocation
162 boolean allocateAddlChrgIndicator = allocateSourceLine.isAdditionalChargeNonTradeInIndicator() | allocateSourceLine.isTradeInAllowance();
163 // Maintain this account List for update. So accounts already allocated won't take effect for account not allocated yet.
164 List<PurchasingAccountsPayableLineAssetAccount> newAccountList = new ArrayList<PurchasingAccountsPayableLineAssetAccount>();
165
166 // For each account in the source item, allocate it to the target items.
167 for (PurchasingAccountsPayableLineAssetAccount sourceAccount : allocateSourceLine.getPurchasingAccountsPayableLineAssetAccounts()) {
168 sourceAccount.refresh();
169 // Get allocate to target account list
170 List<PurchasingAccountsPayableLineAssetAccount> targetAccounts = getAllocateTargetAccounts(sourceAccount, allocateTargetLines, allocateAddlChrgIndicator);
171 if (!targetAccounts.isEmpty()) {
172 // Percentage amount to each target account
173 allocateByItemAccountAmount(sourceAccount, targetAccounts, newAccountList, actionsTakeHistory);
174 }
175 else {
176 allocatedIndicator = false;
177 break;
178 }
179 }
180
181 if (allocatedIndicator) {
182 postAllocateProcess(allocateSourceLine, allocateTargetLines, purApDocs, newAccountList, initiateFromBatch);
183 }
184
185 return allocatedIndicator;
186 }
187
188
189 /**
190 * Process after allocate.
191 *
192 * @param selectedLineItem
193 * @param allocateTargetLines
194 * @param purApDocs
195 * @param newAccountList
196 */
197 protected void postAllocateProcess(PurchasingAccountsPayableItemAsset selectedLineItem, List<PurchasingAccountsPayableItemAsset> allocateTargetLines, List<PurchasingAccountsPayableDocument> purApDocs, List<PurchasingAccountsPayableLineAssetAccount> newAccountList, boolean initiateFromBatch) {
198 // add new account into each item list
199 addNewAccountToItemList(newAccountList);
200
201 // update total cost and unit cost.
202 updateLineItemsCost(allocateTargetLines);
203
204 if (ObjectUtils.isNotNull(selectedLineItem.getPurchasingAccountsPayableDocument())) {
205 // remove allocate source line item.
206 selectedLineItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets().remove(selectedLineItem);
207 // when an line is removed from the document, we should check if it is the only active line in the document and
208 // in-activate document if yes.
209 conditionallyUpdateDocumentStatusAsProcessed(selectedLineItem.getPurchasingAccountsPayableDocument());
210 }
211
212 // Adjust create asset and apply payment indicator only when allocate additional charges.
213 if (!initiateFromBatch && (selectedLineItem.isAdditionalChargeNonTradeInIndicator() || selectedLineItem.isTradeInAllowance())) {
214 setAssetIndicator(purApDocs);
215 }
216
217 // update status code as user modified for allocate target lines
218 if (!initiateFromBatch) {
219 for (PurchasingAccountsPayableItemAsset allocateTargetItem : allocateTargetLines) {
220 updateItemStatusAsUserModified(allocateTargetItem);
221 }
222 }
223 }
224
225 /**
226 * Build removable asset lock map from the processedItems list. We need to remove all asset locks hold be items which has been
227 * merged or allocated to other lines.
228 *
229 * @param processedItems
230 * @return
231 */
232 protected Map<String, Set> getRemovableAssetLocks(List<PurchasingAccountsPayableItemAsset> processedItems) {
233 Map<String, Set> removableAssetLocks = new HashMap<String, Set>();
234
235 for (PurchasingAccountsPayableItemAsset processedItem : processedItems) {
236 // For the time being, only for individual system, each item has its own asset numbers.
237 if (processedItem.getLockingInformation() != null && !CamsConstants.defaultLockingInformation.equals(processedItem.getLockingInformation())) {
238 addAssetLock(removableAssetLocks, processedItem);
239 }
240 else if (ObjectUtils.isNotNull(processedItem.getPurchasingAccountsPayableDocument())) {
241 // check other items if they are fully processed and can release the lock
242 List<PurchasingAccountsPayableItemAsset> remainingItems = processedItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets();
243 boolean fullyProcessed = true;
244 for (PurchasingAccountsPayableItemAsset itemAsset : remainingItems) {
245 if (!CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equalsIgnoreCase(itemAsset.getActivityStatusCode())) {
246 fullyProcessed = false;
247 break;
248 }
249 }
250 if (fullyProcessed) {
251 // All the items are either merged or allocated to other document item. We should remove the asset lock
252 // retained by this document.
253 addAssetLock(removableAssetLocks, processedItem);
254 }
255 }
256 }
257
258 processedItems.clear();
259 return removableAssetLocks;
260 }
261
262 protected void addAssetLock(Map<String, Set> removableAssetLocks, PurchasingAccountsPayableItemAsset processedItem) {
263 if (processedItem.getLockingInformation() == null) {
264 processedItem.setLockingInformation(CamsConstants.defaultLockingInformation);
265 }
266 if (removableAssetLocks.containsKey(processedItem.getDocumentNumber())) {
267 Set lockingInfoList = removableAssetLocks.get(processedItem.getDocumentNumber());
268 lockingInfoList.add(processedItem.getLockingInformation());
269 }
270 else {
271 Set lockingInfoList = new HashSet<String>();
272 lockingInfoList.add(processedItem.getLockingInformation());
273 removableAssetLocks.put(processedItem.getDocumentNumber(), lockingInfoList);
274 }
275 }
276
277 /**
278 * Reset item total cost and unit cost for each item.
279 *
280 * @param allocateTargetLines
281 */
282 protected void updateLineItemsCost(List<PurchasingAccountsPayableItemAsset> lineItems) {
283 // update target item unit cost and total cost
284 for (PurchasingAccountsPayableItemAsset item : lineItems) {
285 setLineItemCost(item);
286 }
287 }
288
289 /**
290 * update account list for each line item
291 *
292 * @param updatedAccountList
293 */
294 protected void addNewAccountToItemList(List<PurchasingAccountsPayableLineAssetAccount> newAccountList) {
295 PurchasingAccountsPayableItemAsset lineItem = null;
296 for (PurchasingAccountsPayableLineAssetAccount newAccount : newAccountList) {
297 lineItem = newAccount.getPurchasingAccountsPayableItemAsset();
298 if (ObjectUtils.isNotNull(lineItem) && ObjectUtils.isNotNull(lineItem.getPurchasingAccountsPayableLineAssetAccounts())) {
299 lineItem.getPurchasingAccountsPayableLineAssetAccounts().add(newAccount);
300 }
301 }
302 }
303
304 /**
305 * Set line item total cost and unit cost.
306 *
307 * @param item
308 */
309 protected void setLineItemCost(PurchasingAccountsPayableItemAsset item) {
310 KualiDecimal totalCost = calculateItemAssetTotalCost(item);
311 item.setTotalCost(totalCost);
312 setItemAssetUnitCost(item, totalCost);
313 }
314
315 /**
316 * Allocate one account line to target account lines percentage based on the account line amount.
317 *
318 * @param sourceAccount Account line to be allocated.
319 * @param targetAccounts Account lines which accept amount.
320 */
321 protected void allocateByItemAccountAmount(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableLineAssetAccount> targetAccounts, List<PurchasingAccountsPayableLineAssetAccount> newAccountList, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory) {
322 KualiDecimal targetAccountsTotalAmount = KualiDecimal.ZERO;
323 KualiDecimal sourceAccountTotalAmount = sourceAccount.getItemAccountTotalAmount();
324 KualiDecimal amountAllocated = KualiDecimal.ZERO;
325 KualiDecimal additionalAmount = null;
326
327 // Calculate the targetAccountTotalAmount. Ignore the sign of the account amount when proportionally allocate based on
328 // account amount
329 for (PurchasingAccountsPayableLineAssetAccount targetAccount : targetAccounts) {
330 targetAccountsTotalAmount = targetAccountsTotalAmount.add(targetAccount.getItemAccountTotalAmount().abs());
331 }
332
333 for (Iterator<PurchasingAccountsPayableLineAssetAccount> iterator = targetAccounts.iterator(); iterator.hasNext();) {
334 PurchasingAccountsPayableLineAssetAccount targetAccount = (PurchasingAccountsPayableLineAssetAccount) iterator.next();
335 if (iterator.hasNext()) {
336 // Not working on the last node. Calculate additional charge amount by percentage. Ignore the sign of the account
337 // amount when proportionally allocate based on account amount
338 additionalAmount = targetAccount.getItemAccountTotalAmount().abs().multiply(sourceAccountTotalAmount).divide(targetAccountsTotalAmount);
339 amountAllocated = amountAllocated.add(additionalAmount);
340 }
341 else {
342 // Working on the last node, set the additional charge amount to the rest of sourceAccountTotalAmount.
343 additionalAmount = sourceAccountTotalAmount.subtract(amountAllocated);
344 }
345
346 // Code below mainly handle grouping account lines if they're from the same GL.
347 PurchasingAccountsPayableLineAssetAccount newAccount = getMatchingFromAccountList(targetAccounts, sourceAccount.getGeneralLedgerAccountIdentifier(), targetAccount);
348 if (newAccount != null) {
349 // If exists the same account line and GL entry, update its itemAccountTotalAmount. This account line could be other
350 // than targetAccount, but must belong to the same line item.
351 updateAccountAmount(additionalAmount, newAccount);
352 }
353 else {
354 // If exist account just created, grouping them and update the account amount.
355 newAccount = getMatchingFromAccountList(newAccountList, sourceAccount.getGeneralLedgerAccountIdentifier(), targetAccount);
356 if (newAccount != null) {
357 updateAccountAmount(additionalAmount, newAccount);
358 }
359 else {
360 // If the target account is a different GL entry, create a new account and attach to this line item.
361 newAccount = new PurchasingAccountsPayableLineAssetAccount(targetAccount.getPurchasingAccountsPayableItemAsset(), sourceAccount.getGeneralLedgerAccountIdentifier());
362 newAccount.setItemAccountTotalAmount(additionalAmount);
363 newAccount.setGeneralLedgerEntry(sourceAccount.getGeneralLedgerEntry());
364 newAccountList.add(newAccount);
365 }
366 }
367
368 // add to action history
369 addAllocateHistory(sourceAccount, actionsTakeHistory, additionalAmount, newAccount);
370 }
371 }
372
373 /**
374 * Save allocate action into session object.
375 *
376 * @param sourceAccount
377 * @param actionsTakeHistory
378 * @param additionalAmount
379 * @param newAccount
380 */
381 protected void addAllocateHistory(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, KualiDecimal additionalAmount, PurchasingAccountsPayableLineAssetAccount newAccount) {
382 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(sourceAccount.getPurchasingAccountsPayableItemAsset(), newAccount.getPurchasingAccountsPayableItemAsset(), CabConstants.Actions.ALLOCATE);
383 newAction.setGeneralLedgerAccountIdentifier(sourceAccount.getGeneralLedgerAccountIdentifier());
384 newAction.setItemAccountTotalAmount(additionalAmount);
385 newAction.setAccountsPayableItemQuantity(sourceAccount.getPurchasingAccountsPayableItemAsset().getAccountsPayableItemQuantity());
386 actionsTakeHistory.add(newAction);
387 }
388
389
390 /**
391 * Search matching account in targetAccounts by glIdentifier.
392 *
393 * @param targetAccounts
394 * @param glIdentifier
395 * @return
396 */
397 protected PurchasingAccountsPayableLineAssetAccount getFromTargetAccountList(List<PurchasingAccountsPayableLineAssetAccount> targetAccounts, Long glIdentifier) {
398 for (PurchasingAccountsPayableLineAssetAccount account : targetAccounts) {
399 if (account.getGeneralLedgerAccountIdentifier().equals(glIdentifier)) {
400 return account;
401 }
402 }
403 return null;
404 }
405
406 /**
407 * Update targetAccount by additionalAmount.
408 *
409 * @param additionalAmount
410 * @param targetAccount
411 */
412 protected void updateAccountAmount(KualiDecimal additionalAmount, PurchasingAccountsPayableLineAssetAccount targetAccount) {
413 KualiDecimal baseAmount = targetAccount.getItemAccountTotalAmount();
414 targetAccount.setItemAccountTotalAmount(baseAmount != null ? baseAmount.add(additionalAmount) : additionalAmount);
415 }
416
417
418 /**
419 * Searching in accountList by glIdentifier for matching account which associated with the same item as targetAccount.
420 *
421 * @param accountList
422 * @param glIdentifier
423 * @param targetAccount
424 * @return
425 */
426 protected PurchasingAccountsPayableLineAssetAccount getMatchingFromAccountList(List<PurchasingAccountsPayableLineAssetAccount> accountList, Long glIdentifier, PurchasingAccountsPayableLineAssetAccount targetAccount) {
427 for (PurchasingAccountsPayableLineAssetAccount account : accountList) {
428 if (StringUtils.equalsIgnoreCase(targetAccount.getDocumentNumber(), account.getDocumentNumber()) && targetAccount.getAccountsPayableLineItemIdentifier().equals(account.getAccountsPayableLineItemIdentifier()) && targetAccount.getCapitalAssetBuilderLineNumber().equals(account.getCapitalAssetBuilderLineNumber()) && glIdentifier.equals(account.getGeneralLedgerAccountIdentifier())) {
429 return account;
430 }
431 }
432 return null;
433 }
434
435 /**
436 * Get the target account lines which will be used for allocate.
437 *
438 * @param sourceAccount
439 * @param lineItems
440 * @param addtionalCharge
441 * @return
442 */
443 protected List<PurchasingAccountsPayableLineAssetAccount> getAllocateTargetAccounts(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableItemAsset> allocateTargetLines, boolean addtionalCharge) {
444 GeneralLedgerEntry candidateEntry = null;
445 GeneralLedgerEntry sourceEntry = sourceAccount.getGeneralLedgerEntry();
446 List<PurchasingAccountsPayableLineAssetAccount> matchingAccounts = new ArrayList<PurchasingAccountsPayableLineAssetAccount>();
447 List<PurchasingAccountsPayableLineAssetAccount> allAccounts = new ArrayList<PurchasingAccountsPayableLineAssetAccount>();
448
449 // For additional charge allocation, target account selection is based on account lines with the same account number and
450 // object code. If no matching, select all account lines. For line item to line items, select all account lines as target.
451 for (PurchasingAccountsPayableItemAsset item : allocateTargetLines) {
452 for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
453 //KFSMI-5122: We need to refresh account general ledger entry so that the gl entries become visible as candidateEntry
454 account.refreshReferenceObject("generalLedgerEntry");
455 candidateEntry = account.getGeneralLedgerEntry();
456
457 if (ObjectUtils.isNotNull(candidateEntry)) {
458 // For additional charge, select matching account when account number and object code both match.
459 if (addtionalCharge && StringUtils.equalsIgnoreCase(sourceEntry.getChartOfAccountsCode(), candidateEntry.getChartOfAccountsCode()) && StringUtils.equalsIgnoreCase(sourceEntry.getAccountNumber(), candidateEntry.getAccountNumber()) && StringUtils.equalsIgnoreCase(sourceEntry.getFinancialObjectCode(), candidateEntry.getFinancialObjectCode())) {
460 matchingAccounts.add(account);
461 }
462 }
463
464 allAccounts.add(account);
465 }
466 }
467
468 return matchingAccounts.isEmpty() ? allAccounts : matchingAccounts;
469 }
470
471 /**
472 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#getAllocateTargetLines(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset,
473 * java.util.List)
474 */
475 public List<PurchasingAccountsPayableItemAsset> getAllocateTargetLines(PurchasingAccountsPayableItemAsset selectedLineItem, List<PurchasingAccountsPayableDocument> purApDocs) {
476 List<PurchasingAccountsPayableItemAsset> targetLineItems = new TypedArrayList(PurchasingAccountsPayableItemAsset.class);
477
478 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
479 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
480 // If selected Line is additional charge line, get target lines from the same document.
481 // Else if selected Line is trade in allowance, target lines are item lines with TI indicator set.
482 // Otherwise, select items with check box set.
483 if (item.isActive() && item != selectedLineItem && ((selectedLineItem.isAdditionalChargeNonTradeInIndicator() && !item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && StringUtils.equalsIgnoreCase(selectedLineItem.getDocumentNumber(), item.getDocumentNumber())) || (selectedLineItem.isTradeInAllowance() && item.isItemAssignedToTradeInIndicator()) || (item.isSelectedValue()))) {
484 targetLineItems.add(item);
485 }
486 }
487 }
488 return targetLineItems;
489 }
490
491 /**
492 * Get selected merge lines. If this is merge all action, we need to manually append all additional charge lines since no select
493 * box associated with them.
494 *
495 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#getSelectedMergeLines(boolean, java.util.List)
496 */
497 public List<PurchasingAccountsPayableItemAsset> getSelectedMergeLines(boolean isMergeAll, List<PurchasingAccountsPayableDocument> purApDocs) {
498 List<PurchasingAccountsPayableItemAsset> mergeLines = new TypedArrayList(PurchasingAccountsPayableItemAsset.class);
499 boolean excludeTradeInAllowance = false;
500
501 // Handle one exception for merge all: when we have TI allowance but no TI indicator line, we should exclude
502 if (isMergeAll && !isTradeInIndicatorExistInAllLines(purApDocs) && isTradeInAllowanceExist(purApDocs)) {
503 excludeTradeInAllowance = true;
504 }
505
506 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
507 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
508 // If not merge all action, select items are the merge lines. If it is merge all action, all lines should be
509 // candidate merge lines except trade-in allowance line when there is no trade-in indicator line.
510 if ((!isMergeAll && item.isSelectedValue()) || (isMergeAll && (!excludeTradeInAllowance || !item.isTradeInAllowance()))) {
511 mergeLines.add(item);
512 // setup non-persistent relationship from item to document.
513 item.setPurchasingAccountsPayableDocument(purApDoc);
514 }
515 }
516 }
517 return mergeLines;
518 }
519
520 /**
521 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isTradeInAllowanceExist(java.util.List)
522 */
523 public boolean isTradeInAllowanceExist(List<PurchasingAccountsPayableDocument> purApDocs) {
524 boolean tradeInAllowance = false;
525 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
526 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
527 if (item.isTradeInAllowance()) {
528 tradeInAllowance = true;
529 break;
530 }
531 }
532 }
533 return tradeInAllowance;
534 }
535
536 /**
537 * Check if TI indicator exists in all form lines
538 *
539 * @param purApDocs
540 * @return
541 */
542 protected boolean isTradeInIndicatorExistInAllLines(List<PurchasingAccountsPayableDocument> purApDocs) {
543 boolean tradeInIndicator = false;
544 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
545 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
546 if (item.isItemAssignedToTradeInIndicator()) {
547 tradeInIndicator = true;
548 break;
549 }
550 }
551 }
552 return tradeInIndicator;
553 }
554
555 /**
556 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isTradeInIndicatorExist(java.util.List)
557 */
558 public boolean isTradeInIndExistInSelectedLines(List<PurchasingAccountsPayableItemAsset> itemAssets) {
559 boolean tradeInIndicator = false;
560 for (PurchasingAccountsPayableItemAsset item : itemAssets) {
561 if (item.isItemAssignedToTradeInIndicator()) {
562 tradeInIndicator = true;
563 break;
564 }
565 }
566 return tradeInIndicator;
567 }
568
569 /**
570 * If item assets are from the same document, we can ignore additional charges pending.
571 *
572 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isAdditionalChargePending(java.util.List)
573 */
574 public boolean isAdditionalChargePending(List<PurchasingAccountsPayableItemAsset> itemAssets) {
575 boolean additionalChargePending = false;
576 Boolean diffDocment = false;
577 PurchasingAccountsPayableItemAsset firstAsset = itemAssets.get(0);
578 PurchasingAccountsPayableItemAsset lastAsset = itemAssets.get(itemAssets.size() - 1);
579
580 // Check if itemAssets are in different PurAp Document. itemAssets is a sorted list which has item assets from the same
581 // document grouping together.
582 if (ObjectUtils.isNotNull(firstAsset) && ObjectUtils.isNotNull(lastAsset) && !firstAsset.getDocumentNumber().equalsIgnoreCase(lastAsset.getDocumentNumber())) {
583 diffDocment = true;
584 }
585
586 // check if item assets from different document have additional charges not allocated yet. Bring all lines in
587 // the same document as checking candidate.
588 if (diffDocment) {
589 for (PurchasingAccountsPayableItemAsset item : itemAssets) {
590 if (ObjectUtils.isNotNull(item.getPurchasingAccountsPayableDocument())) {
591 for (PurchasingAccountsPayableItemAsset itemLine : item.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets()) {
592 if (itemLine.isAdditionalChargeNonTradeInIndicator()) {
593 additionalChargePending = true;
594 return additionalChargePending;
595 }
596 }
597 }
598 }
599 }
600 return additionalChargePending;
601 }
602
603 /**
604 * Check if the merge action is merge all.
605 *
606 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isMergeAllAction(java.util.List)
607 */
608 public boolean isMergeAllAction(List<PurchasingAccountsPayableDocument> purApDocs) {
609 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
610 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
611 // When there is one item line not selected, mergeAll is false
612 if (!item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && !item.isSelectedValue()) {
613 return false;
614 }
615 }
616 }
617 return true;
618 }
619
620 /**
621 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isAdditionalChargeExistInAllLines(java.util.List)
622 */
623 public boolean isAdditionalChargeExistInAllLines(List<PurchasingAccountsPayableDocument> purApDocs) {
624 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
625 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
626 if (item.isAdditionalChargeNonTradeInIndicator()) {
627 return true;
628 }
629 }
630 }
631 return false;
632 }
633
634 /**
635 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processMerge(java.util.List)
636 */
637 public void processMerge(List<PurchasingAccountsPayableItemAsset> mergeLines, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, boolean isMergeAll) {
638 PurchasingAccountsPayableItemAsset sourceItem = null;
639 PurchasingAccountsPayableLineAssetAccount targetAccount = null;
640 // use the first item and its accounts as the target
641 PurchasingAccountsPayableItemAsset firstItem = mergeLines.get(0);
642 List<PurchasingAccountsPayableLineAssetAccount> firstAccountList = firstItem.getPurchasingAccountsPayableLineAssetAccounts();
643
644 // Merge accounts starting from the second item to the last one.
645 for (int i = 1; i < mergeLines.size(); i++) {
646 sourceItem = mergeLines.get(i);
647 for (PurchasingAccountsPayableLineAssetAccount account : sourceItem.getPurchasingAccountsPayableLineAssetAccounts()) {
648 // Check if we can grouping the accounts. If yes, update the account amount without moving account line.
649 targetAccount = getFromTargetAccountList(firstAccountList, account.getGeneralLedgerAccountIdentifier());
650 if (targetAccount != null) {
651 updateAccountAmount(account.getItemAccountTotalAmount(), targetAccount);
652 }
653 else {
654 // move account from source to target
655 account.setDocumentNumber(firstItem.getDocumentNumber());
656 account.setAccountsPayableLineItemIdentifier(firstItem.getAccountsPayableLineItemIdentifier());
657 account.setCapitalAssetBuilderLineNumber(firstItem.getCapitalAssetBuilderLineNumber());
658 account.setPurchasingAccountsPayableItemAsset(firstItem);
659 firstAccountList.add(account);
660 }
661 }
662 }
663
664 // update action history, remove lines before merge and clean up the user input
665 postMergeProcess(mergeLines, actionsTakeHistory, isMergeAll);
666 }
667
668 /**
669 * Process after merge.
670 *
671 * @param mergeLines
672 * @param purApLineSession
673 * @param isMergeAll
674 */
675 protected void postMergeProcess(List<PurchasingAccountsPayableItemAsset> mergeLines, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, boolean isMergeAll) {
676 String actionTypeCode = isMergeAll ? CabConstants.Actions.MERGE_ALL : CabConstants.Actions.MERGE;
677 PurchasingAccountsPayableItemAsset targetItem = mergeLines.get(0);
678
679 // set unit cost and total cost
680 setLineItemCost(targetItem);
681 targetItem.setItemAssignedToTradeInIndicator(false);
682
683 // For all merge source lines(the first line is considered as target technically), remove it from the document and update in
684 // the action history.
685 for (int i = 1; i < mergeLines.size(); i++) {
686 PurchasingAccountsPayableItemAsset sourceItem = mergeLines.get(i);
687
688 // Update the action history.
689 addMergeHistory(actionsTakeHistory, actionTypeCode, sourceItem, targetItem);
690
691 if (ObjectUtils.isNotNull(sourceItem.getPurchasingAccountsPayableDocument())) {
692 // remove mergeLines from the document
693 sourceItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets().remove(sourceItem);
694 // if all active lines are merged to other line, we need to in-activate the current document
695 conditionallyUpdateDocumentStatusAsProcessed(sourceItem.getPurchasingAccountsPayableDocument());
696 }
697 }
698 // set the target item itemLineNumber for pre-tagging
699 Integer poId = targetItem.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier();
700 if (poId != null) {
701 Pretag targetPretag = getTargetPretag(mergeLines, poId);
702 if (targetPretag != null) {
703 targetItem.setItemLineNumber(targetPretag.getItemLineNumber());
704 }
705 }
706
707 // update create asset/ apply payment indicator if any of the merged lines has the indicator set.
708 updateAssetIndicatorAfterMerge(mergeLines);
709
710 // update activity status code as modified
711 updateItemStatusAsUserModified(targetItem);
712 }
713
714 /**
715 * Update create asset and apply payment indicators after merge.
716 *
717 * @param mergeLines
718 */
719 protected void updateAssetIndicatorAfterMerge(List<PurchasingAccountsPayableItemAsset> mergeLines) {
720 boolean existCreateAsset = false;
721 boolean existApplyPayment = false;
722 PurchasingAccountsPayableItemAsset targetItem = mergeLines.get(0);
723 // set indicator if any of the source item set it or target item set it.
724 for (int i = 1; i < mergeLines.size(); i++) {
725 PurchasingAccountsPayableItemAsset sourceItem = mergeLines.get(i);
726 existCreateAsset |= sourceItem.isCreateAssetIndicator();
727 existApplyPayment |= sourceItem.isApplyPaymentIndicator();
728 }
729 targetItem.setCreateAssetIndicator(targetItem.isCreateAssetIndicator() | existCreateAsset);
730 targetItem.setApplyPaymentIndicator(targetItem.isApplyPaymentIndicator() | existApplyPayment);
731 }
732
733 /**
734 * Get the first pre-tag for given itemLines
735 *
736 * @param itemLines
737 * @param purchaseOrderIdentifier
738 * @return
739 */
740 protected Pretag getTargetPretag(List<PurchasingAccountsPayableItemAsset> itemLines, Integer purchaseOrderIdentifier) {
741 for (PurchasingAccountsPayableItemAsset item : itemLines) {
742 Pretag newTag = getPreTagLineItem(purchaseOrderIdentifier, item.getItemLineNumber());
743
744 if (isPretaggingExisting(newTag))
745 return newTag;
746 }
747
748 return null;
749 }
750
751 /**
752 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isPretaggingExisting(org.kuali.kfs.module.cab.businessobject.Pretag)
753 */
754 public boolean isPretaggingExisting(Pretag newTag) {
755 return ObjectUtils.isNotNull(newTag) && newTag.getPretagDetails() != null && !newTag.getPretagDetails().isEmpty();
756 }
757
758
759 /**
760 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isMultipleTagExisting(java.lang.Integer, java.util.Set)
761 */
762 public boolean isMultipleTagExisting(Integer purchaseOrderIdentifier, Set<Integer> itemLineNumbers) {
763 Pretag firstTag = null;
764 for (Iterator iterator = itemLineNumbers.iterator(); iterator.hasNext();) {
765 Integer itemLineNumber = (Integer) iterator.next();
766 Pretag newTag = getPreTagLineItem(purchaseOrderIdentifier, itemLineNumber);
767
768 if (isPretaggingExisting(newTag))
769 if (firstTag != null) {
770 // find the second preTagging item
771 return true;
772 }
773 else {
774 firstTag = newTag;
775 }
776 }
777
778 return false;
779 }
780
781 /**
782 * Add merge action to the action history.
783 *
784 * @param purApLineSession
785 * @param isMergeAllAction
786 * @param firstItem
787 * @param item
788 */
789 protected void addMergeHistory(List<PurchasingAccountsPayableActionHistory> actionsTakenHistory, String actionTypeCode, PurchasingAccountsPayableItemAsset sourceItem, PurchasingAccountsPayableItemAsset targetItem) {
790 // create action history records for each account from the source lines.
791 for (PurchasingAccountsPayableLineAssetAccount sourceAccount : sourceItem.getPurchasingAccountsPayableLineAssetAccounts()) {
792 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(sourceItem, targetItem, actionTypeCode);
793 newAction.setAccountsPayableItemQuantity(sourceItem.getAccountsPayableItemQuantity());
794 newAction.setItemAccountTotalAmount(sourceAccount.getItemAccountTotalAmount());
795 newAction.setGeneralLedgerAccountIdentifier(sourceAccount.getGeneralLedgerAccountIdentifier());
796 actionsTakenHistory.add(newAction);
797 }
798 }
799
800
801 /**
802 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processPercentPayment(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset)
803 */
804 public void processPercentPayment(PurchasingAccountsPayableItemAsset itemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakenHistory) {
805 KualiDecimal oldQty = itemAsset.getAccountsPayableItemQuantity();
806 KualiDecimal newQty = new KualiDecimal(1);
807 // update quantity, total cost and unit cost.
808 if (oldQty.isLessThan(newQty)) {
809 itemAsset.setAccountsPayableItemQuantity(newQty);
810 setLineItemCost(itemAsset);
811 // add to action history
812 addPercentPaymentHistory(actionsTakenHistory, itemAsset, oldQty);
813 // update status code
814 updateItemStatusAsUserModified(itemAsset);
815 }
816 }
817
818
819 /**
820 * Update activity status code when percent payment/split/allocate/merge action taken.
821 *
822 * @param itemAsset
823 */
824 protected void updateItemStatusAsUserModified(PurchasingAccountsPayableItemAsset itemAsset) {
825 itemAsset.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
826 for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
827 account.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
828 }
829
830 itemAsset.getPurchasingAccountsPayableDocument().setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
831
832 }
833
834 /**
835 * Update action history for the percent payment action.
836 *
837 * @param actionsTaken
838 * @param item
839 * @param oldQty
840 */
841 protected void addPercentPaymentHistory(List<PurchasingAccountsPayableActionHistory> actionsTakenHistory, PurchasingAccountsPayableItemAsset item, KualiDecimal oldQty) {
842 // create and set up one action history record for this action
843 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(item, item, CabConstants.Actions.PERCENT_PAYMENT);
844 // record quantity before percent payment into action history
845 newAction.setAccountsPayableItemQuantity(oldQty);
846 actionsTakenHistory.add(newAction);
847 }
848
849 /**
850 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processSplit(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset)
851 */
852 public void processSplit(PurchasingAccountsPayableItemAsset splitItemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory) {
853 PurchasingAccountsPayableDocument purApDoc = splitItemAsset.getPurchasingAccountsPayableDocument();
854 // update activity status code for split item. it will be propogated to new created item and its accounts.
855 updateItemStatusAsUserModified(splitItemAsset);
856
857 // create a new item asset from the current item asset.
858 PurchasingAccountsPayableItemAsset newItemAsset = new PurchasingAccountsPayableItemAsset(splitItemAsset);
859
860 // set cab line number
861 newItemAsset.setCapitalAssetBuilderLineNumber(getMaxCabLineNumber(splitItemAsset, purApDoc) + 1);
862
863 newItemAsset.setAccountsPayableItemQuantity(splitItemAsset.getSplitQty());
864
865 // Set account list for new item asset and update current account amount value.
866 createAccountsForNewItemAsset(splitItemAsset, newItemAsset);
867 // set unit cost and total cost in new item
868 setLineItemCost(newItemAsset);
869
870 // Adjust current item asset quantity, total cost and unit cost
871 splitItemAsset.setAccountsPayableItemQuantity(splitItemAsset.getAccountsPayableItemQuantity().subtract(splitItemAsset.getSplitQty()));
872 setLineItemCost(splitItemAsset);
873
874 // add the new item into document and sort.
875 purApDoc.getPurchasingAccountsPayableItemAssets().add(newItemAsset);
876 Collections.sort(purApDoc.getPurchasingAccountsPayableItemAssets());
877
878 // Add to action history
879 addSplitHistory(splitItemAsset, newItemAsset, actionsTakeHistory);
880
881 // clear up user input
882 splitItemAsset.setSplitQty(null);
883 }
884
885
886 /**
887 * Get the max cab line #. As part of the primary key, it should be the max value among the form item list and DB.
888 *
889 * @param splitItemAsset
890 * @param purApDoc
891 * @return
892 */
893 protected int getMaxCabLineNumber(PurchasingAccountsPayableItemAsset splitItemAsset, PurchasingAccountsPayableDocument purApDoc) {
894 // get the max CAB line number in DB.
895 Integer maxDBCabLineNbr = purApLineDao.getMaxCabLineNumber(splitItemAsset.getDocumentNumber(), splitItemAsset.getAccountsPayableLineItemIdentifier());
896 // get the max CAB line number in form.
897 int availableCabLineNbr = getMaxCabLineNbrForItemInForm(purApDoc, splitItemAsset);
898
899 if (maxDBCabLineNbr.intValue() > availableCabLineNbr) {
900 availableCabLineNbr = maxDBCabLineNbr.intValue();
901 }
902 return availableCabLineNbr;
903 }
904
905 /**
906 * Search the current active items and return the max CAB line # for split new item .
907 *
908 * @param purApDoc
909 * @param currentItemAsset
910 * @return
911 */
912 protected int getMaxCabLineNbrForItemInForm(PurchasingAccountsPayableDocument purApDoc, PurchasingAccountsPayableItemAsset currentItemAsset) {
913 int maxCabLineNbr = 0;
914 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
915 if (item.getDocumentNumber().equalsIgnoreCase(currentItemAsset.getDocumentNumber()) && item.getAccountsPayableLineItemIdentifier().equals(currentItemAsset.getAccountsPayableLineItemIdentifier()) && item.getCapitalAssetBuilderLineNumber().intValue() > maxCabLineNbr) {
916 maxCabLineNbr = item.getCapitalAssetBuilderLineNumber().intValue();
917 }
918
919 }
920 return maxCabLineNbr;
921 }
922
923 /**
924 * Update action history for a split action.
925 *
926 * @param currentItemAsset
927 * @param newItemAsset
928 * @param actionsTaken
929 */
930 protected void addSplitHistory(PurchasingAccountsPayableItemAsset currentItemAsset, PurchasingAccountsPayableItemAsset newItemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakenHistory) {
931 // for each account moved from original item to new item, create one action history record
932 for (PurchasingAccountsPayableLineAssetAccount account : newItemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
933 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(currentItemAsset, newItemAsset, CabConstants.Actions.SPLIT);
934 newAction.setGeneralLedgerAccountIdentifier(account.getGeneralLedgerAccountIdentifier());
935 // quantity moved from original item to new item
936 newAction.setAccountsPayableItemQuantity(newItemAsset.getAccountsPayableItemQuantity());
937 // account amount moved to new item
938 newAction.setItemAccountTotalAmount(account.getItemAccountTotalAmount());
939 actionsTakenHistory.add(newAction);
940 }
941 }
942
943
944 /**
945 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processSaveBusinessObjects(java.util.List,
946 * org.kuali.kfs.module.cab.document.web.PurApLineSession)
947 */
948 public void processSaveBusinessObjects(List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession) {
949 // Get removable asset locks which could be generated by allocate or merge when items removed and the lock should be
950 // released.
951 Map<String, Set> removableAssetLocks = getRemovableAssetLocks(purApLineSession.getProcessedItems());
952 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
953 // auto save items(including deleted items) and accounts due to auto-update setting in OJB.
954 businessObjectService.save(purApDoc);
955 }
956 // remove asset locks
957 for (String lockingDocumentNbr : removableAssetLocks.keySet()) {
958 Set<String> lockingInfoList = removableAssetLocks.get(lockingDocumentNbr);
959 for (String lockingInfo : lockingInfoList) {
960 if (this.getCapitalAssetManagementModuleService().isAssetLockedByCurrentDocument(lockingDocumentNbr, lockingInfo)) {
961 this.getCapitalAssetManagementModuleService().deleteAssetLocks(lockingDocumentNbr, lockingInfo);
962 }
963 }
964 }
965
966 if (purApLineSession != null) {
967 // save to action history table
968 List<PurchasingAccountsPayableActionHistory> historyList = purApLineSession.getActionsTakenHistory();
969 if (historyList != null && !historyList.isEmpty()) {
970 businessObjectService.save(historyList);
971 historyList.clear();
972 }
973
974 // save to generalLedgerEntry table
975 List<GeneralLedgerEntry> glUpdateList = purApLineSession.getGlEntryUpdateList();
976 if (glUpdateList != null && !glUpdateList.isEmpty()) {
977 businessObjectService.save(glUpdateList);
978 glUpdateList.clear();
979 }
980
981 }
982 }
983
984
985 /**
986 * Create asset account list for new item asset and update the current account amount.
987 *
988 * @param oldItemAsset old line item.
989 * @param newItemAsset new line item.
990 */
991 protected void createAccountsForNewItemAsset(PurchasingAccountsPayableItemAsset currentItemAsset, PurchasingAccountsPayableItemAsset newItemAsset) {
992 KualiDecimal currentQty = currentItemAsset.getAccountsPayableItemQuantity();
993 KualiDecimal splitQty = currentItemAsset.getSplitQty();
994 List<PurchasingAccountsPayableLineAssetAccount> newAccountsList = newItemAsset.getPurchasingAccountsPayableLineAssetAccounts();
995 PurchasingAccountsPayableLineAssetAccount newAccount;
996 for (PurchasingAccountsPayableLineAssetAccount currentAccount : currentItemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
997 // create accounts for new item asset.
998 newAccount = new PurchasingAccountsPayableLineAssetAccount(newItemAsset, currentAccount.getGeneralLedgerAccountIdentifier());
999 newAccount.setItemAccountTotalAmount(currentAccount.getItemAccountTotalAmount().multiply(splitQty).divide(currentQty));
1000 newAccount.setGeneralLedgerEntry(currentAccount.getGeneralLedgerEntry());
1001 newAccountsList.add(newAccount);
1002
1003 // Adjust current account amount for split item( subtract new account amount for original amount)
1004 currentAccount.setItemAccountTotalAmount(currentAccount.getItemAccountTotalAmount().subtract(newAccount.getItemAccountTotalAmount()));
1005 }
1006 }
1007
1008
1009 /**
1010 * Set object code by the first one from the accounting lines.
1011 *
1012 * @param item Selected line item.
1013 */
1014 protected void setFirstFinancialObjectCode(PurchasingAccountsPayableItemAsset item) {
1015 String firstFinancialObjectCode = null;
1016 for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
1017 if (ObjectUtils.isNotNull(account.getGeneralLedgerEntry())) {
1018 firstFinancialObjectCode = account.getGeneralLedgerEntry().getFinancialObjectCode();
1019 break;
1020 }
1021 }
1022 item.setFirstFincialObjectCode(firstFinancialObjectCode);
1023 }
1024
1025
1026 /**
1027 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#buildPurApItemAssetList(java.util.List)
1028 */
1029 public void buildPurApItemAssetList(List<PurchasingAccountsPayableDocument> purApDocs) {
1030 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
1031 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
1032 // set item non-persistent fields from PurAp PREQ/CM item tables
1033 purApInfoService.setAccountsPayableItemsFromPurAp(item, purApDoc.getDocumentTypeCode());
1034
1035 // set line item unit cost and total cost
1036 setLineItemCost(item);
1037
1038 // set financial object code
1039 setFirstFinancialObjectCode(item);
1040 }
1041 // For display purpose, move additional charges including trade-in below item lines.
1042 Collections.sort(purApDoc.getPurchasingAccountsPayableItemAssets());
1043 }
1044
1045 // set CAMS Transaction type from PurAp
1046 purApInfoService.setCamsTransactionFromPurAp(purApDocs);
1047
1048 // set create asset/apply payment indicator which are used to control display two buttons.
1049 setAssetIndicator(purApDocs);
1050
1051 }
1052
1053
1054 /**
1055 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#getPreTagLineItem(java.lang.String, java.lang.Integer)
1056 */
1057 public Pretag getPreTagLineItem(Integer purchaseOrderIdentifier, Integer lineItemNumber) {
1058
1059 if (purchaseOrderIdentifier == null || lineItemNumber == null) {
1060 return null;
1061 }
1062
1063 Map<String, Object> pKeys = new HashMap<String, Object>();
1064
1065 pKeys.put(CabPropertyConstants.Pretag.PURCHASE_ORDER_NUMBER, purchaseOrderIdentifier);
1066 pKeys.put(CabPropertyConstants.Pretag.ITEM_LINE_NUMBER, lineItemNumber);
1067 return (Pretag) businessObjectService.findByPrimaryKey(Pretag.class, pKeys);
1068 }
1069
1070 /**
1071 * Set create asset and apply payment indicator. These two indicators are referenced by jsp to control display of these two
1072 * buttons. How to set these two indicators is based on the business rules. We need to put the following situations into
1073 * consideration. Since we move allocate additional charge allocation to CAB batch, we bring over addl charge lines only when
1074 * they are the only items in the document or they are from the FO changes. To accommodate this, we relax the rules and defined
1075 * as:
1076 * <p>
1077 * 1. If the line is trade-in allowance and without trade-in indicator items, we open these two buttons.
1078 * <p>
1079 * 2. For line items, if it has trade-in indicator set, there must be no trade-in allowance pending for allocation. Trade-in
1080 * allowance could from other document but share the same po_id.
1081 *
1082 * @param purApDocs
1083 */
1084 protected void setAssetIndicator(List<PurchasingAccountsPayableDocument> purApDocs) {
1085 // get the trade-in allowance in the form-wise
1086 boolean existTradeInAllowance = isTradeInAllowanceExist(purApDocs);
1087 // get the trade-in indicator in the form-wise
1088 boolean existTradeInIndicator = isTradeInIndicatorExistInAllLines(purApDocs);
1089 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
1090 boolean existAdditionalCharge = false;
1091 // check if within the same document, exist both additional charge lines and normal line items.
1092 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
1093 existAdditionalCharge |= item.isAdditionalChargeNonTradeInIndicator();
1094 }
1095 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
1096 // when the indicator is not set yet...
1097 if (!item.isCreateAssetIndicator() || !item.isApplyPaymentIndicator()) {
1098 // If the lines on the purchase order are all additional charge lines, or trade-in allowance then we can apply
1099 // payment, or create asset.
1100 existTradeInIndicator = item.isItemAssignedToTradeInIndicator();
1101
1102 if (item.isAdditionalChargeNonTradeInIndicator() || (item.isTradeInAllowance() && !existTradeInIndicator)) {
1103 item.setCreateAssetIndicator(true);
1104 item.setApplyPaymentIndicator(true);
1105 }
1106 // For line item(not additional charge line), if there is no pending additional charges and itself not a
1107 // trade-in indicator or it has trade-in indicator but no trade-in allowance, we allow apply payment or create
1108 // asset.
1109 else if (!existAdditionalCharge && (!item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && (!item.isItemAssignedToTradeInIndicator() || !existTradeInAllowance))) {
1110 item.setCreateAssetIndicator(true);
1111 item.setApplyPaymentIndicator(true);
1112 }
1113 }
1114 }
1115 }
1116
1117 }
1118
1119
1120 /**
1121 * Set item asset unit cost.
1122 *
1123 * @param item line item
1124 * @param totalCost total cost for this line item.
1125 */
1126 protected void setItemAssetUnitCost(PurchasingAccountsPayableItemAsset item, KualiDecimal totalCost) {
1127 // set unit cost
1128 KualiDecimal quantity = item.getAccountsPayableItemQuantity();
1129 if (quantity != null && quantity.isNonZero()) {
1130 item.setUnitCost(totalCost.divide(quantity));
1131 }
1132 }
1133
1134 /**
1135 * Calculate item asset total cost
1136 *
1137 * @param item
1138 * @return line item total cost
1139 */
1140 public KualiDecimal calculateItemAssetTotalCost(PurchasingAccountsPayableItemAsset item) {
1141 // Calculate and set total cost
1142 KualiDecimal totalCost = KualiDecimal.ZERO;
1143 for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
1144 if (account.getItemAccountTotalAmount() != null) {
1145 totalCost = totalCost.add(account.getItemAccountTotalAmount());
1146 }
1147 }
1148 return totalCost;
1149 }
1150
1151
1152 /**
1153 * Gets the businessObjectService attribute.
1154 *
1155 * @return Returns the businessObjectService.
1156 */
1157 public BusinessObjectService getBusinessObjectService() {
1158 return businessObjectService;
1159 }
1160
1161
1162 /**
1163 * Sets the businessObjectService attribute value.
1164 *
1165 * @param businessObjectService The businessObjectService to set.
1166 */
1167 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1168 this.businessObjectService = businessObjectService;
1169 }
1170
1171
1172 /**
1173 * Gets the purApLineDao attribute.
1174 *
1175 * @return Returns the purApLineDao.
1176 */
1177 public PurApLineDao getPurApLineDao() {
1178 return purApLineDao;
1179 }
1180
1181
1182 /**
1183 * Sets the purApLineDao attribute value.
1184 *
1185 * @param purApLineDao The purApLineDao to set.
1186 */
1187 public void setPurApLineDao(PurApLineDao purApLineDao) {
1188 this.purApLineDao = purApLineDao;
1189 }
1190
1191 /**
1192 * Gets the purApInfoService attribute.
1193 *
1194 * @return Returns the purApInfoService.
1195 */
1196 public PurApInfoService getPurApInfoService() {
1197 return purApInfoService;
1198 }
1199
1200 /**
1201 * Sets the purApInfoService attribute value.
1202 *
1203 * @param purApInfoService The purApInfoService to set.
1204 */
1205 public void setPurApInfoService(PurApInfoService purApInfoService) {
1206 this.purApInfoService = purApInfoService;
1207 }
1208
1209 /**
1210 * get CAMS AssetService.
1211 *
1212 * @return
1213 */
1214 protected AssetService getAssetService() {
1215 return SpringContext.getBean(AssetService.class);
1216 }
1217
1218 /**
1219 * Gets the capitalAssetManagementModuleService attribute.
1220 *
1221 * @return Returns the capitalAssetManagementModuleService.
1222 */
1223 public CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
1224 return capitalAssetManagementModuleService;
1225 }
1226
1227 /**
1228 * Sets the capitalAssetManagementModuleService attribute value.
1229 *
1230 * @param capitalAssetManagementModuleService The capitalAssetManagementModuleService to set.
1231 */
1232 public void setCapitalAssetManagementModuleService(CapitalAssetManagementModuleService capitalAssetManagementModuleService) {
1233 this.capitalAssetManagementModuleService = capitalAssetManagementModuleService;
1234 }
1235
1236
1237 }