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.HashMap;
020    import java.util.Iterator;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.log4j.Logger;
026    import org.kuali.kfs.integration.purap.CapitalAssetLocation;
027    import org.kuali.kfs.integration.purap.ItemCapitalAsset;
028    import org.kuali.kfs.module.cab.CabConstants;
029    import org.kuali.kfs.module.cab.CabPropertyConstants;
030    import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry;
031    import org.kuali.kfs.module.cab.businessobject.Pretag;
032    import org.kuali.kfs.module.cab.businessobject.PretagDetail;
033    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument;
034    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset;
035    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount;
036    import org.kuali.kfs.module.cab.document.service.PurApInfoService;
037    import org.kuali.kfs.module.cab.document.service.PurApLineDocumentService;
038    import org.kuali.kfs.module.cab.document.service.PurApLineService;
039    import org.kuali.kfs.module.cab.document.web.PurApLineSession;
040    import org.kuali.kfs.module.cam.CamsConstants;
041    import org.kuali.kfs.module.cam.CamsPropertyConstants;
042    import org.kuali.kfs.module.cam.businessobject.Asset;
043    import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
044    import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail;
045    import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail;
046    import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
047    import org.kuali.kfs.module.cam.businessobject.AssetType;
048    import org.kuali.kfs.module.cam.businessobject.defaultvalue.NextAssetNumberFinder;
049    import org.kuali.kfs.module.cam.document.AssetPaymentDocument;
050    import org.kuali.kfs.module.cam.document.service.AssetGlobalService;
051    import org.kuali.kfs.module.cam.document.service.AssetService;
052    import org.kuali.kfs.module.purap.PurapPropertyConstants;
053    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderCapitalAssetSystem;
054    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
055    import org.kuali.kfs.sys.businessobject.Building;
056    import org.kuali.kfs.sys.businessobject.Room;
057    import org.kuali.kfs.sys.context.SpringContext;
058    import org.kuali.rice.kew.exception.WorkflowException;
059    import org.kuali.rice.kns.bo.Campus;
060    import org.kuali.rice.kns.document.MaintenanceDocument;
061    import org.kuali.rice.kns.service.BusinessObjectService;
062    import org.kuali.rice.kns.service.DocumentService;
063    import org.kuali.rice.kns.service.KualiModuleService;
064    import org.kuali.rice.kns.util.KNSConstants;
065    import org.kuali.rice.kns.util.KualiDecimal;
066    import org.kuali.rice.kns.util.ObjectUtils;
067    
068    
069    /**
070     * This class provides default implementations of {@link PurApLineService}
071     */
072    public class PurApLineDocumentServiceImpl implements PurApLineDocumentService {
073        private static final Logger LOG = Logger.getLogger(PurApLineDocumentServiceImpl.class);
074        private BusinessObjectService businessObjectService;
075        private DocumentService documentService;
076        private PurApLineService purApLineService;
077        private PurApInfoService purApInfoService;
078        private AssetGlobalService assetGlobalService;
079    
080        public static final String DOCUMENT_DESC_PREFIX = "CAB created for ";
081    
082        /**
083         * @see org.kuali.kfs.module.cab.document.service.PurApLineDocumentService#processApplyPayment(PurchasingAccountsPayableItemAsset,
084         *      List, PurApLineSession, Integer)
085         */
086        public String processApplyPayment(PurchasingAccountsPayableItemAsset selectedItem, List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession, Integer requisitionIdentifer) throws WorkflowException {
087            AssetPaymentDocument newDocument = (AssetPaymentDocument) documentService.getNewDocument(AssetPaymentDocument.class);
088            if (ObjectUtils.isNotNull(selectedItem) && ObjectUtils.isNotNull(selectedItem.getPurchasingAccountsPayableDocument())) {
089                newDocument.getDocumentHeader().setDocumentDescription(DOCUMENT_DESC_PREFIX + selectedItem.getPurchasingAccountsPayableDocument().getDocumentTypeCode() + " " + selectedItem.getDocumentNumber());
090            }
091            // set assetPaymentDetail list
092            createAssetPaymentDetails(newDocument.getSourceAccountingLines(), selectedItem, newDocument.getDocumentNumber(), requisitionIdentifer);
093    
094            // If PurAp user entered capitalAssetNumbers, include them in the Asset Payment Document.
095            if (selectedItem.getPurApItemAssets() != null && !selectedItem.getPurApItemAssets().isEmpty()) {
096                createAssetPaymentAssetDetails(newDocument.getAssetPaymentAssetDetail(), selectedItem, newDocument.getDocumentNumber());
097    
098            }
099            // set origin code in the Asset Payment Document
100            newDocument.setCapitalAssetBuilderOriginIndicator(true);
101            documentService.saveDocument(newDocument);
102    
103            postProcessCreatingDocument(selectedItem, purApDocs, purApLineSession, newDocument.getDocumentNumber());
104            return newDocument.getDocumentNumber();
105        }
106    
107    
108        /**
109         * Create AssetPaymentAssetDetail List for assetPaymentDocument.
110         * 
111         * @param assetPaymentAssetDetails
112         * @param selectedItem
113         * @param documentNumber
114         */
115        protected void createAssetPaymentAssetDetails(List assetPaymentAssetDetails, PurchasingAccountsPayableItemAsset selectedItem, String documentNumber) {
116            for (ItemCapitalAsset capitalAssetNumber : selectedItem.getPurApItemAssets()) {
117                // check if capitalAssetNumber is a valid value or not.
118                if (isAssetNumberValid(capitalAssetNumber.getCapitalAssetNumber())) {
119                    AssetPaymentAssetDetail assetDetail = new AssetPaymentAssetDetail();
120                    assetDetail.setDocumentNumber(documentNumber);
121    
122                    assetDetail.setCapitalAssetNumber(capitalAssetNumber.getCapitalAssetNumber());
123                    assetDetail.refreshReferenceObject(CamsPropertyConstants.AssetPaymentAssetDetail.ASSET);
124    
125                    AssetService assetService = SpringContext.getBean(AssetService.class);
126                    Asset candidateAsset = assetDetail.getAsset();
127                    // asset must be an active & not retired. Duplication check is done during feeding asset numbers from PurAp.
128                    if (ObjectUtils.isNotNull(candidateAsset) && assetService.isCapitalAsset(candidateAsset) && !assetService.isAssetRetired(candidateAsset)) {
129                        assetDetail.setPreviousTotalCostAmount(assetDetail.getAsset().getTotalCostAmount());
130                        assetPaymentAssetDetails.add(assetDetail);
131                    }
132    
133                }
134            }
135        }
136    
137    
138        /**
139         * Check the asset table if given capitalAssetNumber is valid or not.
140         * 
141         * @param capitalAssetNumber
142         * @return
143         */
144        protected boolean isAssetNumberValid(Long capitalAssetNumber) {
145            Map pKeys = new HashMap<String, Object>();
146    
147            pKeys.put(CamsPropertyConstants.Asset.CAPITAL_ASSET_NUMBER, capitalAssetNumber);
148    
149            Asset asset = (Asset) businessObjectService.findByPrimaryKey(Asset.class, pKeys);
150    
151            return ObjectUtils.isNotNull(asset);
152        }
153    
154    
155        /**
156         * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processCreateAsset(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset,
157         *      org.kuali.kfs.module.cab.document.web.struts.PurApLineForm)
158         */
159        public String processCreateAsset(PurchasingAccountsPayableItemAsset selectedItem, List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession, Integer requisitionIdentifier) throws WorkflowException {
160            // Create new CAMS asset global document
161            MaintenanceDocument newDocument = (MaintenanceDocument) documentService.getNewDocument(CamsConstants.DocumentTypeName.ASSET_ADD_GLOBAL);
162            newDocument.getNewMaintainableObject().setMaintenanceAction(KNSConstants.MAINTENANCE_NEW_ACTION);
163            if (ObjectUtils.isNotNull(selectedItem) && ObjectUtils.isNotNull(selectedItem.getPurchasingAccountsPayableDocument())) {
164                newDocument.getDocumentHeader().setDocumentDescription(DOCUMENT_DESC_PREFIX + selectedItem.getPurchasingAccountsPayableDocument().getDocumentTypeCode() + " " + selectedItem.getDocumentNumber());
165            }
166    
167            // populate pre-tagging entry
168            Integer poId = selectedItem.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier();
169            Pretag preTag = purApLineService.getPreTagLineItem(poId, selectedItem.getItemLineNumber());
170    
171            // create asset global BO instance
172            AssetGlobal assetGlobal = createAssetGlobal(selectedItem, newDocument.getDocumentNumber(), preTag, requisitionIdentifier);
173    
174            // save asset global BO to the document
175            newDocument.getNewMaintainableObject().setBusinessObject(assetGlobal);
176            documentService.saveDocument(newDocument);
177    
178            postProcessCreatingDocument(selectedItem, purApDocs, purApLineSession, newDocument.getDocumentNumber());
179    
180            // Save for in-active pre-tag detail if it got feed into CAMS
181            if (isItemPretagged(preTag)) {
182                businessObjectService.save(preTag);
183            }
184            return newDocument.getDocumentNumber();
185        }
186    
187    
188        /**
189         * Process item line, cab document after creating CAMs document.
190         * 
191         * @param selectedItem
192         * @param purApForm
193         * @param purApLineSession
194         * @param documentNumber
195         */
196        protected void postProcessCreatingDocument(PurchasingAccountsPayableItemAsset selectedItem, List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession, String documentNumber) {
197            // save CAMS document number in CAB
198            selectedItem.setCapitalAssetManagementDocumentNumber(documentNumber);
199    
200            // in-activate item, item account and glEntry(conditionally)
201            inActivateItem(selectedItem);
202    
203            // update submit amount in the associated general ledger entries.
204            updateGlEntrySubmitAmount(selectedItem, purApLineSession.getGlEntryUpdateList());
205    
206            // in-activate document if all the associated items are inactive.
207            if (ObjectUtils.isNotNull(selectedItem.getPurchasingAccountsPayableDocument())) {
208                // update document status code as 'Enroute' when all its items are in CAMs. Link reference from item to document should
209                // be set in PurApLineAction.getSelectedLineItem().
210                conditionalyUpdateDocumentStatusAsEnroute(selectedItem.getPurchasingAccountsPayableDocument());
211            }
212    
213            // persistent to the table
214            purApLineService.processSaveBusinessObjects(purApDocs, purApLineSession);
215            // In-activate general ledger afterwards because we don't maintain the non-persistent relationship from GL to account, so
216            // account need to persistent changes first.
217            List<GeneralLedgerEntry> glEntryUpdatesList = getGlEntryInActivedList(selectedItem);
218            if (glEntryUpdatesList != null && !glEntryUpdatesList.isEmpty()) {
219                businessObjectService.save(glEntryUpdatesList);
220            }
221        }
222    
223    
224        /**
225         * set doc status as enroute when all its items are in CAMs
226         * 
227         * @param selectedDoc
228         */
229        protected void conditionalyUpdateDocumentStatusAsEnroute(PurchasingAccountsPayableDocument selectedDoc) {
230            for (PurchasingAccountsPayableItemAsset item : selectedDoc.getPurchasingAccountsPayableItemAssets()) {
231                if (item.isActive()) {
232                    return;
233                }
234            }
235    
236            selectedDoc.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
237        }
238    
239    
240        /**
241         * Update transactionLedgerSubmitAmount in the associated generalLedgerEntry for each item account.
242         * 
243         * @param selectedItem
244         */
245        protected void updateGlEntrySubmitAmount(PurchasingAccountsPayableItemAsset selectedItem, List glEntryList) {
246            GeneralLedgerEntry glEntry = null;
247            for (PurchasingAccountsPayableLineAssetAccount account : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
248                glEntry = account.getGeneralLedgerEntry();
249    
250                if (ObjectUtils.isNotNull(glEntry)) {
251                    // Add account amount to GL entry submit amount.
252                    if (glEntry.getTransactionLedgerSubmitAmount() != null) {
253                        glEntry.setTransactionLedgerSubmitAmount(glEntry.getTransactionLedgerSubmitAmount().add(account.getItemAccountTotalAmount()));
254                    }
255                    else {
256                        glEntry.setTransactionLedgerSubmitAmount(new KualiDecimal(account.getItemAccountTotalAmount().toString()));
257                    }
258                }
259                // add to the session for persistence
260                glEntryList.add(glEntry);
261            }
262        }
263    
264    
265        /**
266         * Build asset details/shared details/unique details lists for new asset global document
267         * 
268         * @param selectedItem
269         * @param newDocument
270         * @param assetGlobal
271         */
272        protected void setAssetGlobalDetails(PurchasingAccountsPayableItemAsset selectedItem, AssetGlobal assetGlobal, Pretag preTag, PurchaseOrderCapitalAssetSystem capitalAssetSystem) {
273            // build assetGlobalDetail list( will be used for creating unique details list at the same time)
274            List<AssetGlobalDetail> assetDetailsList = assetGlobal.getAssetGlobalDetails();
275            // shared location details list
276            List<AssetGlobalDetail> sharedDetails = assetGlobal.getAssetSharedDetails();
277            for (int i = 0; i < selectedItem.getAccountsPayableItemQuantity().intValue(); i++) {
278                AssetGlobalDetail assetGlobalDetail = new AssetGlobalDetail();
279                assetGlobalDetail.setDocumentNumber(assetGlobal.getDocumentNumber());
280                assetGlobalDetail.setCapitalAssetNumber(NextAssetNumberFinder.getLongValue());
281                assetDetailsList.add(assetGlobalDetail);
282                // build assetSharedDetails and assetGlobalUniqueDetails list. There two lists will be used to rebuild
283                // assetGlobalDetails list when AssetGlobalMaintainableImpl.prepareForSave() is called during save the document.
284                AssetGlobalDetail sharedDetail = new AssetGlobalDetail();
285                // added as unique detail
286                sharedDetail.getAssetGlobalUniqueDetails().add(assetGlobalDetail);
287                sharedDetails.add(sharedDetail);
288            }
289    
290            // feeding data from pre-tag details into shared location details list and unique detail list
291            if (isItemPretagged(preTag)) {
292                setAssetDetailFromPreTag(preTag, sharedDetails, assetDetailsList);
293            }
294    
295            // feeding location data from PurAp into assetGlobalDetail List.
296            if (!isItemFullyPretagged(preTag, assetGlobal) && ObjectUtils.isNotNull(capitalAssetSystem)) {
297                setAssetGlobalDetailFromPurAp(capitalAssetSystem, sharedDetails);
298            }
299        }
300    
301        /**
302         * Check if the all new assets get pre-tagged by pre-tagging.
303         * 
304         * @param preTag
305         * @param assetGlobal
306         * @return
307         */
308        protected boolean isItemFullyPretagged(Pretag preTag, AssetGlobal assetGlobal) {
309            if (isItemPretagged(preTag)) {
310                List<PretagDetail> pretagDetails = preTag.getPretagDetails();
311                int pretagSize = 0;
312                for (PretagDetail pretagDetail : pretagDetails) {
313                    if (pretagDetail.isActive()) {
314                        pretagSize++;
315                    }
316                }
317                return pretagSize >= assetGlobal.getAssetSharedDetails().size();
318            }
319            return false;
320        }
321    
322    
323        /**
324         * Set asset global detail location information from PurAp input. In this method, no grouping for shared location because
325         * AssetGlobalMaintainableImpl.processAfterRetrieve() will group the shared location anyway...
326         * 
327         * @param capitalAssetSystem
328         * @param assetDetailsList
329         */
330        protected void setAssetGlobalDetailFromPurAp(PurchaseOrderCapitalAssetSystem capitalAssetSystem, List<AssetGlobalDetail> assetSharedDetail) {
331            List<CapitalAssetLocation> capitalAssetLocations = capitalAssetSystem.getCapitalAssetLocations();
332    
333            if (ObjectUtils.isNotNull(capitalAssetLocations) && !capitalAssetLocations.isEmpty()) {
334                Iterator<CapitalAssetLocation> locationIterator = capitalAssetLocations.iterator();
335                int locationQuantity = 0;
336                CapitalAssetLocation assetLocation = null;
337                for (AssetGlobalDetail assetDetail : assetSharedDetail) {
338                    // if it's already pre-tagged, skip it.
339                    if (StringUtils.isNotEmpty(assetDetail.getCampusCode())) {
340                        continue;
341                    }
342    
343                    // Each line item can have multiple locations and each location can have a quantity value with it.
344                    if (locationQuantity <= 0 && locationIterator.hasNext()) {
345                        // when we consume the current location quantity, we need to move to the next PurAp location.
346                        assetLocation = locationIterator.next();
347                        // initialize location quantity by PurAp setting
348                        if (assetLocation.getItemQuantity() != null) {
349                            locationQuantity = assetLocation.getItemQuantity().intValue();
350                        }
351                        else {
352                            // if Purap not set item quantity, we set it to 1.
353                            locationQuantity = 1;
354                        }
355                    }
356                    else if (locationQuantity <= 0 && !locationIterator.hasNext()) {
357                        // Consume the current location quantity and no more PurAp locations can be used, stop here.
358                        break;
359                    }
360                    // set PurAp asset location into asset global document
361                    setNewAssetByPurApLocation(assetLocation, assetDetail);
362    
363                    locationQuantity--;
364                }
365            }
366    
367        }
368    
369        /**
370         * Set asset global detail by PurAp asset location.
371         * 
372         * @param assetLocation
373         * @param assetDetail
374         */
375        protected void setNewAssetByPurApLocation(CapitalAssetLocation assetLocation, AssetGlobalDetail assetDetail) {
376            String campusCode = assetLocation.getCampusCode();
377            // Set campus code only when it is a valid value. Otherwise, when save document, invalid data will violate data integrity
378            // and block save.
379            if (!StringUtils.isBlank(campusCode) && checkCampusCodeValid(campusCode)) {
380                assetDetail.setCampusCode(campusCode);
381    
382                // for on-campus
383                if (!assetLocation.isOffCampusIndicator()) {
384                    String buildingCode = assetLocation.getBuildingCode();
385                    // Set building code only when it is a valid value. Otherwise, when save document, invalid data will violate data
386                    // integrity and block save.
387                    if (!StringUtils.isBlank(buildingCode) && checkBuildingCodeValid(campusCode, buildingCode)) {
388                        assetDetail.setBuildingCode(buildingCode);
389    
390                        String buildingRoomNumber = assetLocation.getBuildingRoomNumber();
391                        // Set building room number only when it is a valid value. Otherwise, when save document, invalid data will
392                        // violate data integrity and block save.
393                        if (!StringUtils.isBlank(buildingRoomNumber) && checkBuildingRoomNumberValid(campusCode, buildingCode, buildingRoomNumber)) {
394                            assetDetail.setBuildingRoomNumber(buildingRoomNumber);
395                        }
396                    }
397                }
398                else {
399                    // off-campus
400                    assetDetail.setOffCampusCityName(assetLocation.getCapitalAssetCityName());
401                    assetDetail.setOffCampusAddress(assetLocation.getCapitalAssetLine1Address());
402                    assetDetail.setOffCampusCountryCode(assetLocation.getCapitalAssetCountryCode());
403                    assetDetail.setOffCampusStateCode(assetLocation.getCapitalAssetStateCode());
404                    assetDetail.setOffCampusZipCode(assetLocation.getCapitalAssetPostalCode());
405                }
406            }
407        }
408    
409    
410        /**
411         * Check the given buildingCode and campusCode valid.
412         * 
413         * @param campusCode
414         * @param buildingCode
415         * @param buildingRoomNumber
416         * @return
417         */
418        protected boolean checkBuildingRoomNumberValid(String campusCode, String buildingCode, String buildingRoomNumber) {
419            Map<String, Object> pKeys = new HashMap<String, Object>();
420            pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAMPUS_CODE, campusCode);
421            pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.BUILDING_CODE, buildingCode);
422            pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.BUILDING_ROOM_NUMBER, buildingRoomNumber);
423            Room room = (Room) this.getBusinessObjectService().findByPrimaryKey(Room.class, pKeys);
424            return ObjectUtils.isNotNull(room) && room.isActive();
425        }
426    
427    
428        /**
429         * Check the given buildingCode and campusCode valid.
430         * 
431         * @param buildingCode
432         * @return
433         */
434        protected boolean checkBuildingCodeValid(String campusCode, String buildingCode) {
435            Map<String, Object> pKeys = new HashMap<String, Object>();
436            pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAMPUS_CODE, campusCode);
437            pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.BUILDING_CODE, buildingCode);
438            Building building = (Building) this.getBusinessObjectService().findByPrimaryKey(Building.class, pKeys);
439            return ObjectUtils.isNotNull(building) && building.isActive();
440        }
441    
442    
443        /**
444         * check the given campus code existing and active status.
445         * 
446         * @param campusCode
447         * @return
448         */
449        protected boolean checkCampusCodeValid(String campusCode) {
450            Map<String, Object> criteria = new HashMap<String, Object>();
451            criteria.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAMPUS_CODE, campusCode);
452            Campus campus = (Campus) SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(Campus.class).getExternalizableBusinessObject(Campus.class, criteria);
453            return ObjectUtils.isNotNull(campus) && campus.isActive();
454        }
455    
456        /**
457         * Feeding data into assetGlobalDetail list from preTagDetail
458         * 
459         * @param preTag
460         * @param assetDetailsList
461         */
462        protected void setAssetDetailFromPreTag(Pretag preTag, List<AssetGlobalDetail> assetSharedDetails, List<AssetGlobalDetail> assetUniqueDetails) {
463            Iterator<AssetGlobalDetail> sharedDetailsIterator = assetSharedDetails.iterator();
464            Iterator<AssetGlobalDetail> uniqueDetailsIterator = assetUniqueDetails.iterator();
465            for (PretagDetail preTagDetail : preTag.getPretagDetails()) {
466                if (!preTagDetail.isActive()) {
467                    continue;
468                }
469                if (sharedDetailsIterator.hasNext()) {
470                    // set shared location details
471                    AssetGlobalDetail sharedDetail = sharedDetailsIterator.next();
472                    sharedDetail.setBuildingCode(preTagDetail.getBuildingCode());
473                    sharedDetail.setBuildingRoomNumber(preTagDetail.getBuildingRoomNumber());
474                    sharedDetail.setBuildingSubRoomNumber(preTagDetail.getBuildingSubRoomNumber());
475                    sharedDetail.setCampusCode(preTagDetail.getCampusCode());
476                    // In-activate pre-tagging detail, and will be persistent to the DB.
477                    preTagDetail.setActive(false);
478                }
479                if (uniqueDetailsIterator.hasNext()) {
480                    // set asset unique detail
481                    AssetGlobalDetail uniqueDetail = uniqueDetailsIterator.next();
482                    uniqueDetail.setGovernmentTagNumber(preTagDetail.getGovernmentTagNumber());
483                    uniqueDetail.setNationalStockNumber(preTagDetail.getNationalStockNumber());
484                    uniqueDetail.setCampusTagNumber(preTagDetail.getCampusTagNumber());
485                    uniqueDetail.setOrganizationInventoryName(preTag.getOrganizationInventoryName());
486                    uniqueDetail.setSerialNumber(preTagDetail.getSerialNumber());
487                    uniqueDetail.setRepresentativeUniversalIdentifier(preTag.getRepresentativeUniversalIdentifier());
488                    // In-activate pre-tagging detail and will be persistent to the DB.
489                    preTagDetail.setActive(false);
490                }
491            }
492            // In-activate preTag if possible.
493            inActivatePreTag(preTag);
494        }
495    
496        /**
497         * In-activate preTag if all its preTagDetail entry are inactive.
498         * 
499         * @param preTag
500         */
501        protected void inActivatePreTag(Pretag preTag) {
502            // get the number of inactive pre-tag detail.
503            int inActiveCounter = 0;
504            for (PretagDetail preTagDetail : preTag.getPretagDetails()) {
505                if (!preTagDetail.isActive()) {
506                    inActiveCounter++;
507                }
508            }
509            // if the number of inactive preTagDetail is equal or greater than (when quantityInvoiced is decimal) quantityInvoiced,
510            // in-activate the preTag active field.
511            if (preTag.getQuantityInvoiced().isLessEqual(new KualiDecimal(inActiveCounter))) {
512                preTag.setActive(false);
513            }
514        }
515    
516        /**
517         * Build asset payment details list for new asset global document.
518         * 
519         * @param selectedItem
520         * @param assetGlobal
521         * @param requisitionIdentifier
522         */
523        protected void createAssetPaymentDetails(List<AssetPaymentDetail> assetPaymentList, PurchasingAccountsPayableItemAsset selectedItem, String documentNumber, Integer requisitionIdentifier) {
524            int seq = 1;
525    
526            for (PurchasingAccountsPayableLineAssetAccount account : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
527                GeneralLedgerEntry glEntry = account.getGeneralLedgerEntry();
528    
529                if (ObjectUtils.isNotNull(glEntry)) {
530                    AssetPaymentDetail assetPaymentDetail = new AssetPaymentDetail();
531                    // initialize payment detail fields
532                    assetPaymentDetail.setDocumentNumber(documentNumber);
533                    assetPaymentDetail.setSequenceNumber(Integer.valueOf(seq++));
534                    assetPaymentDetail.setChartOfAccountsCode(glEntry.getChartOfAccountsCode());
535                    assetPaymentDetail.setAccountNumber(replaceFiller(glEntry.getAccountNumber()));
536                    assetPaymentDetail.setSubAccountNumber(replaceFiller(glEntry.getSubAccountNumber()));
537                    assetPaymentDetail.setFinancialObjectCode(replaceFiller(glEntry.getFinancialObjectCode()));
538                    assetPaymentDetail.setFinancialSubObjectCode(replaceFiller(glEntry.getFinancialSubObjectCode()));
539                    assetPaymentDetail.setProjectCode(replaceFiller(glEntry.getProjectCode()));
540                    assetPaymentDetail.setOrganizationReferenceId(glEntry.getOrganizationReferenceId());
541                    assetPaymentDetail.setPostingYear(glEntry.getUniversityFiscalYear());
542                    assetPaymentDetail.setPostingPeriodCode(glEntry.getUniversityFiscalPeriodCode());
543                    assetPaymentDetail.setExpenditureFinancialSystemOriginationCode(replaceFiller(glEntry.getFinancialSystemOriginationCode()));
544                    assetPaymentDetail.setExpenditureFinancialDocumentNumber(glEntry.getDocumentNumber());
545                    assetPaymentDetail.setExpenditureFinancialDocumentTypeCode(replaceFiller(glEntry.getFinancialDocumentTypeCode()));
546                    assetPaymentDetail.setExpenditureFinancialDocumentPostedDate(glEntry.getTransactionDate());
547                    assetPaymentDetail.setAmount(account.getItemAccountTotalAmount());
548                    assetPaymentDetail.setPurchaseOrderNumber(replaceFiller(glEntry.getReferenceFinancialDocumentNumber()));
549                    assetPaymentDetail.setRequisitionNumber(requisitionIdentifier.toString());
550                    assetPaymentDetail.setTransferPaymentIndicator(false);
551    
552                    // add to payment list
553                    assetPaymentList.add(assetPaymentDetail);
554                }
555            }
556        }
557    
558    
559        /**
560         * In-activate item, item Account and generalLedgerEntry active indicator.
561         * 
562         * @param selectedItem
563         * @param glEntryList
564         */
565        protected void inActivateItem(PurchasingAccountsPayableItemAsset selectedItem) {
566            // in-active each account.
567            for (PurchasingAccountsPayableLineAssetAccount selectedAccount : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
568                selectedAccount.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
569            }
570            // in-activate selected Item
571            selectedItem.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
572        }
573    
574        /**
575         * Update GL Entry status as "enroute" if all its amount are consumed by submit CAMs document.Return the general ledger entry
576         * changes as a list.
577         * 
578         * @param glEntryList
579         * @param selectedAccount
580         * @param glEntry
581         */
582        protected List<GeneralLedgerEntry> getGlEntryInActivedList(PurchasingAccountsPayableItemAsset selectedItem) {
583            List<GeneralLedgerEntry> glEntryUpdateList = new ArrayList<GeneralLedgerEntry>();
584            for (PurchasingAccountsPayableLineAssetAccount selectedAccount : selectedItem.getPurchasingAccountsPayableLineAssetAccounts()) {
585                GeneralLedgerEntry glEntry = selectedAccount.getGeneralLedgerEntry();
586                KualiDecimal relatedAccountAmount = KualiDecimal.ZERO;
587    
588                // get persistent account list which should be save before hand
589                glEntry.refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.PURAP_LINE_ASSET_ACCOUNTS);
590                // check if all accounts are inactive status
591                for (PurchasingAccountsPayableLineAssetAccount account : glEntry.getPurApLineAssetAccounts()) {
592                    if (!account.isActive()) {
593                        relatedAccountAmount = relatedAccountAmount.add(account.getItemAccountTotalAmount());
594                    }
595                }
596    
597                // if one account shows active, won't in-activate this general ledger entry.
598                if (relatedAccountAmount.compareTo(glEntry.getAmount()) == 0) {
599                    glEntry.setActivityStatusCode(CabConstants.ActivityStatusCode.ENROUTE);
600                    glEntryUpdateList.add(glEntry);
601                }
602            }
603            return glEntryUpdateList;
604        }
605    
606    
607        protected String replaceFiller(String val) {
608            return val == null ? "" : val.trim().replaceAll("-", "");
609        }
610    
611        /**
612         * Create AssetGlobal BO and feed data from pre-asset tagging table.
613         * 
614         * @param selectedItem
615         * @param newDocument
616         * @param preTag
617         * @return
618         */
619        protected AssetGlobal createAssetGlobal(PurchasingAccountsPayableItemAsset selectedItem, String documentNumber, Pretag preTag, Integer requisitionIdentifier) {
620            // instantiate AssetGlobal BO
621            AssetGlobal assetGlobal = new AssetGlobal();
622            assetGlobal.setDocumentNumber(documentNumber);
623            assetGlobal.setCapitalAssetDescription(selectedItem.getAccountsPayableLineItemDescription());
624            assetGlobal.setConditionCode(CamsConstants.Asset.CONDITION_CODE_E);
625            assetGlobal.setAcquisitionTypeCode(getAssetGlobalService().getNewAcquisitionTypeCode());
626            assetGlobal.setInventoryStatusCode(CamsConstants.InventoryStatusCode.CAPITAL_ASSET_ACTIVE_IDENTIFIABLE);
627            // set vendor name from Purchase Order Document
628            PurchaseOrderDocument purApdocument = this.getPurApInfoService().getCurrentDocumentForPurchaseOrderIdentifier(selectedItem.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier());
629            if (purApdocument != null) {
630                assetGlobal.setVendorName(purApdocument.getVendorName());
631            }
632            // set origin code in the Asset Payment Document
633            assetGlobal.setCapitalAssetBuilderOriginIndicator(true);
634    
635            PurchaseOrderCapitalAssetSystem capitalAssetSystem = null;
636            // check and set if purAp has new asset information
637            if (selectedItem.getCapitalAssetSystemIdentifier() != null) {
638                capitalAssetSystem = findCapitalAssetSystem(selectedItem.getCapitalAssetSystemIdentifier());
639                if (ObjectUtils.isNotNull(capitalAssetSystem)) {
640                    setAssetGlobalFromPurAp(assetGlobal, capitalAssetSystem);
641                }
642            }
643            // feeding data from pre-asset tagging table into assetGlboal. Here, if there are data overlap in pretag and PurAp, we
644            // respect data in Pretagging.
645            if (isItemPretagged(preTag)) {
646                setAssetGlobalFromPreTag(preTag, assetGlobal);
647            }
648    
649            // set asset global detail list
650            setAssetGlobalDetails(selectedItem, assetGlobal, preTag, capitalAssetSystem);
651    
652            // Set Organization Inventory Name for each asset unique detail from PO
653            setOrgInventoryNameForAssetDetail(assetGlobal.getAssetGlobalDetails(), purApdocument);
654    
655            // build payments list for asset global
656            createAssetPaymentDetails(assetGlobal.getAssetPaymentDetails(), selectedItem, documentNumber, requisitionIdentifier);
657    
658            // set total cost
659            setAssetGlobalTotalCost(assetGlobal);
660            // Set Asset Global organization owner account, which is the account that contributed the most dollars.
661            setAssetGlobalOrgOwnerAccount(assetGlobal);
662    
663            return assetGlobal;
664        }
665    
666        /**
667         * Set organization inventory name for each asset detail by PO Contact name or if empty, by Requestor Name.
668         * 
669         * @param assetGlobalDetails
670         * @param purApdocument
671         */
672        protected void setOrgInventoryNameForAssetDetail(List<AssetGlobalDetail> assetGlobalDetails, PurchaseOrderDocument purApdocument) {
673            if (ObjectUtils.isNotNull(purApdocument)) {
674                String orgInventoryName = purApdocument.getInstitutionContactName();
675    
676                if (StringUtils.isBlank(orgInventoryName)) {
677                    orgInventoryName = purApdocument.getRequestorPersonName();
678                }
679    
680                for (AssetGlobalDetail assetGlobalDetail : assetGlobalDetails) {
681                    assetGlobalDetail.setOrganizationInventoryName(orgInventoryName);
682                }
683            }
684        }
685    
686    
687        /**
688         * check if item is pre-tagged already.
689         * 
690         * @param preTag
691         * @return
692         */
693        protected boolean isItemPretagged(Pretag preTag) {
694            return ObjectUtils.isNotNull(preTag) && preTag.isActive() && ObjectUtils.isNotNull(preTag.getPretagDetails()) && !preTag.getPretagDetails().isEmpty();
695        }
696    
697    
698        /**
699         * Set asset information from PurAp PurchaseOrderCapitalAssetSystem.
700         * 
701         * @param assetGlobal
702         * @param capitalAssetSystem
703         */
704        protected void setAssetGlobalFromPurAp(AssetGlobal assetGlobal, PurchaseOrderCapitalAssetSystem capitalAssetSystem) {
705            assetGlobal.setManufacturerName(capitalAssetSystem.getCapitalAssetManufacturerName());
706            assetGlobal.setManufacturerModelNumber(capitalAssetSystem.getCapitalAssetModelDescription());
707            String capitalAssetTypeCode = capitalAssetSystem.getCapitalAssetTypeCode();
708            if (!StringUtils.isBlank(capitalAssetTypeCode) && checkCapitalAssetTypeCodeExist(capitalAssetTypeCode)) {
709                assetGlobal.setCapitalAssetTypeCode(capitalAssetSystem.getCapitalAssetTypeCode());
710            }
711        }
712    
713    
714        /**
715         * check the given capital asset type code exists in CAM
716         * 
717         * @param capitalAssetTypeCode
718         * @return
719         */
720        protected boolean checkCapitalAssetTypeCodeExist(String capitalAssetTypeCode) {
721            Map<String, Object> pKeys = new HashMap<String, Object>();
722            pKeys.put(CabPropertyConstants.AssetGlobalDocumentCreate.CAPITAL_ASSET_TYPE_CODE, capitalAssetTypeCode);
723            AssetType assetType = (AssetType) this.getBusinessObjectService().findByPrimaryKey(AssetType.class, pKeys);
724            return ObjectUtils.isNotNull(assetType);
725        }
726    
727    
728        /**
729         * Get PurAp PurchaseOrderCapitalAssetSystem Object if exists.
730         * 
731         * @param capitalAssetSystemIdentifier
732         * @return
733         */
734        protected PurchaseOrderCapitalAssetSystem findCapitalAssetSystem(Integer capitalAssetSystemIdentifier) {
735            Map pKeys = new HashMap<String, Object>();
736    
737            pKeys.put(PurapPropertyConstants.CAPITAL_ASSET_SYSTEM_IDENTIFIER, capitalAssetSystemIdentifier);
738            return (PurchaseOrderCapitalAssetSystem) businessObjectService.findByPrimaryKey(PurchaseOrderCapitalAssetSystem.class, pKeys);
739        }
740    
741    
742        /**
743         * Set Asset Global org owner account and chart code. It's assigned by selecting the account that contributed the most dollars
744         * on the payment request.
745         * 
746         * @param assetGlobal
747         */
748        protected void setAssetGlobalOrgOwnerAccount(AssetGlobal assetGlobal) {
749            AssetPaymentDetail maxCostPayment = null;
750            // get the maximum payment cost
751            for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
752                if (maxCostPayment == null || assetPaymentDetail.getAmount().isGreaterThan(maxCostPayment.getAmount())) {
753                    maxCostPayment = assetPaymentDetail;
754                }
755            }
756    
757            if (maxCostPayment != null) {
758                assetGlobal.setOrganizationOwnerAccountNumber(maxCostPayment.getAccountNumber());
759                assetGlobal.setOrganizationOwnerChartOfAccountsCode(maxCostPayment.getChartOfAccountsCode());
760            }
761        }
762    
763    
764        /**
765         * Set Asset Global total cost amount.
766         * 
767         * @param assetGlobal
768         */
769        protected void setAssetGlobalTotalCost(AssetGlobal assetGlobal) {
770            KualiDecimal totalCost = KualiDecimal.ZERO;
771            for (AssetPaymentDetail assetPaymentDetail : assetGlobal.getAssetPaymentDetails()) {
772                totalCost = totalCost.add(assetPaymentDetail.getAmount());
773            }
774    
775            assetGlobal.setTotalCostAmount(totalCost);
776        }
777    
778    
779        /**
780         * Feeding data from preTag and set into asset global for shared information. PreTag data may override PurAp asset data since
781         * the strategy choose to respect Pretagging
782         * 
783         * @param preTag
784         * @param assetGlobal
785         */
786        protected void setAssetGlobalFromPreTag(Pretag preTag, AssetGlobal assetGlobal) {
787            if (StringUtils.isNotBlank(preTag.getManufacturerName())) {
788                assetGlobal.setManufacturerName(preTag.getManufacturerName());
789            }
790            if (StringUtils.isNotBlank(preTag.getManufacturerModelNumber())) {
791                assetGlobal.setManufacturerModelNumber(preTag.getManufacturerModelNumber());
792            }
793    
794            if (StringUtils.isNotBlank(preTag.getCapitalAssetTypeCode())) {
795                assetGlobal.setCapitalAssetTypeCode(preTag.getCapitalAssetTypeCode());
796            }
797            assetGlobal.setOrganizationText(preTag.getOrganizationText());
798            assetGlobal.setRepresentativeUniversalIdentifier(preTag.getRepresentativeUniversalIdentifier());
799            if (StringUtils.isNotBlank(preTag.getVendorName())) {
800                assetGlobal.setVendorName(preTag.getVendorName());
801            }
802            // acquisition type code is set to "P"(Pre-asset tagging)
803            assetGlobal.setAcquisitionTypeCode(CamsConstants.AssetGlobal.PRE_TAGGING_ACQUISITION_TYPE_CODE);
804        }
805    
806        /**
807         * Gets the businessObjectService attribute.
808         * 
809         * @return Returns the businessObjectService.
810         */
811        public BusinessObjectService getBusinessObjectService() {
812            return businessObjectService;
813        }
814    
815        /**
816         * Sets the businessObjectService attribute value.
817         * 
818         * @param businessObjectService The businessObjectService to set.
819         */
820        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
821            this.businessObjectService = businessObjectService;
822        }
823    
824        /**
825         * Gets the documentService attribute.
826         * 
827         * @return Returns the documentService.
828         */
829        public DocumentService getDocumentService() {
830            return documentService;
831        }
832    
833    
834        /**
835         * Sets the documentService attribute value.
836         * 
837         * @param documentService The documentService to set.
838         */
839        public void setDocumentService(DocumentService documentService) {
840            this.documentService = documentService;
841        }
842    
843        /**
844         * Gets the purApLineService attribute.
845         * 
846         * @return Returns the purApLineService.
847         */
848        public PurApLineService getPurApLineService() {
849            return purApLineService;
850        }
851    
852        /**
853         * Sets the purApLineService attribute value.
854         * 
855         * @param purApLineService The purApLineService to set.
856         */
857        public void setPurApLineService(PurApLineService purApLineService) {
858            this.purApLineService = purApLineService;
859        }
860    
861    
862        /**
863         * Gets the purApInfoService attribute.
864         * 
865         * @return Returns the purApInfoService.
866         */
867        public PurApInfoService getPurApInfoService() {
868            return purApInfoService;
869        }
870    
871    
872        /**
873         * Sets the purApInfoService attribute value.
874         * 
875         * @param purApInfoService The purApInfoService to set.
876         */
877        public void setPurApInfoService(PurApInfoService purApInfoService) {
878            this.purApInfoService = purApInfoService;
879        }
880    
881    
882        private AssetGlobalService getAssetGlobalService() {
883            return assetGlobalService;
884        }
885    
886    
887        public void setAssetGlobalService(AssetGlobalService assetGlobalService) {
888            this.assetGlobalService = assetGlobalService;
889        }
890    }