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.cam.document;
017    
018    import java.sql.Date;
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.kuali.kfs.coa.businessobject.ObjectCode;
026    import org.kuali.kfs.coa.service.ObjectCodeService;
027    import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
028    import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
029    import org.kuali.kfs.module.cab.CabPropertyConstants;
030    import org.kuali.kfs.module.cab.businessobject.PretagDetail;
031    import org.kuali.kfs.module.cam.CamsConstants;
032    import org.kuali.kfs.module.cam.CamsPropertyConstants;
033    import org.kuali.kfs.module.cam.businessobject.Asset;
034    import org.kuali.kfs.module.cam.businessobject.AssetDepreciationConvention;
035    import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
036    import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail;
037    import org.kuali.kfs.module.cam.businessobject.AssetOrganization;
038    import org.kuali.kfs.module.cam.businessobject.AssetPayment;
039    import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
040    import org.kuali.kfs.module.cam.businessobject.AssetType;
041    import org.kuali.kfs.module.cam.businessobject.defaultvalue.NextAssetNumberFinder;
042    import org.kuali.kfs.module.cam.document.gl.AssetGlobalGeneralLedgerPendingEntrySource;
043    import org.kuali.kfs.module.cam.document.service.AssetDateService;
044    import org.kuali.kfs.module.cam.document.service.AssetGlobalService;
045    import org.kuali.kfs.module.cam.document.validation.impl.AssetGlobalRule;
046    import org.kuali.kfs.module.cam.util.KualiDecimalUtils;
047    import org.kuali.kfs.sys.KFSConstants;
048    import org.kuali.kfs.sys.KFSPropertyConstants;
049    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
050    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
051    import org.kuali.kfs.sys.context.SpringContext;
052    import org.kuali.kfs.sys.document.LedgerPostingMaintainable;
053    import org.kuali.rice.kns.bo.DocumentHeader;
054    import org.kuali.rice.kns.bo.PersistableBusinessObject;
055    import org.kuali.rice.kns.document.MaintenanceDocument;
056    import org.kuali.rice.kns.document.MaintenanceLock;
057    import org.kuali.rice.kns.service.BusinessObjectService;
058    import org.kuali.rice.kns.service.DateTimeService;
059    import org.kuali.rice.kns.util.KNSConstants;
060    import org.kuali.rice.kns.util.KualiDecimal;
061    import org.kuali.rice.kns.util.ObjectUtils;
062    import org.kuali.rice.kns.util.TypedArrayList;
063    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
064    
065    /**
066     * This class overrides the base {@link KualiGlobalMaintainableImpl} to generate the specific maintenance locks for Global assets
067     */
068    public class AssetGlobalMaintainableImpl extends LedgerPostingMaintainable {
069        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AssetGlobalMaintainableImpl.class);
070    
071        protected static final String REQUIRES_REVIEW = "RequiresReview";
072    
073        /**
074         * Lock on purchase order document since post processor will update PO document by adding notes.
075         * 
076         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#getWorkflowEngineDocumentIdsToLock()
077         */
078        @Override
079        public List<Long> getWorkflowEngineDocumentIdsToLock() {
080            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
081            if (ObjectUtils.isNotNull(assetGlobal) && assetGlobal.isCapitalAssetBuilderOriginIndicator()) {
082                String poDocId = SpringContext.getBean(CapitalAssetBuilderModuleService.class).getCurrentPurchaseOrderDocumentNumber(this.documentNumber);
083                if (StringUtils.isNotBlank(poDocId)) {
084                    List<Long> documentIds = new ArrayList<Long>();
085                    documentIds.add(new Long(poDocId));
086                    return documentIds;
087                }
088            }
089            return null;
090        }
091    
092        /**
093         * If the Add Asset Global document is submit from CAB, bypass all the approvers.
094         */
095        @Override
096        protected boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException {
097            if (REQUIRES_REVIEW.equals(nodeName))
098                return !isAccountAndOrganizationReviewRequired();
099            throw new UnsupportedOperationException("Cannot answer split question for this node you call \"" + nodeName + "\"");
100        }
101    
102        /**
103         * check whether or not isCapitalAssetBuilderOriginIndicator
104         */
105        protected boolean isAccountAndOrganizationReviewRequired() {
106            return ((AssetGlobal) getBusinessObject()).isCapitalAssetBuilderOriginIndicator();
107        }
108    
109    
110        /**
111         * Get Asset from AssetGlobal
112         * 
113         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterNew(org.kuali.rice.kns.document.MaintenanceDocument,
114         *      java.util.Map)
115         */
116        @Override
117        public void processAfterNew(MaintenanceDocument document, Map<String, String[]> parameters) {
118    
119            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
120    
121            // set "asset number" and "type code" from URL
122            setSeparateSourceCapitalAssetParameters(assetGlobal, parameters);
123            setFinancialDocumentTypeCode(assetGlobal, parameters);
124    
125            // populate required fields for "Asset Separate" doc
126            if (getAssetGlobalService().isAssetSeparate(assetGlobal)) {
127                Asset asset = getAsset(assetGlobal);
128                AssetOrganization assetOrganization = getAssetOrganization(assetGlobal);
129                populateAssetSeparateAssetDetails(assetGlobal, asset, assetOrganization);
130                populateAssetSeparatePaymentDetails(assetGlobal, asset);
131                populateAssetLocationTabInformation(asset);
132                AssetGlobalRule.validateAssetTotalCostMatchesPaymentTotalCost(assetGlobal);
133    
134                if (getAssetGlobalService().isAssetSeparateByPayment(assetGlobal)) {
135                    AssetGlobalRule.validateAssetAlreadySeparated(assetGlobal.getSeparateSourceCapitalAssetNumber());
136                }
137                // populate doc header description with the doc type
138                document.getDocumentHeader().setDocumentDescription(CamsConstants.AssetSeparate.SEPARATE_AN_ASSET_DESCRIPTION);
139            }
140    
141            super.processAfterNew(document, parameters);
142        }
143    
144        @Override
145        public void setGenerateDefaultValues(String docTypeName) {
146        }
147    
148        @Override
149        public void setupNewFromExisting(MaintenanceDocument document, Map<String, String[]> parameters) {
150            super.setupNewFromExisting(document, parameters);
151    
152            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
153    
154            assetGlobal.setLastInventoryDate(SpringContext.getBean(DateTimeService.class).getCurrentSqlDate());
155        }
156    
157        /**
158         * Get Asset from AssetGlobal
159         * 
160         * @param assetGlobal
161         * @return Asset
162         */
163        private Asset getAsset(AssetGlobal assetGlobal) {
164            HashMap<Object, Object> map = new HashMap<Object, Object>();
165            map.put(CamsPropertyConstants.Asset.CAPITAL_ASSET_NUMBER, assetGlobal.getSeparateSourceCapitalAssetNumber());
166            Asset asset = (Asset) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Asset.class, map);
167            return asset;
168        }
169    
170        /**
171         * Get AssetOrganization from AssetGlobal
172         * 
173         * @param assetGlobal
174         * @return AssetOrganization
175         */
176        private AssetOrganization getAssetOrganization(AssetGlobal assetGlobal) {
177            HashMap<Object, Object> map = new HashMap<Object, Object>();
178            map.put(CamsPropertyConstants.Asset.CAPITAL_ASSET_NUMBER, assetGlobal.getSeparateSourceCapitalAssetNumber());
179            AssetOrganization assetOrganization = (AssetOrganization) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(AssetOrganization.class, map);
180            return assetOrganization;
181        }
182    
183        /**
184         * Populate Asset Details for Asset Separate document
185         * 
186         * @param assetGlobal
187         * @param asset
188         * @param assetOrganization
189         */
190        private void populateAssetSeparateAssetDetails(AssetGlobal assetGlobal, Asset asset, AssetOrganization assetOrganization) {
191            assetGlobal.setOrganizationOwnerAccountNumber(asset.getOrganizationOwnerAccountNumber());
192            assetGlobal.setOrganizationOwnerChartOfAccountsCode(asset.getOrganizationOwnerChartOfAccountsCode());
193            assetGlobal.setAgencyNumber(asset.getAgencyNumber());
194            assetGlobal.setAcquisitionTypeCode(asset.getAcquisitionTypeCode());
195            assetGlobal.setInventoryStatusCode(asset.getInventoryStatusCode());
196            assetGlobal.setConditionCode(asset.getConditionCode());
197            assetGlobal.setCapitalAssetDescription(asset.getCapitalAssetDescription());
198            assetGlobal.setCapitalAssetTypeCode(asset.getCapitalAssetTypeCode());
199            assetGlobal.setVendorName(asset.getVendorName());
200            assetGlobal.setManufacturerName(asset.getManufacturerName());
201            assetGlobal.setManufacturerModelNumber(asset.getManufacturerModelNumber());
202            if (ObjectUtils.isNotNull(assetOrganization)) {
203                assetGlobal.setOrganizationText(assetOrganization.getOrganizationText());
204            }
205            // added in case of NULL date in DB
206            if (asset.getLastInventoryDate() == null) {
207                assetGlobal.setLastInventoryDate(SpringContext.getBean(DateTimeService.class).getCurrentSqlDate());
208            }
209            else {
210                assetGlobal.setLastInventoryDate(new java.sql.Date(asset.getLastInventoryDate().getTime()));
211            }
212            assetGlobal.setCreateDate(asset.getCreateDate());
213            assetGlobal.setCapitalAssetInServiceDate(asset.getCapitalAssetInServiceDate());
214            assetGlobal.setLandCountyName(asset.getLandCountyName());
215            assetGlobal.setLandAcreageSize(asset.getLandAcreageSize());
216            assetGlobal.setLandParcelNumber(asset.getLandParcelNumber());
217    
218            assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.ORGANIZATION_OWNER_ACCOUNT);
219        }
220    
221        /**
222         * Populate Asset Payment Details for Asset Separate document. It will do this whether we are separating by asset or payment. If
223         * it is by asset it picks up all the payments and sets the total amount on the document of that per the asset. If it is by
224         * payment it picks only the payment out we are interested in and set the document total amount to that payment only.
225         * 
226         * @param assetGlobal
227         * @param asset
228         */
229        private void populateAssetSeparatePaymentDetails(AssetGlobal assetGlobal, Asset asset) {
230            // clear and create temp AssetPaymentDetail list
231            assetGlobal.getAssetPaymentDetails().clear();
232            List<AssetPaymentDetail> newAssetPaymentDetailList = assetGlobal.getAssetPaymentDetails();
233    
234            if (!getAssetGlobalService().isAssetSeparateByPayment(assetGlobal)) {
235                // Separate by Asset. Pick all payments up
236    
237                for (AssetPayment assetPayment : asset.getAssetPayments()) {
238    
239                    // create new AssetPaymentDetail
240                    AssetPaymentDetail assetPaymentDetail = new AssetPaymentDetail(assetPayment);
241    
242                    // add assetPaymentDetail to AssetPaymentDetail list
243                    newAssetPaymentDetailList.add(assetPaymentDetail);
244                }
245    
246                // Set total amount per asset
247                assetGlobal.setTotalCostAmount(asset.getTotalCostAmount());
248                assetGlobal.setSeparateSourceRemainingAmount(asset.getTotalCostAmount());
249            }
250            else {
251                for (AssetPayment assetPayment : asset.getAssetPayments()) {
252                    // Separate by Payment. Pick only the appropriate payment up and then break
253    
254                    if (assetPayment.getPaymentSequenceNumber().equals(assetGlobal.getSeparateSourcePaymentSequenceNumber())) {
255                        // create new AssetPaymentDetail
256                        AssetPaymentDetail assetPaymentDetail = new AssetPaymentDetail(assetPayment);
257    
258                        // add assetPaymentDetail to AssetPaymentDetail list
259                        newAssetPaymentDetailList.add(assetPaymentDetail);
260    
261                        // Set total amount per payment
262                        assetGlobal.setTotalCostAmount(assetPayment.getAccountChargeAmount());
263                        assetGlobal.setSeparateSourceRemainingAmount(assetPayment.getAccountChargeAmount());
264    
265                        break;
266                    }
267                }
268            }
269            assetGlobal.setSeparateSourceTotalAmount(KualiDecimal.ZERO);
270    
271            // set AssetGlobal payment details with new payment details
272            assetGlobal.setAssetPaymentDetails(newAssetPaymentDetailList);
273        }
274    
275        /**
276         * Set capital asset number and payment sequence number from URL on the AssetGlobal BO. It only does so if each is available.
277         * 
278         * @see org.kuali.module.cams.lookup.AssetLookupableHelperServiceImpl#getSeparateUrl(BusinessObject)
279         * @see org.kuali.module.cams.lookup.AssetPaymentLookupableHelperServiceImpl#getSeparateUrl(BusinessObject)
280         * @param assetGlobal
281         * @param parameters
282         */
283        private void setSeparateSourceCapitalAssetParameters(AssetGlobal assetGlobal, Map<String, String[]> parameters) {
284            String[] separateSourceCapitalAssetNumber = parameters.get(CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET_NUMBER);
285            if (separateSourceCapitalAssetNumber != null) {
286                assetGlobal.setSeparateSourceCapitalAssetNumber(Long.parseLong(separateSourceCapitalAssetNumber[0].toString()));
287            }
288    
289            String[] separateSourcePaymentSequenceNumber = parameters.get(CamsPropertyConstants.AssetGlobal.SEPERATE_SOURCE_PAYMENT_SEQUENCE_NUMBER);
290            if (separateSourcePaymentSequenceNumber != null) {
291                assetGlobal.setSeparateSourcePaymentSequenceNumber(Integer.parseInt(separateSourcePaymentSequenceNumber[0].toString()));
292            }
293        }
294    
295        /**
296         * Set document type code from URL.
297         * 
298         * @see org.kuali.module.cams.lookup.AssetLookupableHelperServiceImpl#getSeparateUrl(BusinessObject)
299         * @param assetGlobal
300         * @param parameters
301         */
302        private void setFinancialDocumentTypeCode(AssetGlobal assetGlobal, Map<String, String[]> parameters) {
303            String[] financialDocumentTypeCode = parameters.get(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE);
304            if (financialDocumentTypeCode != null) {
305                assetGlobal.setFinancialDocumentTypeCode(financialDocumentTypeCode[0].toString());
306            }
307        }
308    
309        /**
310         * Hook for quantity and setting asset numbers.
311         * 
312         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#addNewLineToCollection(java.lang.String)
313         */
314        @Override
315        public void addNewLineToCollection(String collectionName) {
316            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
317    
318            if (CamsPropertyConstants.AssetGlobal.ASSET_PAYMENT_DETAILS.equalsIgnoreCase(collectionName)) {
319                handAssetPaymentsCollection(collectionName, assetGlobal);
320            }
321            if (CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS.equalsIgnoreCase(collectionName)) {
322                handleAssetSharedDetailsCollection(collectionName, assetGlobal);
323            }
324            int sharedDetailsIndex = assetGlobal.getAssetSharedDetails().size() - 1;
325            if (sharedDetailsIndex > -1 && (CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS + "[" + sharedDetailsIndex + "]." + CamsPropertyConstants.AssetGlobalDetail.ASSET_UNIQUE_DETAILS).equalsIgnoreCase(collectionName)) {
326                handleAssetUniqueCollection(collectionName, assetGlobal);
327            }
328            super.addNewLineToCollection(collectionName);
329        }
330    
331        /**
332         * Sets required fields with specific values when an individual unique asset added.
333         * 
334         * @param collectionName
335         */
336        private void handleAssetUniqueCollection(String collectionName, AssetGlobal assetGlobal) {
337            AssetGlobalDetail assetGlobalDetail = (AssetGlobalDetail) newCollectionLines.get(collectionName);
338    
339            if (ObjectUtils.isNotNull(assetGlobalDetail)) {
340                assetGlobalDetail.setCapitalAssetNumber(NextAssetNumberFinder.getLongValue());
341    
342                // if not set, populate unique asset fields using original asset data. "Asset Separate" doc (location tab)
343                if (ObjectUtils.isNotNull(assetGlobal)) {
344                    if (getAssetGlobalService().isAssetSeparate(assetGlobal)) {
345                        if (assetGlobalDetail.getCapitalAssetTypeCode() == null) {
346                            assetGlobalDetail.setCapitalAssetTypeCode(assetGlobal.getCapitalAssetTypeCode());
347                        }
348                        if (assetGlobalDetail.getCapitalAssetDescription() == null) {
349                            assetGlobalDetail.setCapitalAssetDescription(assetGlobal.getCapitalAssetDescription());
350                        }
351                        if (assetGlobalDetail.getManufacturerName() == null) {
352                            assetGlobalDetail.setManufacturerName(assetGlobal.getManufacturerName());
353                        }
354                        if (assetGlobalDetail.getSeparateSourceAmount() == null) {
355                            assetGlobalDetail.setSeparateSourceAmount(KualiDecimal.ZERO);
356                        }
357                    }
358                }
359            }
360        }
361    
362        /**
363         * Sets required fields with specific values when multiple unique assets added (i.e. field "Quantity Of Assets To Be Created").
364         * 
365         * @param collectionName
366         * @param assetGlobal
367         */
368        private void handleAssetSharedDetailsCollection(String collectionName, AssetGlobal assetGlobal) {
369            AssetGlobalDetail assetGlobalDetail = (AssetGlobalDetail) newCollectionLines.get(collectionName);
370            Integer locationQuantity = assetGlobalDetail.getLocationQuantity();
371            while (locationQuantity != null && locationQuantity > 0) {
372                AssetGlobalDetail newAssetUnique = new AssetGlobalDetail();
373                newAssetUnique.setCapitalAssetNumber(NextAssetNumberFinder.getLongValue());
374    
375                // populate unique asset fields using original asset data. "Asset Separate" doc (location tab)
376                if (getAssetGlobalService().isAssetSeparate(assetGlobal)) {
377                    newAssetUnique.setCapitalAssetTypeCode(assetGlobal.getCapitalAssetTypeCode());
378                    newAssetUnique.setCapitalAssetDescription(assetGlobal.getCapitalAssetDescription());
379                    newAssetUnique.setManufacturerName(assetGlobal.getManufacturerName());
380                    newAssetUnique.setOrganizationInventoryName(this.getAsset(assetGlobal).getOrganizationInventoryName());
381                    newAssetUnique.setSeparateSourceAmount(KualiDecimal.ZERO);
382                }
383                assetGlobalDetail.getAssetGlobalUniqueDetails().add(newAssetUnique);
384                newAssetUnique.setNewCollectionRecord(true);
385                locationQuantity--;
386            }
387        }
388    
389        /**
390         * Sets the default values in some of the fields of the asset payment section
391         * 
392         * @param collectionName
393         * @param assetGlobal
394         */
395        private void handAssetPaymentsCollection(String collectionName, AssetGlobal assetGlobal) {
396            AssetPaymentDetail assetPaymentDetail = (AssetPaymentDetail) newCollectionLines.get(collectionName);
397            if (assetPaymentDetail != null) {
398                assetPaymentDetail.setSequenceNumber(assetGlobal.incrementFinancialDocumentLineNumber());
399                // Set for document number and document type code
400                if (getAssetGlobalService().existsInGroup(getAssetGlobalService().getNonNewAcquisitionCodeGroup(), assetGlobal.getAcquisitionTypeCode())) {
401                    assetPaymentDetail.setExpenditureFinancialDocumentNumber(documentNumber);
402                    assetPaymentDetail.setExpenditureFinancialDocumentTypeCode(CamsConstants.DocumentTypeName.ASSET_ADD_GLOBAL);
403                    assetPaymentDetail.setExpenditureFinancialSystemOriginationCode(KFSConstants.ORIGIN_CODE_KUALI);
404                }
405            }
406        }
407    
408    
409        /**
410         * We are using a substitute mechanism for asset locking which can lock on assets when rule check passed. Return empty list from
411         * this method.
412         * 
413         * @see org.kuali.rice.kns.maintenance.Maintainable#generateMaintenanceLocks()
414         */
415        @Override
416        public List<MaintenanceLock> generateMaintenanceLocks() {
417            return new ArrayList<MaintenanceLock>();
418        }
419    
420        protected CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
421            return SpringContext.getBean(CapitalAssetManagementModuleService.class);
422        }
423    
424    
425        /**
426         * @see org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl#prepareForSave()
427         */
428        @Override
429        public void prepareForSave() {
430            super.prepareForSave();
431            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
432            List<AssetGlobalDetail> assetSharedDetails = assetGlobal.getAssetSharedDetails();
433            List<AssetGlobalDetail> newDetails = new TypedArrayList(AssetGlobalDetail.class);
434            AssetGlobalDetail newAssetGlobalDetail = null;
435            if (!assetSharedDetails.isEmpty() && !assetSharedDetails.get(0).getAssetGlobalUniqueDetails().isEmpty()) {
436    
437                for (AssetGlobalDetail locationDetail : assetSharedDetails) {
438                    List<AssetGlobalDetail> assetGlobalUniqueDetails = locationDetail.getAssetGlobalUniqueDetails();
439    
440                    for (AssetGlobalDetail detail : assetGlobalUniqueDetails) {
441                        // read from location and set it to detail
442                        if (ObjectUtils.isNotNull(locationDetail.getCampusCode())) {
443                            detail.setCampusCode(locationDetail.getCampusCode().toUpperCase());
444                        }
445                        else {
446                            detail.setCampusCode(locationDetail.getCampusCode());
447                        }
448                        if (ObjectUtils.isNotNull(locationDetail.getBuildingCode())) {
449                            detail.setBuildingCode(locationDetail.getBuildingCode().toUpperCase());
450                        }
451                        else {
452                            detail.setBuildingCode(locationDetail.getBuildingCode());
453                        }
454                        detail.setBuildingRoomNumber(locationDetail.getBuildingRoomNumber());
455                        detail.setBuildingSubRoomNumber(locationDetail.getBuildingSubRoomNumber());
456                        detail.setOffCampusName(locationDetail.getOffCampusName());
457                        detail.setOffCampusAddress(locationDetail.getOffCampusAddress());
458                        detail.setOffCampusCityName(locationDetail.getOffCampusCityName());
459                        detail.setOffCampusStateCode(locationDetail.getOffCampusStateCode());
460                        detail.setOffCampusCountryCode(locationDetail.getOffCampusCountryCode());
461                        detail.setOffCampusZipCode(locationDetail.getOffCampusZipCode());
462                        newDetails.add(detail);
463                    }
464                }
465            }
466    
467            if (assetGlobal.getCapitalAssetTypeCode() != null) {
468                assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.CAPITAL_ASSET_TYPE);
469                AssetType capitalAssetType = assetGlobal.getCapitalAssetType();
470                if (ObjectUtils.isNotNull(capitalAssetType)) {
471                    if (capitalAssetType.getDepreciableLifeLimit() != null && capitalAssetType.getDepreciableLifeLimit().intValue() != 0) {
472                        assetGlobal.setCapitalAssetInServiceDate(assetGlobal.getCreateDate() == null ? SpringContext.getBean(DateTimeService.class).getCurrentSqlDate() : assetGlobal.getCreateDate());
473                    }
474                    else {
475                        assetGlobal.setCapitalAssetInServiceDate(null);
476                    }
477                    computeDepreciationDate(assetGlobal);
478                }
479            }
480            assetGlobal.getAssetGlobalDetails().clear();
481            assetGlobal.setAssetGlobalDetails(newDetails);
482        }
483    
484        /**
485         * computes depreciation date
486         * 
487         * @param assetGlobal
488         */
489        private void computeDepreciationDate(AssetGlobal assetGlobal) {
490            List<AssetPaymentDetail> assetPaymentDetails = assetGlobal.getAssetPaymentDetails();
491            if (assetPaymentDetails != null && !assetPaymentDetails.isEmpty()) {
492    
493                LOG.debug("Compute depreciation date based on asset type, depreciation convention and in-service date");
494                AssetPaymentDetail firstAssetPaymentDetail = assetPaymentDetails.get(0);
495                ObjectCode objectCode = SpringContext.getBean(ObjectCodeService.class).getByPrimaryId(firstAssetPaymentDetail.getPostingYear(), firstAssetPaymentDetail.getChartOfAccountsCode(), firstAssetPaymentDetail.getFinancialObjectCode());
496                if (ObjectUtils.isNotNull(objectCode)) {
497                    Map<String, String> primaryKeys = new HashMap<String, String>();
498                    primaryKeys.put(CamsPropertyConstants.AssetDepreciationConvention.FINANCIAL_OBJECT_SUB_TYPE_CODE, objectCode.getFinancialObjectSubTypeCode());
499                    AssetDepreciationConvention depreciationConvention = (AssetDepreciationConvention) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(AssetDepreciationConvention.class, primaryKeys);
500                    Date depreciationDate = SpringContext.getBean(AssetDateService.class).computeDepreciationDate(assetGlobal.getCapitalAssetType(), depreciationConvention, assetGlobal.getCapitalAssetInServiceDate());
501                    assetGlobal.setCapitalAssetDepreciationDate(depreciationDate);
502                }
503            }
504        }
505    
506        /**
507         * @see org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl#processAfterRetrieve()
508         */
509        @Override
510        public void processAfterRetrieve() {
511            super.processAfterRetrieve();
512            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
513            assetGlobal.refresh();
514            assetGlobal.refreshReferenceObject(CamsPropertyConstants.AssetGlobal.SEPARATE_SOURCE_CAPITAL_ASSET);
515            if (ObjectUtils.isNotNull(assetGlobal.getSeparateSourceCapitalAsset())) {
516                assetGlobal.setLastInventoryDate(new java.sql.Date(assetGlobal.getSeparateSourceCapitalAsset().getLastInventoryDate().getTime()));
517            }
518            else {
519                assetGlobal.setLastInventoryDate(SpringContext.getBean(DateTimeService.class).getCurrentSqlDate());
520            }
521            List<AssetGlobalDetail> assetGlobalDetails = assetGlobal.getAssetGlobalDetails();
522            AssetGlobalDetail currLocationDetail = null;
523            HashMap<String, AssetGlobalDetail> locationMap = new HashMap<String, AssetGlobalDetail>();
524            AssetGlobalDetail copyValue = null;
525            for (AssetGlobalDetail detail : assetGlobalDetails) {
526                copyValue = (AssetGlobalDetail) ObjectUtils.deepCopy(detail);
527                copyValue.getAssetGlobalUniqueDetails().clear();
528                String key = generateLocationKey(copyValue);
529                if ((currLocationDetail = locationMap.get(key)) == null) {
530                    currLocationDetail = copyValue;
531                    locationMap.put(key, currLocationDetail);
532                }
533                currLocationDetail.getAssetGlobalUniqueDetails().add(copyValue);
534                currLocationDetail.setLocationQuantity(currLocationDetail.getAssetGlobalUniqueDetails().size());
535            }
536            assetGlobal.getAssetSharedDetails().clear();
537            assetGlobal.getAssetSharedDetails().addAll(locationMap.values());
538    
539            // When document starts routing, FO won't allow to change asset total amount which is a derived value from Asset payments
540            // and the quantity of assets. To compare asset total amount , we need to calculate and save the value before FO made
541            // changes. No handle to the workflow document and see if it starts routing. Otherwise, we can add if condition here.
542            setAssetTotalAmountFromPersistence(assetGlobal);
543        }
544    
545        private void setAssetTotalAmountFromPersistence(AssetGlobal assetGlobal) {
546            KualiDecimal minAssetTotalAmount = getAssetGlobalService().totalPaymentByAsset(assetGlobal, false);
547            KualiDecimal maxAssetTotalAmount = getAssetGlobalService().totalPaymentByAsset(assetGlobal, true);
548            if (minAssetTotalAmount.isGreaterThan(maxAssetTotalAmount)) {
549                // swap min and max
550                KualiDecimal totalPayment = minAssetTotalAmount;
551                minAssetTotalAmount = maxAssetTotalAmount;
552                maxAssetTotalAmount = totalPayment;
553            }
554            assetGlobal.setMinAssetTotalAmount(minAssetTotalAmount);
555            assetGlobal.setMaxAssetTotalAmount(maxAssetTotalAmount);
556        }
557    
558        /**
559         * Generates a unique using location fields to keep track of user changes
560         * 
561         * @param location Location
562         * @return Key String
563         */
564        private String generateLocationKey(AssetGlobalDetail location) {
565            StringBuilder builder = new StringBuilder();
566            builder.append(location.getCampusCode() == null ? "" : location.getCampusCode().trim().toLowerCase());
567            builder.append(location.getBuildingCode() == null ? "" : location.getBuildingCode().trim().toLowerCase());
568            builder.append(location.getBuildingRoomNumber() == null ? "" : location.getBuildingRoomNumber().trim().toLowerCase());
569            builder.append(location.getBuildingSubRoomNumber() == null ? "" : location.getBuildingSubRoomNumber().trim().toLowerCase());
570            builder.append(location.getOffCampusName() == null ? "" : location.getOffCampusName().trim().toLowerCase());
571            builder.append(location.getOffCampusAddress() == null ? "" : location.getOffCampusAddress().trim().toLowerCase());
572            builder.append(location.getOffCampusCityName() == null ? "" : location.getOffCampusCityName().trim().toLowerCase());
573            builder.append(location.getOffCampusStateCode() == null ? "" : location.getOffCampusStateCode().trim().toLowerCase());
574            builder.append(location.getOffCampusZipCode() == null ? "" : location.getOffCampusZipCode().trim().toLowerCase());
575            builder.append(location.getOffCampusCountryCode() == null ? "" : location.getOffCampusCountryCode().trim().toLowerCase());
576            return builder.toString();
577        }
578    
579        /**
580         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterPost(org.kuali.rice.kns.document.MaintenanceDocument,
581         *      java.util.Map)
582         */
583        @Override
584        public void processAfterPost(MaintenanceDocument document, Map<String, String[]> parameters) {
585            super.processAfterPost(document, parameters);
586            // adjust the quantity
587            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
588            List<AssetGlobalDetail> sharedDetailsList = assetGlobal.getAssetSharedDetails();
589    
590            // each shared detail is a group of new assets to be created.
591            // so to equally split the source amount into all new assets (all groups),
592            // we need to get the total of ALL location quantities from each shared detail group
593            int locationQtyTotal = 0;
594            if (!sharedDetailsList.isEmpty()) {
595                for (AssetGlobalDetail sharedDetail : sharedDetailsList) {
596                    sharedDetail.setLocationQuantity(sharedDetail.getAssetGlobalUniqueDetails().size());
597                    locationQtyTotal += sharedDetail.getLocationQuantity();
598                }
599            }
600    
601            // button actions for Asset Separate document
602            if (getAssetGlobalService().isAssetSeparate(assetGlobal) && sharedDetailsList.size() >= 1) {
603                String[] customAction = parameters.get(KNSConstants.CUSTOM_ACTION);
604    
605                // calculate equal source total amounts and set separate source amount fields
606                if (customAction != null && CamsConstants.AssetSeparate.CALCULATE_EQUAL_SOURCE_AMOUNTS_BUTTON.equals(customAction[0])) {
607                    KualiDecimalUtils kualiDecimalService = new KualiDecimalUtils(assetGlobal.getTotalCostAmount(), CamsConstants.CURRENCY_USD);
608                    // add source asset to the current location quantity
609                    KualiDecimal[] equalSourceAmountsArray = kualiDecimalService.allocateByQuantity(locationQtyTotal + 1);
610                    setEqualSeparateSourceAmounts(equalSourceAmountsArray, assetGlobal);
611    
612                    recalculateTotalAmount(assetGlobal);
613                }
614    
615                // calculate source asset remaining amount
616                if (customAction != null && (CamsConstants.AssetSeparate.CALCULATE_SEPARATE_SOURCE_REMAINING_AMOUNT_BUTTON.equals(customAction[0]))) {
617                    // Don't do anything because we are anyway recalculating always below
618                }
619    
620                // Do recalculate every time even if button (CamsConstants.CALCULATE_SEPARATE_SOURCE_REMAINING_AMOUNT_BUTTON) wasn't
621                // pressed. We do that so that it also happens on add / delete lines.
622                recalculateTotalAmount(assetGlobal);
623            }
624        }
625    
626        /**
627         * Recalculate amounts in the Recalculate Total Amount Tab
628         * 
629         * @param assetGlobal
630         */
631        protected void recalculateTotalAmount(AssetGlobal assetGlobal) {
632            // set Less Additions
633            assetGlobal.setSeparateSourceTotalAmount(getAssetGlobalService().getUniqueAssetsTotalAmount(assetGlobal));
634            // set Remaining Total Amount
635            assetGlobal.setSeparateSourceRemainingAmount(assetGlobal.getTotalCostAmount().subtract(getAssetGlobalService().getUniqueAssetsTotalAmount(assetGlobal)));
636        }
637    
638        /**
639         * Separates the current asset amount equally into new unique assets.
640         * 
641         * @param kualiDecimalArray
642         * @param assetGlobal
643         */
644        public void setEqualSeparateSourceAmounts(KualiDecimal[] equalSourceAmountsArray, AssetGlobal assetGlobal) {
645            int i = 0;
646            for (AssetGlobalDetail assetSharedDetail : assetGlobal.getAssetSharedDetails()) {
647                for (AssetGlobalDetail assetGlobalUniqueDetail : assetSharedDetail.getAssetGlobalUniqueDetails()) {
648                    assetGlobalUniqueDetail.setSeparateSourceAmount(equalSourceAmountsArray[i]);
649                    i++;
650                }
651            }
652        }
653    
654        /**
655         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#doRouteStatusChange(org.kuali.rice.kns.bo.DocumentHeader)
656         */
657        @Override
658        public void doRouteStatusChange(DocumentHeader documentHeader) {
659            super.doRouteStatusChange(documentHeader);
660            AssetGlobal assetGlobal = (AssetGlobal) getBusinessObject();
661            List<GeneralLedgerPendingEntry> generalLedgerPendingEntries = assetGlobal.getGeneralLedgerPendingEntries();
662            new AssetGlobalGeneralLedgerPendingEntrySource((FinancialSystemDocumentHeader) documentHeader).doRouteStatusChange(generalLedgerPendingEntries);
663    
664            KualiWorkflowDocument workflowDoc = documentHeader.getWorkflowDocument();
665    
666            // force pretagDetail active indicators back to true
667            if (workflowDoc.stateIsCanceled()) {
668                if (ObjectUtils.isNotNull(assetGlobal)) {
669                    List<AssetGlobalDetail> assetGlobalDetailsList = assetGlobal.getAssetGlobalDetails();
670                    if (ObjectUtils.isNotNull(assetGlobalDetailsList)) {
671                        for (AssetGlobalDetail assetGlobaldetails : assetGlobalDetailsList) {
672                            if (assetGlobaldetails.getCampusTagNumber() != null && !assetGlobaldetails.getCampusTagNumber().isEmpty()) {
673                                HashMap<Object, Object> map = new HashMap<Object, Object>();
674                                map.put(CabPropertyConstants.PretagDetail.CAMPUS_TAG_NUMBER, assetGlobaldetails.getCampusTagNumber());
675                                List<PretagDetail> pretagDetailList = (List<PretagDetail>) SpringContext.getBean(BusinessObjectService.class).findMatching(PretagDetail.class, map);
676                                if (ObjectUtils.isNotNull(pretagDetailList)) {
677                                    for (PretagDetail pretagDetail : pretagDetailList) {
678                                        pretagDetail.setActive(true);
679                                        SpringContext.getBean(BusinessObjectService.class).save(pretagDetail);
680                                    }
681                                }
682                            }
683                        }
684                    }
685                }
686            }
687    
688            // release lock for separate source asset...We don't include stateIsFinal since document always go to 'processed' first.
689            AssetGlobalService assetGlobalService = SpringContext.getBean(AssetGlobalService.class);
690            if (assetGlobalService.isAssetSeparate(assetGlobal) && (workflowDoc.stateIsCanceled() || workflowDoc.stateIsDisapproved() || workflowDoc.stateIsProcessed())) {
691                this.getCapitalAssetManagementModuleService().deleteAssetLocks(documentNumber, null);
692            }
693    
694            // notify CAB of document status change
695            if (((AssetGlobal) getBusinessObject()).isCapitalAssetBuilderOriginIndicator()) {
696                SpringContext.getBean(CapitalAssetBuilderModuleService.class).notifyRouteStatusChange(documentHeader);
697            }
698        }
699    
700        /**
701         * @see org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl#getPrimaryEditedBusinessObjectClass()
702         */
703        @Override
704        public Class<? extends PersistableBusinessObject> getPrimaryEditedBusinessObjectClass() {
705            return Asset.class;
706        }
707    
708        /**
709         * Returns the AssetGlobalService from context
710         * 
711         * @return AssetGlobalService
712         */
713        private AssetGlobalService getAssetGlobalService() {
714            return SpringContext.getBean(AssetGlobalService.class);
715        }
716    
717        /**
718         * populates the asset location information (add new section)
719         * 
720         * @param asset
721         */
722        private void populateAssetLocationTabInformation(Asset asset) {
723            AssetGlobalDetail assetSharedDetail = (AssetGlobalDetail) this.getNewCollectionLine(CamsPropertyConstants.AssetGlobal.ASSET_SHARED_DETAILS);
724            assetSharedDetail.setCampusCode(asset.getCampusCode());
725            assetSharedDetail.setBuildingCode(asset.getBuildingCode());
726            assetSharedDetail.setBuildingRoomNumber(asset.getBuildingRoomNumber());
727        }
728    }