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.purap.document;
017    
018    import java.sql.Timestamp;
019    import java.util.List;
020    
021    import org.apache.commons.lang.StringUtils;
022    import org.kuali.kfs.module.purap.PurapPropertyConstants;
023    import org.kuali.kfs.module.purap.PurapWorkflowConstants.NodeDetails;
024    import org.kuali.kfs.module.purap.businessobject.AccountsPayableItem;
025    import org.kuali.kfs.module.purap.businessobject.PurApItemUseTax;
026    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
027    import org.kuali.kfs.module.purap.document.service.AccountsPayableDocumentSpecificService;
028    import org.kuali.kfs.module.purap.document.service.PurapService;
029    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
030    import org.kuali.kfs.sys.KFSPropertyConstants;
031    import org.kuali.kfs.sys.businessobject.Bank;
032    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
033    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
034    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
035    import org.kuali.kfs.sys.context.SpringContext;
036    import org.kuali.kfs.vnd.businessobject.CampusParameter;
037    import org.kuali.rice.kew.dto.DocumentRouteLevelChangeDTO;
038    import org.kuali.rice.kim.bo.Person;
039    import org.kuali.rice.kns.rule.event.KualiDocumentEvent;
040    import org.kuali.rice.kns.service.DataDictionaryService;
041    import org.kuali.rice.kns.util.KualiDecimal;
042    import org.kuali.rice.kns.util.ObjectUtils;
043    
044    /**
045     * Accounts Payable Document Base
046     */
047    public abstract class AccountsPayableDocumentBase extends PurchasingAccountsPayableDocumentBase implements AccountsPayableDocument {
048        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountsPayableDocumentBase.class);
049    
050        // SHARED FIELDS BETWEEN PAYMENT REQUEST AND CREDIT MEMO
051        protected Timestamp accountsPayableApprovalTimestamp;
052        protected String lastActionPerformedByPersonId;
053        protected String accountsPayableProcessorIdentifier;
054        protected boolean holdIndicator;
055        protected Timestamp extractedTimestamp;
056        protected Integer purchaseOrderIdentifier;
057        protected String processingCampusCode;
058        protected String noteLine1Text;
059        protected String noteLine2Text;
060        protected String noteLine3Text;
061        protected boolean continuationAccountIndicator;
062        protected boolean closePurchaseOrderIndicator;
063        protected boolean reopenPurchaseOrderIndicator;
064        protected String bankCode;
065        
066        protected boolean unmatchedOverride; // not persisted
067        
068        // NOT PERSISTED IN DB
069        // BELOW USED BY ROUTING
070        protected String chartOfAccountsCode;
071        protected String organizationCode;
072    
073        // NOT PERSISTED IN DB
074        // BELOW USED BY GL ENTRY CREATION
075        protected boolean generateEncumbranceEntries;
076        protected String debitCreditCodeForGLEntries;
077        protected PurApItemUseTax offsetUseTax;
078    
079        // REFERENCE OBJECTS
080        protected CampusParameter processingCampus;
081        protected transient PurchaseOrderDocument purchaseOrderDocument;
082        protected Bank bank;
083        
084        /**
085         * Constructs a AccountsPayableDocumentBase
086         */
087        public AccountsPayableDocumentBase() {
088            super();
089            setUnmatchedOverride(false);
090        }
091    
092        public void setLineItemTotal(KualiDecimal total) {
093            // do nothing, this is so that the jsp won't complain about lineItemTotal have no setter method.
094        }
095    
096        public void setGrandTotal(KualiDecimal total) {
097            // do nothing, this is so that the jsp won't complain about grandTotal have no setter method.
098        }
099    
100        /**
101         * Overriding to stop the deleting of general ledger entries.
102         * 
103         * @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#removeGeneralLedgerPendingEntries()
104         */
105        @Override
106        protected void removeGeneralLedgerPendingEntries() {
107            // do not delete entries for PREQ or CM (hjs)
108        }
109    
110        /**
111         * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#requiresAccountsPayableReviewRouting()
112         */
113        public boolean requiresAccountsPayableReviewRouting() {
114            return !approvalAtAccountsPayableReviewAllowed();
115        }
116    
117        /**
118         * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#approvalAtAccountsPayableReviewAllowed()
119         */
120        public boolean approvalAtAccountsPayableReviewAllowed() {
121            return !(isAttachmentRequired() && documentHasNoImagesAttached());
122        }
123    
124        /**
125         * Checks whether an attachment is required
126         * 
127         * @return - true if attachment is required, otherwise false
128         */
129        protected abstract boolean isAttachmentRequired();
130    
131        /**
132         * Checks all documents notes for attachments and to be overriden by sub class
133         * 
134         * @return - true if document does not have an image attached, false otherwise
135         */
136        public abstract boolean documentHasNoImagesAttached();
137    
138        /**
139         * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#populateDocumentForRouting()
140         */
141        @Override
142        public void populateDocumentForRouting() {
143            if (ObjectUtils.isNotNull(getPurchaseOrderDocument())) {
144                this.setChartOfAccountsCode(getPurchaseOrderDocument().getChartOfAccountsCode());
145                this.setOrganizationCode(getPurchaseOrderDocument().getOrganizationCode());
146                if (ObjectUtils.isNull(this.getPurchaseOrderDocument().getDocumentHeader().getDocumentNumber())) {
147                    this.getPurchaseOrderDocument().refreshReferenceObject(KFSPropertyConstants.DOCUMENT_HEADER);
148                }
149            }
150            super.populateDocumentForRouting();
151        }
152    
153        /**
154         * Calls a custom prepare for save method, as the super class does GL entry creation that causes problems with AP documents.
155         * 
156         * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#prepareForSave(org.kuali.rice.kns.rule.event.KualiDocumentEvent)
157         */
158        @Override
159        public void prepareForSave(KualiDocumentEvent event) {
160    
161            // copied from super because we can't call super for AP docs
162            customPrepareForSave(event);
163    
164            // DO NOT CALL SUPER HERE!! Cannot call super because it will mess up the GL entry creation process (hjs)
165            // super.prepareForSave(event);
166        }
167    
168        /**
169         * Helper method to be called from custom prepare for save and to be overriden by sub class.
170         * 
171         * @return - Po Document Type
172         */
173        public abstract String getPoDocumentTypeForAccountsPayableDocumentCancel();
174    
175        /**
176         * @see org.kuali.rice.kns.document.DocumentBase#handleRouteLevelChange(org.kuali.rice.kew.clientapp.vo.DocumentRouteLevelChangeDTO)
177         */
178        @Override
179        public void doRouteLevelChange(DocumentRouteLevelChangeDTO levelChangeEvent) {
180            LOG.debug("handleRouteLevelChange() started");
181            super.doRouteLevelChange(levelChangeEvent);
182            String newNodeName = levelChangeEvent.getNewNodeName();
183            if (processNodeChange(newNodeName, levelChangeEvent.getOldNodeName())) {
184                if (StringUtils.isNotBlank(newNodeName)) {
185                    NodeDetails nodeDetailEnum = getNodeDetailEnum(newNodeName);
186                    if (ObjectUtils.isNotNull(nodeDetailEnum)) {
187                        String statusCode = nodeDetailEnum.getAwaitingStatusCode();
188                        if (StringUtils.isNotBlank(statusCode)) {
189                            SpringContext.getBean(PurapService.class).updateStatus(this, statusCode);
190                            saveDocumentFromPostProcessing();
191                        }
192                        else {
193                            LOG.debug("Document with id " + getDocumentNumber() + " will stop in route node '" + newNodeName + "' but no awaiting status found to set");
194                        }
195                    }
196                }
197            }
198        }
199    
200        /**
201         * Hook to allow processing after a route level is passed.
202         * 
203         * @param newNodeName - current route level
204         * @param oldNodeName - previous route level
205         * @return - true if process completes to valid state
206         */
207        public abstract boolean processNodeChange(String newNodeName, String oldNodeName);
208    
209        /**
210         * Retrieves node details object based on name.
211         * 
212         * @param nodeName - route level
213         * @return - Information about the supplied route level
214         */
215        public abstract NodeDetails getNodeDetailEnum(String nodeName);
216    
217        /**
218         * Hook point to allow processing after a save.
219         */
220        public abstract void saveDocumentFromPostProcessing();
221    
222        // GETTERS AND SETTERS
223        public Integer getPurchaseOrderIdentifier() {
224            return purchaseOrderIdentifier;
225        }
226    
227        public void setPurchaseOrderIdentifier(Integer purchaseOrderIdentifier) {
228            this.purchaseOrderIdentifier = purchaseOrderIdentifier;
229        }
230    
231        public String getAccountsPayableProcessorIdentifier() {
232            return accountsPayableProcessorIdentifier;
233        }
234    
235        public void setAccountsPayableProcessorIdentifier(String accountsPayableProcessorIdentifier) {
236            this.accountsPayableProcessorIdentifier = accountsPayableProcessorIdentifier;
237        }
238    
239        public String getLastActionPerformedByPersonId() {
240            return lastActionPerformedByPersonId;
241        }
242    
243        public void setLastActionPerformedByPersonId(String lastActionPerformedByPersonId) {
244            this.lastActionPerformedByPersonId = lastActionPerformedByPersonId;
245        }
246    
247        public String getProcessingCampusCode() {
248            return processingCampusCode;
249        }
250    
251        public void setProcessingCampusCode(String processingCampusCode) {
252            this.processingCampusCode = processingCampusCode;
253        }
254    
255        public Timestamp getAccountsPayableApprovalTimestamp() {
256            return accountsPayableApprovalTimestamp;
257        }
258    
259        public void setAccountsPayableApprovalTimestamp(Timestamp accountsPayableApprovalTimestamp) {
260            this.accountsPayableApprovalTimestamp = accountsPayableApprovalTimestamp;
261        }
262    
263        public Timestamp getExtractedTimestamp() {
264            return extractedTimestamp;
265        }
266    
267        public void setExtractedTimestamp(Timestamp extractedTimestamp) {
268            this.extractedTimestamp = extractedTimestamp;
269        }
270    
271        public boolean isHoldIndicator() {
272            return holdIndicator;
273        }
274    
275        public void setHoldIndicator(boolean holdIndicator) {
276            this.holdIndicator = holdIndicator;
277        }
278    
279        public String getNoteLine1Text() {
280            return noteLine1Text;
281        }
282    
283        public void setNoteLine1Text(String noteLine1Text) {
284            this.noteLine1Text = noteLine1Text;
285        }
286    
287        public String getNoteLine2Text() {
288            return noteLine2Text;
289        }
290    
291        public void setNoteLine2Text(String noteLine2Text) {
292            this.noteLine2Text = noteLine2Text;
293        }
294    
295        public String getNoteLine3Text() {
296            return noteLine3Text;
297        }
298    
299        public void setNoteLine3Text(String noteLine3Text) {
300            this.noteLine3Text = noteLine3Text;
301        }
302    
303        public CampusParameter getProcessingCampus() {
304            return processingCampus;
305        }
306    
307        public String getChartOfAccountsCode() {
308            return chartOfAccountsCode;
309        }
310    
311        public void setChartOfAccountsCode(String chartOfAccountsCode) {
312            this.chartOfAccountsCode = chartOfAccountsCode;
313        }
314    
315        public String getOrganizationCode() {
316            return organizationCode;
317        }
318    
319        public void setOrganizationCode(String organizationCode) {
320            this.organizationCode = organizationCode;
321        }
322    
323        public boolean isGenerateEncumbranceEntries() {
324            return generateEncumbranceEntries;
325        }
326    
327        public void setGenerateEncumbranceEntries(boolean generateEncumbranceEntries) {
328            this.generateEncumbranceEntries = generateEncumbranceEntries;
329        }
330    
331        /**
332         * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getPurchaseOrderDocument()
333         */
334        public PurchaseOrderDocument getPurchaseOrderDocument() {
335            if ((ObjectUtils.isNull(purchaseOrderDocument) || ObjectUtils.isNull(purchaseOrderDocument.getPurapDocumentIdentifier())) && (ObjectUtils.isNotNull(getPurchaseOrderIdentifier()))) {
336                setPurchaseOrderDocument(SpringContext.getBean(PurchaseOrderService.class).getCurrentPurchaseOrder(this.getPurchaseOrderIdentifier()));
337            }
338            return purchaseOrderDocument;
339        }
340    
341        /**
342         * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#setPurchaseOrderDocument(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
343         */
344        public void setPurchaseOrderDocument(PurchaseOrderDocument purchaseOrderDocument) {
345            if (ObjectUtils.isNull(purchaseOrderDocument)) {
346                // KUALI-PURAP 1185 PO Id not being set to null, instead throwing error on main screen that value is invalid.
347                // setPurchaseOrderIdentifier(null);
348                this.purchaseOrderDocument = null;
349            }
350            else {
351                if (ObjectUtils.isNotNull(purchaseOrderDocument.getPurapDocumentIdentifier())) {
352                    setPurchaseOrderIdentifier(purchaseOrderDocument.getPurapDocumentIdentifier());
353                }
354                this.purchaseOrderDocument = purchaseOrderDocument;
355            }
356        }
357    
358        public boolean isClosePurchaseOrderIndicator() {
359            return closePurchaseOrderIndicator;
360        }
361    
362        public void setClosePurchaseOrderIndicator(boolean closePurchaseOrderIndicator) {
363            this.closePurchaseOrderIndicator = closePurchaseOrderIndicator;
364        }
365    
366        public boolean isReopenPurchaseOrderIndicator() {
367            return reopenPurchaseOrderIndicator;
368        }
369    
370        public void setReopenPurchaseOrderIndicator(boolean reopenPurchaseOrderIndicator) {
371            this.reopenPurchaseOrderIndicator = reopenPurchaseOrderIndicator;
372        }
373        
374        public String getBankCode() {
375            return bankCode;
376        }
377    
378        public void setBankCode(String bankCode) {
379            this.bankCode = bankCode;
380        }
381    
382        public Bank getBank() {
383            return bank;
384        }
385    
386        public void setBank(Bank bank) {
387            this.bank = bank;
388        }
389    
390        /**
391         * Sets the processing campus.
392         * @deprecated
393         * @param processingCampus
394         */
395        public void setProcessingCampus(CampusParameter processingCampus) {
396            this.processingCampus = processingCampus;
397        }
398    
399        // Helper methods
400        /**
401         * Retrieves the universal user object for the last person to perform an action on the document.
402         */
403        public Person getLastActionPerformedByUser() {
404            return SpringContext.getBean(org.kuali.rice.kim.service.PersonService.class).getPerson(getLastActionPerformedByPersonId());
405        }
406    
407        /**
408         * Retrieves the person name for the last person to perform an action on the document.
409         * 
410         * @return - the person's name who last performed an action on the document.
411         */
412        public String getLastActionPerformedByPersonName() {
413            Person user = getLastActionPerformedByUser();
414            if (ObjectUtils.isNull(user)) {
415                return "";
416            }
417            else {
418                return user.getName();
419            }
420        }
421    
422        public String getDebitCreditCodeForGLEntries() {
423            return debitCreditCodeForGLEntries;
424        }
425    
426        public void setDebitCreditCodeForGLEntries(String debitCreditCodeForGLEntries) {
427            this.debitCreditCodeForGLEntries = debitCreditCodeForGLEntries;
428        }
429    
430        public boolean isUnmatchedOverride() {
431            return unmatchedOverride;
432        }
433    
434        public void setUnmatchedOverride(boolean unmatchedOverride) {
435            this.unmatchedOverride = unmatchedOverride;
436        }
437     
438        public boolean getExtractedIndicatorForSearching() {
439            return extractedTimestamp != null;
440        }
441        
442        public boolean isHoldIndicatorForSearching() {
443            return holdIndicator;
444        }
445        
446        /**
447         * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getGrandTotal()
448         */
449        public abstract KualiDecimal getGrandTotal();
450    
451        /**
452         * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getInitialAmount()
453         */
454        public abstract KualiDecimal getInitialAmount();
455    
456        public boolean isContinuationAccountIndicator() {
457            return continuationAccountIndicator;
458        }
459    
460        public void setContinuationAccountIndicator(boolean continuationAccountIndicator) {
461            this.continuationAccountIndicator = continuationAccountIndicator;
462        }
463    
464        public boolean isExtracted() {
465            return (ObjectUtils.isNotNull(getExtractedTimestamp()));
466        }
467    
468        public abstract AccountsPayableDocumentSpecificService getDocumentSpecificService();
469    
470        public AccountsPayableItem getAPItemFromPOItem(PurchaseOrderItem poi) {
471            for (AccountsPayableItem preqItem : (List<AccountsPayableItem>) this.getItems()) {
472                if (preqItem.getItemType().isLineItemIndicator()) {
473                    if (preqItem.getItemLineNumber().compareTo(poi.getItemLineNumber()) == 0) {
474                        return preqItem;
475                    }
476                }
477                else {
478                    return (AccountsPayableItem) SpringContext.getBean(PurapService.class).getBelowTheLineByType(this, poi.getItemType());
479                }
480            }
481            return null;
482        }
483    
484        /**
485         * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#getItemClass()
486         */
487        public Class getItemClass() {
488            return null;
489        }
490    
491        /**
492         * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#getPurApSourceDocumentIfPossible()
493         */
494        public PurchasingAccountsPayableDocument getPurApSourceDocumentIfPossible() {
495            return null;
496        }
497    
498        /**
499         * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase#getPurApSourceDocumentLabelIfPossible()
500         */
501        public String getPurApSourceDocumentLabelIfPossible() {
502            return null;
503        }
504    
505        public void updateExtendedPriceOnItems() {
506            for (AccountsPayableItem item : (List<AccountsPayableItem>) getItems()) {
507                item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE);
508        
509                final KualiDecimal itemExtendedPrice = (item.getExtendedPrice()==null)?KualiDecimal.ZERO:item.getExtendedPrice();;
510                if (item.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
511                    KualiDecimal newExtendedPrice = item.calculateExtendedPrice();
512                    item.setExtendedPrice(newExtendedPrice);
513                }
514            }
515        }
516    
517        /**
518         * 
519         * @see org.kuali.kfs.module.purap.document.AccountsPayableDocument#getTotalRemitAmount()
520         */
521        public KualiDecimal getTotalRemitTax() {
522            if(!this.isUseTaxIndicator()) {
523                return (KualiDecimal.ZERO.equals(this.getTotalTaxAmount()))?null:this.getTotalTaxAmount();
524            }
525            return null;
526        }
527    
528        @Override
529        public boolean customizeOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail accountingLine, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
530            boolean value = super.customizeOffsetGeneralLedgerPendingEntry(accountingLine, explicitEntry, offsetEntry);
531            if(offsetEntry != null && this.offsetUseTax != null) {
532                offsetEntry.setChartOfAccountsCode(this.offsetUseTax.getChartOfAccountsCode());
533                offsetEntry.refreshReferenceObject(KFSPropertyConstants.CHART);
534                offsetEntry.setAccountNumber(this.offsetUseTax.getAccountNumber());
535                offsetEntry.refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
536                offsetEntry.setFinancialObjectCode(this.offsetUseTax.getFinancialObjectCode());
537                offsetEntry.refreshReferenceObject(KFSPropertyConstants.FINANCIAL_OBJECT);
538            } else {
539                value=false;
540            }
541            return value;
542        }
543    
544        public boolean generateGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntrySequenceHelper sequenceHelper, PurApItemUseTax offsetUseTax) {
545            this.offsetUseTax = offsetUseTax; 
546            boolean value = this.generateGeneralLedgerPendingEntries(glpeSourceDetail, sequenceHelper);
547            this.offsetUseTax = null;
548            return value;
549        }
550        
551        public String getHoldIndicatorForResult(){
552            return isHoldIndicator() ? "Yes" : "No";
553        }
554        
555        public String getProcessingCampusCodeForSearch(){
556            return getProcessingCampusCode();
557        }
558        
559        public String getDocumentChartOfAccountsCodeForSearching(){
560            return getPurchaseOrderDocument().getChartOfAccountsCode();
561        }
562        
563        public String getDocumentOrganizationCodeForSearching(){
564            return getPurchaseOrderDocument().getOrganizationCode();
565        }
566        
567        /**
568         * @return workflow document type for the purap document
569         */
570        public String getDocumentType() {
571            return SpringContext.getBean(DataDictionaryService.class).getDocumentTypeNameByClass(this.getClass());
572        }
573        
574        public boolean shouldGiveErrorForEmptyAccountsProration() {
575            return true;
576        }
577    }
578