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.util.ArrayList;
019    import java.util.Iterator;
020    import java.util.List;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.apache.log4j.Logger;
024    import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
025    import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
026    import org.kuali.kfs.module.cam.CamsConstants;
027    import org.kuali.kfs.module.cam.businessobject.AssetPaymentAccountingLineParser;
028    import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail;
029    import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
030    import org.kuali.kfs.module.cam.document.service.AssetPaymentService;
031    import org.kuali.kfs.module.cam.document.validation.event.AssetPaymentManuallyAddAccountingLineEvent;
032    import org.kuali.kfs.module.cam.util.AssetPaymentDistributor;
033    import org.kuali.kfs.sys.KFSConstants;
034    import org.kuali.kfs.sys.businessobject.AccountingLine;
035    import org.kuali.kfs.sys.businessobject.AccountingLineParser;
036    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
037    import org.kuali.kfs.sys.context.SpringContext;
038    import org.kuali.kfs.sys.document.AccountingDocumentBase;
039    import org.kuali.kfs.sys.document.AmountTotaling;
040    import org.kuali.rice.kew.dto.DocumentRouteStatusChangeDTO;
041    import org.kuali.rice.kns.document.Copyable;
042    import org.kuali.rice.kns.exception.ValidationException;
043    import org.kuali.rice.kns.rule.event.KualiDocumentEvent;
044    import org.kuali.rice.kns.rule.event.SaveDocumentEvent;
045    import org.kuali.rice.kns.util.KualiDecimal;
046    import org.kuali.rice.kns.util.TypedArrayList;
047    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
048    
049    /**
050     * Capital assets document class for the asset payment document
051     */
052    public class AssetPaymentDocument extends AccountingDocumentBase implements Copyable, AmountTotaling {
053        protected static Logger LOG = Logger.getLogger(AssetPaymentDocument.class);
054    
055        protected List<AssetPaymentAssetDetail> assetPaymentAssetDetail;
056        protected Long capitalAssetNumber;
057        protected boolean capitalAssetBuilderOriginIndicator;
058    
059        public AssetPaymentDocument() {
060            super();
061            this.setAssetPaymentAssetDetail(new TypedArrayList(AssetPaymentAssetDetail.class));
062        }
063    
064        /**
065         * Remove asset from collection for deletion
066         * 
067         * @see org.kuali.kfs.sys.document.AccountingDocumentBase#buildListOfDeletionAwareLists()
068         */
069        @Override
070        public List buildListOfDeletionAwareLists() {
071            List<List> deletionAwareList = super.buildListOfDeletionAwareLists();
072            deletionAwareList.add(this.getAssetPaymentAssetDetail());
073            return deletionAwareList;
074        }
075    
076        /**
077         * When document save, AddAccountingLineEvent is added by the framework. Also, we need to add
078         * AssetPaymentManuallyAddAccountingLineEvent manually to run all relating validations.
079         * 
080         * @see org.kuali.kfs.sys.document.AccountingDocumentBase#generateSaveEvents()
081         */
082        @Override
083        public List generateSaveEvents() {
084            List subEvents = new ArrayList();
085            // keep the order of events as for validation will run in the same order.
086            if (!isCapitalAssetBuilderOriginIndicator()) {
087                // Add AssetPaymentManuallyAddAccountingLineEvent for each manually added accounting line.
088                String errorPathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_SOURCE_ACCT_LINE_PROPERTY_NAME;
089                int index = 0;
090                for (Iterator i = getSourceAccountingLines().iterator(); i.hasNext(); index++) {
091                    String indexedErrorPathPrefix = errorPathPrefix + "[" + index + "]";
092                    AccountingLine currentLine = (AccountingLine) i.next();
093                    AssetPaymentManuallyAddAccountingLineEvent newSubEvent = new AssetPaymentManuallyAddAccountingLineEvent(indexedErrorPathPrefix, this, currentLine);
094                    subEvents.add(newSubEvent);
095                }
096            }
097    
098            subEvents.addAll(super.generateSaveEvents());
099    
100            return subEvents;
101        }
102    
103        /**
104         * Lock on purchase order document since post processor will update PO document by adding notes.
105         * 
106         * @see org.kuali.rice.kns.document.DocumentBase#getWorkflowEngineDocumentIdsToLock()
107         */
108        @Override
109        public List<Long> getWorkflowEngineDocumentIdsToLock() {
110            if (this.isCapitalAssetBuilderOriginIndicator()) {
111                String poDocId = SpringContext.getBean(CapitalAssetBuilderModuleService.class).getCurrentPurchaseOrderDocumentNumber(this.getDocumentNumber());
112                if (StringUtils.isNotBlank(poDocId)) {
113                    List<Long> documentIds = new ArrayList<Long>();
114                    documentIds.add(new Long(poDocId));
115                    return documentIds;
116                }
117            }
118            return null;
119        }
120    
121        /**
122         * Determines if the given AccountingLine (as a GeneralLedgerPostable) is a credit or a debit, in terms of GLPE generation
123         * 
124         * @see org.kuali.kfs.sys.document.AccountingDocumentBase#isDebit(org.kuali.kfs.bo.GeneralLedgerPostable)
125         */
126        @Override
127        public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) {
128            return false;
129        }
130    
131        public boolean isCapitalAssetBuilderOriginIndicator() {
132            return capitalAssetBuilderOriginIndicator;
133        }
134    
135        public void setCapitalAssetBuilderOriginIndicator(boolean capitalAssetBuilderOriginIndicator) {
136            this.capitalAssetBuilderOriginIndicator = capitalAssetBuilderOriginIndicator;
137        }
138    
139        /**
140         * This method...
141         * 
142         * @param assetPaymentAssetDetail
143         */
144        public void addAssetPaymentAssetDetail(AssetPaymentAssetDetail assetPaymentAssetDetail) {
145            this.getAssetPaymentAssetDetail().add(assetPaymentAssetDetail);
146        }
147    
148        /**
149         * @see org.kuali.rice.kns.document.DocumentBase#postProcessSave(org.kuali.rice.kns.rule.event.KualiDocumentEvent)
150         */
151        @Override
152        public void postProcessSave(KualiDocumentEvent event) {
153            super.postProcessSave(event);
154    
155            if (!(event instanceof SaveDocumentEvent)) { // don't lock until they route
156                ArrayList<Long> capitalAssetNumbers = new ArrayList<Long>();
157                for (AssetPaymentAssetDetail assetPaymentAssetDetail : this.getAssetPaymentAssetDetail()) {
158                    if (assetPaymentAssetDetail.getCapitalAssetNumber() != null) {
159                        capitalAssetNumbers.add(assetPaymentAssetDetail.getCapitalAssetNumber());
160                    }
161                }
162    
163                String documentTypeForLocking = CamsConstants.DocumentTypeName.ASSET_PAYMENT;
164                if (this.isCapitalAssetBuilderOriginIndicator()) {
165                    documentTypeForLocking = CamsConstants.DocumentTypeName.ASSET_PAYMENT_FROM_CAB;
166                }
167    
168                if (!this.getCapitalAssetManagementModuleService().storeAssetLocks(capitalAssetNumbers, this.getDocumentNumber(), documentTypeForLocking, null)) {
169                    throw new ValidationException("Asset " + capitalAssetNumbers.toString() + " is being locked by other documents.");
170                }
171            }
172        }
173    
174        protected CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
175            return SpringContext.getBean(CapitalAssetManagementModuleService.class);
176        }
177    
178        /**
179         * @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#doRouteStatusChange()
180         */
181        @Override
182        public void doRouteStatusChange(DocumentRouteStatusChangeDTO statusChangeEvent) {
183            super.doRouteStatusChange(statusChangeEvent);
184            KualiWorkflowDocument workflowDocument = getDocumentHeader().getWorkflowDocument();
185    
186            // Update asset payment table with the approved asset detail records.
187            if (workflowDocument.stateIsProcessed()) {
188                SpringContext.getBean(AssetPaymentService.class).processApprovedAssetPayment(this);
189            }
190    
191            // Remove asset lock when doc status change. We don't include stateIsFinal since document always go to 'processed' first.
192            if (workflowDocument.stateIsCanceled() || workflowDocument.stateIsDisapproved() || workflowDocument.stateIsProcessed()) {
193                this.getCapitalAssetManagementModuleService().deleteAssetLocks(this.getDocumentNumber(), null);
194            }
195    
196            if (isCapitalAssetBuilderOriginIndicator()) {
197                SpringContext.getBean(CapitalAssetBuilderModuleService.class).notifyRouteStatusChange(getDocumentHeader());
198            }
199        }
200    
201        /**
202         * @see org.kuali.kfs.sys.document.AccountingDocumentBase#prepareForSave(org.kuali.rice.kns.rule.event.KualiDocumentEvent)
203         */
204        @Override
205        public void prepareForSave(KualiDocumentEvent event) {
206            // This is an empty method in order to prevent kuali from generating a gl pending entry record.
207        }
208    
209        public List<AssetPaymentAssetDetail> getAssetPaymentAssetDetail() {
210            return assetPaymentAssetDetail;
211        }
212    
213        public void setAssetPaymentAssetDetail(List<AssetPaymentAssetDetail> assetPaymentAssetDetail) {
214            this.assetPaymentAssetDetail = assetPaymentAssetDetail;
215        }
216    
217        public Long getCapitalAssetNumber() {
218            return capitalAssetNumber;
219        }
220    
221        public void setCapitalAssetNumber(Long capitalAssetNumber) {
222            this.capitalAssetNumber = capitalAssetNumber;
223        }
224    
225        /**
226         * calculates the total previous cost amount of all the assets in the document
227         * 
228         * @return KualiDecimal
229         */
230        public KualiDecimal getAssetsTotalHistoricalCost() {
231            KualiDecimal total = new KualiDecimal(0);
232            if (this.getAssetPaymentAssetDetail().isEmpty())
233                return new KualiDecimal(0);
234    
235            for (AssetPaymentAssetDetail detail : this.getAssetPaymentAssetDetail()) {
236                KualiDecimal amount = (detail.getPreviousTotalCostAmount() == null ? new KualiDecimal(0) : detail.getPreviousTotalCostAmount());
237                total = total.add(amount);
238            }
239            return total;
240        }
241        
242        /**
243         * Get the asset payment distributor built by AssetPaymentDetails, AssetPaymentAssetDetail and
244         * totalHistoricalCost 
245         * 
246         * @return AssetPaymentDistributor
247         */
248        public AssetPaymentDistributor getAssetPaymentDistributor() {
249            
250            // instantiate the distributor and calculate the asset payment distributions
251            AssetPaymentDistributor paymentDistributor = new AssetPaymentDistributor(getSourceAccountingLines(),
252                    getAssetPaymentAssetDetail(), getAssetsTotalHistoricalCost());
253            
254            return paymentDistributor;
255        }
256    }