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.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.sql.Timestamp;
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.Calendar;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    
029    import org.apache.commons.lang.StringUtils;
030    import org.kuali.kfs.module.purap.PurapConstants;
031    import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
032    import org.kuali.kfs.module.purap.PurapKeyConstants;
033    import org.kuali.kfs.module.purap.PurapParameterConstants;
034    import org.kuali.kfs.module.purap.PurapParameterConstants.TaxParameters;
035    import org.kuali.kfs.module.purap.PurapPropertyConstants;
036    import org.kuali.kfs.module.purap.PurapRuleConstants;
037    import org.kuali.kfs.module.purap.businessobject.AccountsPayableItem;
038    import org.kuali.kfs.module.purap.businessobject.BulkReceivingView;
039    import org.kuali.kfs.module.purap.businessobject.CorrectionReceivingView;
040    import org.kuali.kfs.module.purap.businessobject.CreditMemoView;
041    import org.kuali.kfs.module.purap.businessobject.ItemType;
042    import org.kuali.kfs.module.purap.businessobject.LineItemReceivingView;
043    import org.kuali.kfs.module.purap.businessobject.OrganizationParameter;
044    import org.kuali.kfs.module.purap.businessobject.PaymentRequestView;
045    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
046    import org.kuali.kfs.module.purap.businessobject.PurApItem;
047    import org.kuali.kfs.module.purap.businessobject.PurApItemUseTax;
048    import org.kuali.kfs.module.purap.businessobject.PurapEnterableItem;
049    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
050    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderView;
051    import org.kuali.kfs.module.purap.businessobject.PurchasingItem;
052    import org.kuali.kfs.module.purap.businessobject.PurchasingItemBase;
053    import org.kuali.kfs.module.purap.businessobject.RequisitionView;
054    import org.kuali.kfs.module.purap.document.AccountsPayableDocument;
055    import org.kuali.kfs.module.purap.document.AccountsPayableDocumentBase;
056    import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
057    import org.kuali.kfs.module.purap.document.PurapItemOperations;
058    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
059    import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
060    import org.kuali.kfs.module.purap.document.PurchasingDocument;
061    import org.kuali.kfs.module.purap.document.RequisitionDocument;
062    import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument;
063    import org.kuali.kfs.module.purap.document.service.LogicContainer;
064    import org.kuali.kfs.module.purap.document.service.PurapService;
065    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
066    import org.kuali.kfs.module.purap.service.PurapAccountingService;
067    import org.kuali.kfs.module.purap.util.PurApItemUtils;
068    import org.kuali.kfs.sys.KFSConstants;
069    import org.kuali.kfs.sys.KFSPropertyConstants;
070    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
071    import org.kuali.kfs.sys.businessobject.TaxDetail;
072    import org.kuali.kfs.sys.context.SpringContext;
073    import org.kuali.kfs.sys.document.validation.event.DocumentSystemSaveEvent;
074    import org.kuali.kfs.sys.service.NonTransactional;
075    import org.kuali.kfs.sys.service.TaxService;
076    import org.kuali.kfs.sys.service.UniversityDateService;
077    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
078    import org.kuali.kfs.vnd.businessobject.CommodityCode;
079    import org.kuali.kfs.vnd.businessobject.VendorDetail;
080    import org.kuali.kfs.vnd.document.service.VendorService;
081    import org.kuali.rice.kew.exception.WorkflowException;
082    import org.kuali.rice.kew.service.WorkflowDocumentActions;
083    import org.kuali.rice.kns.UserSession;
084    import org.kuali.rice.kns.bo.Note;
085    import org.kuali.rice.kns.document.Document;
086    import org.kuali.rice.kns.exception.InfrastructureException;
087    import org.kuali.rice.kns.service.BusinessObjectService;
088    import org.kuali.rice.kns.service.DataDictionaryService;
089    import org.kuali.rice.kns.service.DateTimeService;
090    import org.kuali.rice.kns.service.DocumentService;
091    import org.kuali.rice.kns.service.KualiConfigurationService;
092    import org.kuali.rice.kns.service.NoteService;
093    import org.kuali.rice.kns.service.ParameterEvaluator;
094    import org.kuali.rice.kns.service.ParameterService;
095    import org.kuali.rice.kns.service.PersistenceService;
096    import org.kuali.rice.kns.util.GlobalVariables;
097    import org.kuali.rice.kns.util.KNSPropertyConstants;
098    import org.kuali.rice.kns.util.KualiDecimal;
099    import org.kuali.rice.kns.util.ObjectUtils;
100    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
101    
102    @NonTransactional
103    public class PurapServiceImpl implements PurapService {
104        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapServiceImpl.class);
105    
106        private BusinessObjectService businessObjectService;
107        private DataDictionaryService dataDictionaryService;
108        private DateTimeService dateTimeService;
109        private DocumentService documentService;
110        private NoteService noteService;
111        private ParameterService parameterService;
112        private PersistenceService persistenceService;
113        private PurchaseOrderService purchaseOrderService;
114        private UniversityDateService universityDateService;
115        private VendorService vendorService;
116        private TaxService taxService; 
117        private PurapAccountingService purapAccountingService;
118        
119        public void setBusinessObjectService(BusinessObjectService boService) {
120            this.businessObjectService = boService;
121        }
122    
123        public void setDateTimeService(DateTimeService dateTimeService) {
124            this.dateTimeService = dateTimeService;
125        }
126    
127        public void setParameterService(ParameterService parameterService) {
128            this.parameterService = parameterService;
129        }
130    
131        public void setDocumentService(DocumentService documentService) {
132            this.documentService = documentService;
133        }
134    
135        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
136            this.dataDictionaryService = dataDictionaryService;
137        }
138    
139        public void setVendorService(VendorService vendorService) {
140            this.vendorService = vendorService;
141        }
142    
143        public void setPersistenceService(PersistenceService persistenceService) {
144            this.persistenceService = persistenceService;
145        }
146    
147        public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
148            this.purchaseOrderService = purchaseOrderService;
149        }
150    
151        public void setNoteService(NoteService noteService) {
152            this.noteService = noteService;
153        }
154    
155        public void setUniversityDateService(UniversityDateService universityDateService) {
156            this.universityDateService = universityDateService;
157        }
158        
159        public void setTaxService(TaxService taxService) {
160            this.taxService = taxService;
161        }
162        
163        /**
164         * @see org.kuali.kfs.module.purap.document.service.PurapService#updateStatus(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument, java.lang.String)
165         */
166      //TODO hjs: is this method really needed now that we don't have status history tables?
167        public boolean updateStatus(PurchasingAccountsPayableDocument document, String newStatus) {
168            LOG.debug("updateStatus() started");
169    
170            if (ObjectUtils.isNotNull(document) || ObjectUtils.isNotNull(newStatus)) {
171                String oldStatus = document.getStatusCode();
172                document.setStatusCode(newStatus);
173                if ( LOG.isDebugEnabled() ) {
174                    LOG.debug("Status of document #" + document.getDocumentNumber() + " has been changed from " + oldStatus + " to " + newStatus);
175                }
176                return true;
177            }
178            else {
179                return false;
180            }
181        }
182    
183        public void saveRoutingDataForRelatedDocuments(Integer accountsPayablePurchasingDocumentLinkIdentifier) {
184            
185            try {
186                //save requisition routing data
187                List<RequisitionView> reqViews = getRelatedViews(RequisitionView.class, accountsPayablePurchasingDocumentLinkIdentifier);
188                for (Iterator<RequisitionView> iterator = reqViews.iterator(); iterator.hasNext();) {
189                    RequisitionView view = (RequisitionView) iterator.next();
190                    Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber());
191                    doc.getDocumentHeader().getWorkflowDocument().saveRoutingData();
192                }
193                
194                //save purchase order routing data
195                List<PurchaseOrderView> poViews = getRelatedViews(PurchaseOrderView.class, accountsPayablePurchasingDocumentLinkIdentifier);
196                for (Iterator<PurchaseOrderView> iterator = poViews.iterator(); iterator.hasNext();) {
197                    PurchaseOrderView view = (PurchaseOrderView) iterator.next();
198                    Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber());
199                    doc.getDocumentHeader().getWorkflowDocument().saveRoutingData();
200                }
201                
202                //save payment request routing data
203                List<PaymentRequestView> preqViews = getRelatedViews(PaymentRequestView.class, accountsPayablePurchasingDocumentLinkIdentifier);
204                for (Iterator<PaymentRequestView> iterator = preqViews.iterator(); iterator.hasNext();) {
205                    PaymentRequestView view = (PaymentRequestView) iterator.next();
206                    Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber());
207                    doc.getDocumentHeader().getWorkflowDocument().saveRoutingData();
208                }
209                
210                //save credit memo routing data
211                List<CreditMemoView> cmViews = getRelatedViews(CreditMemoView.class, accountsPayablePurchasingDocumentLinkIdentifier);
212                for (Iterator<CreditMemoView> iterator = cmViews.iterator(); iterator.hasNext();) {
213                    CreditMemoView view = (CreditMemoView) iterator.next();
214                    Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber());
215                    doc.getDocumentHeader().getWorkflowDocument().saveRoutingData();
216                }
217                
218                //save line item receiving routing data
219                List<LineItemReceivingView> lineViews = getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
220                for (Iterator<LineItemReceivingView> iterator = lineViews.iterator(); iterator.hasNext();) {
221                    LineItemReceivingView view = (LineItemReceivingView) iterator.next();
222                    Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber());
223                    doc.getDocumentHeader().getWorkflowDocument().saveRoutingData();
224                }
225    
226                //save correction receiving routing data
227                List<CorrectionReceivingView> corrViews = getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
228                for (Iterator<CorrectionReceivingView> iterator = corrViews.iterator(); iterator.hasNext();) {
229                    CorrectionReceivingView view = (CorrectionReceivingView) iterator.next();
230                    Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber());
231                    doc.getDocumentHeader().getWorkflowDocument().saveRoutingData();
232                }
233                
234                //save bulk receiving routing data
235                List<BulkReceivingView> bulkViews = getRelatedViews(BulkReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
236                for (Iterator<BulkReceivingView> iterator = bulkViews.iterator(); iterator.hasNext();) {
237                    BulkReceivingView view = (BulkReceivingView) iterator.next();
238                    Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber());
239                    doc.getDocumentHeader().getWorkflowDocument().saveRoutingData();
240                }
241            }
242            catch (WorkflowException e) {
243                throw new InfrastructureException("unable to save routing data for related docs", e);
244            }
245    
246        }
247    
248        /**
249         * @see org.kuali.kfs.module.purap.document.service.PurapService#getRelatedDocumentIds(java.lang.Integer)
250         */
251        public List<String> getRelatedDocumentIds(Integer accountsPayablePurchasingDocumentLinkIdentifier) {
252            LOG.debug("getRelatedDocumentIds() started");
253            List<String> documentIdList = new ArrayList<String>();
254    
255            //get requisition views
256            List<RequisitionView> reqViews = getRelatedViews(RequisitionView.class, accountsPayablePurchasingDocumentLinkIdentifier);
257            for (Iterator<RequisitionView> iterator = reqViews.iterator(); iterator.hasNext();) {
258                RequisitionView view = (RequisitionView) iterator.next();
259                documentIdList.add(view.getDocumentNumber());
260            }
261            
262            //get purchase order views
263            List<PurchaseOrderView> poViews = getRelatedViews(PurchaseOrderView.class, accountsPayablePurchasingDocumentLinkIdentifier);
264            for (Iterator<PurchaseOrderView> iterator = poViews.iterator(); iterator.hasNext();) {
265                PurchaseOrderView view = (PurchaseOrderView) iterator.next();
266                documentIdList.add(view.getDocumentNumber());
267            }
268            
269            //get payment request views
270            List<PaymentRequestView> preqViews = getRelatedViews(PaymentRequestView.class, accountsPayablePurchasingDocumentLinkIdentifier);
271            for (Iterator<PaymentRequestView> iterator = preqViews.iterator(); iterator.hasNext();) {
272                PaymentRequestView view = (PaymentRequestView) iterator.next();
273                documentIdList.add(view.getDocumentNumber());
274            }
275            
276            //get credit memo views
277            List<CreditMemoView> cmViews = getRelatedViews(CreditMemoView.class, accountsPayablePurchasingDocumentLinkIdentifier);
278            for (Iterator<CreditMemoView> iterator = cmViews.iterator(); iterator.hasNext();) {
279                CreditMemoView view = (CreditMemoView) iterator.next();
280                documentIdList.add(view.getDocumentNumber());
281            }
282            
283            //get line item receiving views
284            List<LineItemReceivingView> lineViews = getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
285            for (Iterator<LineItemReceivingView> iterator = lineViews.iterator(); iterator.hasNext();) {
286                LineItemReceivingView view = (LineItemReceivingView) iterator.next();
287                documentIdList.add(view.getDocumentNumber());
288            }
289    
290            //get correction receiving views
291            List<CorrectionReceivingView> corrViews = getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
292            for (Iterator<CorrectionReceivingView> iterator = corrViews.iterator(); iterator.hasNext();) {
293                CorrectionReceivingView view = (CorrectionReceivingView) iterator.next();
294                documentIdList.add(view.getDocumentNumber());
295            }
296            
297            //get bulk receiving views
298            List<BulkReceivingView> bulkViews = getRelatedViews(BulkReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier);
299            for (Iterator<BulkReceivingView> iterator = bulkViews.iterator(); iterator.hasNext();) {
300                BulkReceivingView view = (BulkReceivingView) iterator.next();
301                documentIdList.add(view.getDocumentNumber());
302            }
303            
304            //TODO (hjs)get electronic invoice reject views???
305            
306            return documentIdList;
307        }
308    
309        /**
310         * @see org.kuali.kfs.module.purap.document.service.PurapService#getRelatedViews(java.lang.Class, java.lang.Integer)
311         */
312        @SuppressWarnings("unchecked")
313        public List getRelatedViews(Class clazz, Integer accountsPayablePurchasingDocumentLinkIdentifier) {
314            LOG.debug("getRelatedViews() started");
315    
316            Map criteria = new HashMap();
317            criteria.put("accountsPayablePurchasingDocumentLinkIdentifier", accountsPayablePurchasingDocumentLinkIdentifier);
318            
319            // retrieve in descending order of document number so that newer documents are in the front
320            List boList = (List) businessObjectService.findMatchingOrderBy(clazz, criteria, KFSPropertyConstants.DOCUMENT_NUMBER, false);
321            return boList;
322        }
323    
324        /**
325         * @see org.kuali.kfs.module.purap.document.service.PurapService#addBelowLineItems(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument)
326         */
327        @SuppressWarnings("unchecked")
328        public void addBelowLineItems(PurchasingAccountsPayableDocument document) {
329            LOG.debug("addBelowLineItems() started");
330    
331            String[] itemTypes = getBelowTheLineForDocument(document);
332    
333            List<PurApItem> existingItems = document.getItems();
334    
335            List<PurApItem> belowTheLine = new ArrayList<PurApItem>();
336            // needed in case they get out of sync below won't work
337            sortBelowTheLine(itemTypes, existingItems, belowTheLine);
338    
339            List<String> existingItemTypes = new ArrayList<String>();
340            for (PurApItem existingItem : existingItems) {
341                existingItemTypes.add(existingItem.getItemTypeCode());
342            }
343    
344            Class itemClass = document.getItemClass();
345    
346            for (int i = 0; i < itemTypes.length; i++) {
347                int lastFound;
348                if (!existingItemTypes.contains(itemTypes[i])) {
349                    try {
350                        if (i > 0) {
351                            lastFound = existingItemTypes.lastIndexOf(itemTypes[i - 1]) + 1;
352                        }
353                        else {
354                            lastFound = existingItemTypes.size();
355                        }
356                        PurApItem newItem = (PurApItem) itemClass.newInstance();
357                        newItem.setItemTypeCode(itemTypes[i]);
358                        newItem.setPurapDocument(document);
359                        existingItems.add(lastFound, newItem);
360                        existingItemTypes.add(itemTypes[i]);
361                    }
362                    catch (Exception e) {
363                        // do something
364                    }
365                }
366            }
367            
368            document.fixItemReferences();
369        }
370    
371        /**
372         * Sorts the below the line elements
373         * 
374         * @param itemTypes
375         * @param existingItems
376         * @param belowTheLine
377         */
378        protected void sortBelowTheLine(String[] itemTypes, List<PurApItem> existingItems, List<PurApItem> belowTheLine) {
379            LOG.debug("sortBelowTheLine() started");
380    
381            // sort existing below the line if any
382            for (int i = 0; i < existingItems.size(); i++) {
383                PurApItem purApItem = existingItems.get(i);
384                if (purApItem.getItemType().isAdditionalChargeIndicator()) {
385                    belowTheLine.add(existingItems.get(i));
386                }
387            }
388            existingItems.removeAll(belowTheLine);
389            for (int i = 0; i < itemTypes.length; i++) {
390                for (PurApItem purApItem : belowTheLine) {
391                    if (StringUtils.equalsIgnoreCase(purApItem.getItemTypeCode(), itemTypes[i])) {
392                        existingItems.add(purApItem);
393                        break;
394                    }
395                }
396            }
397            belowTheLine.removeAll(existingItems);
398            if (belowTheLine.size() != 0) {
399                throw new RuntimeException("below the line item sort didn't work: trying to remove an item without adding it back");
400            }
401        }
402    
403        /**
404         * @see org.kuali.kfs.module.purap.document.service.PurapService#sortBelowTheLine(java.lang.String[], java.util.List, java.util.List)
405         */
406        public void sortBelowTheLine(PurchasingAccountsPayableDocument document) {
407            LOG.debug("sortBelowTheLine() started");
408    
409            String[] itemTypes = getBelowTheLineForDocument(document);
410    
411            List<PurApItem> existingItems = document.getItems();
412    
413            List<PurApItem> belowTheLine = new ArrayList<PurApItem>();
414            // needed in case they get out of sync below won't work
415            sortBelowTheLine(itemTypes, existingItems, belowTheLine);
416        }
417    
418        /**
419         * @see org.kuali.kfs.module.purap.document.service.PurapService#getBelowTheLineForDocument(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument)
420         */
421        public String[] getBelowTheLineForDocument(PurchasingAccountsPayableDocument document) {
422            LOG.debug("getBelowTheLineForDocument() started");
423    
424            // Obtain a list of below the line items from system parameter
425    //        String documentTypeClassName = document.getClass().getName();
426    //        String[] documentTypeArray = StringUtils.split(documentTypeClassName, ".");
427    //        String documentType = documentTypeArray[documentTypeArray.length - 1];
428    
429            //FIXME RELEASE 3 (hjs) why is this "if" here with no code in it?  is it supposed to be doing somethign?
430            // If it's a credit memo, we'll have to append the source of the credit memo
431            // whether it's created from a Vendor, a PO or a PREQ.
432    //        if (documentType.equals("CreditMemoDocument")) {
433    //
434    //        }
435    
436            String documentType = dataDictionaryService.getDocumentTypeNameByClass(document.getClass());
437            
438            try {
439                return parameterService.getParameterValues(Class.forName(PurapConstants.PURAP_DETAIL_TYPE_CODE_MAP.get(documentType)), PurapConstants.BELOW_THE_LINES_PARAMETER).toArray(new String[] {});
440            }
441            catch (ClassNotFoundException e) {
442                throw new RuntimeException("The getBelowTheLineForDocument method of PurapServiceImpl was unable to resolve the document class for type: " + PurapConstants.PURAP_DETAIL_TYPE_CODE_MAP.get(documentType), e);
443            }
444        }
445    
446        /**
447         * @see org.kuali.kfs.module.purap.document.service.PurapService#getBelowTheLineByType(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument,
448         *      org.kuali.kfs.module.purap.businessobject.ItemType)
449         */
450        public PurApItem getBelowTheLineByType(PurchasingAccountsPayableDocument document, ItemType iT) {
451            LOG.debug("getBelowTheLineByType() started");
452    
453            String[] itemTypes = getBelowTheLineForDocument(document);
454            boolean foundItemType = false;
455            for (String itemType : itemTypes) {
456                if (StringUtils.equals(iT.getItemTypeCode(), itemType)) {
457                    foundItemType = true;
458                    break;
459                }
460            }
461            if (!foundItemType) {
462                return null;
463            }
464    
465            PurApItem belowTheLineItem = null;
466            for (PurApItem item : (List<PurApItem>) document.getItems()) {
467                if (item.getItemType().isAdditionalChargeIndicator()) {
468                    if (StringUtils.equals(iT.getItemTypeCode(), item.getItemType().getItemTypeCode())) {
469                        belowTheLineItem = item;
470                        break;
471                    }
472                }
473            }
474            return belowTheLineItem;
475        }
476        
477        /**
478         * @see org.kuali.kfs.module.purap.document.service.PurapService#getDateFromOffsetFromToday(int)
479         */
480        public java.sql.Date getDateFromOffsetFromToday(int offsetDays) {
481            Calendar calendar = dateTimeService.getCurrentCalendar();
482            calendar.add(Calendar.DATE, offsetDays);
483            return new java.sql.Date(calendar.getTimeInMillis());
484        }
485    
486        /**
487         * @see org.kuali.kfs.module.purap.document.service.PurapService#isDateInPast(java.sql.Date)
488         */
489        public boolean isDateInPast(Date compareDate) {
490            LOG.debug("isDateInPast() started");
491    
492            Date today = dateTimeService.getCurrentSqlDate();
493            int diffFromToday = dateTimeService.dateDiff(today, compareDate, false);
494            return (diffFromToday < 0);
495        }
496    
497        /**
498         * @see org.kuali.kfs.module.purap.document.service.PurapService#isDateMoreThanANumberOfDaysAway(java.sql.Date, int)
499         */
500        public boolean isDateMoreThanANumberOfDaysAway(Date compareDate, int daysAway) {
501            LOG.debug("isDateMoreThanANumberOfDaysAway() started");
502    
503            Date todayAtMidnight = dateTimeService.getCurrentSqlDateMidnight();
504            Calendar daysAwayCalendar = dateTimeService.getCalendar(todayAtMidnight);
505            daysAwayCalendar.add(Calendar.DATE, daysAway);
506            Timestamp daysAwayTime = new Timestamp(daysAwayCalendar.getTime().getTime());
507            Calendar compareCalendar = dateTimeService.getCalendar(compareDate);
508            compareCalendar.set(Calendar.HOUR, 0);
509            compareCalendar.set(Calendar.MINUTE, 0);
510            compareCalendar.set(Calendar.SECOND, 0);
511            compareCalendar.set(Calendar.MILLISECOND, 0);
512            compareCalendar.set(Calendar.AM_PM, Calendar.AM);
513            Timestamp compareTime = new Timestamp(compareCalendar.getTime().getTime());
514            return (compareTime.compareTo(daysAwayTime) > 0);
515        }
516    
517        /**
518         * @see org.kuali.kfs.module.purap.document.service.PurapService#isDateAYearAfterToday(java.sql.Date)
519         */
520        public boolean isDateAYearBeforeToday(Date compareDate) {
521            LOG.debug("isDateAYearBeforeToday() started");
522    
523            Calendar calendar = dateTimeService.getCurrentCalendar();
524            calendar.add(Calendar.YEAR, -1);
525            java.sql.Date yearAgo = new java.sql.Date(calendar.getTimeInMillis());
526            int diffFromYearAgo = dateTimeService.dateDiff(compareDate, yearAgo, false);
527            return (diffFromYearAgo > 0);
528        }
529    
530        /**
531         * @see org.kuali.kfs.module.purap.document.service.PurapService#getApoLimit(java.lang.Integer, java.lang.String, java.lang.String)
532         */
533        @SuppressWarnings("unchecked")
534        public KualiDecimal getApoLimit(Integer vendorContractGeneratedIdentifier, String chart, String org) {
535            LOG.debug("getApoLimit() started");
536    
537            KualiDecimal purchaseOrderTotalLimit = vendorService.getApoLimitFromContract(vendorContractGeneratedIdentifier, chart, org);
538            
539            // We didn't find the limit on the vendor contract, get it from the org parameter table.
540            if (ObjectUtils.isNull(purchaseOrderTotalLimit) && !ObjectUtils.isNull(chart) && !ObjectUtils.isNull(org)) {
541                OrganizationParameter organizationParameter = new OrganizationParameter();
542                organizationParameter.setChartOfAccountsCode(chart);
543                organizationParameter.setOrganizationCode(org);
544                Map orgParamKeys = persistenceService.getPrimaryKeyFieldValues(organizationParameter);
545                orgParamKeys.put(KNSPropertyConstants.ACTIVE_INDICATOR, true);
546                organizationParameter = (OrganizationParameter) businessObjectService.findByPrimaryKey(OrganizationParameter.class, orgParamKeys);
547                purchaseOrderTotalLimit = (organizationParameter == null) ? null : organizationParameter.getOrganizationAutomaticPurchaseOrderLimit();
548            }
549            
550            if (ObjectUtils.isNull(purchaseOrderTotalLimit)) {
551                String defaultLimit = parameterService.getParameterValue(RequisitionDocument.class, PurapParameterConstants.AUTOMATIC_PURCHASE_ORDER_DEFAULT_LIMIT_AMOUNT);
552                purchaseOrderTotalLimit = new KualiDecimal(defaultLimit);
553            }
554    
555            return purchaseOrderTotalLimit;
556        }
557    
558        /**
559         * @see org.kuali.kfs.module.purap.document.service.PurapService#isFullDocumentEntryCompleted(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument)
560         */
561        public boolean isFullDocumentEntryCompleted(PurchasingAccountsPayableDocument purapDocument) {
562            LOG.debug("isFullDocumentEntryCompleted() started");
563    
564            // for now just return true if not in one of the first few states
565            boolean value = false;
566            if (purapDocument instanceof PaymentRequestDocument) {
567                value = PurapConstants.PaymentRequestStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocument.getStatusCode());
568            }
569            else if (purapDocument instanceof VendorCreditMemoDocument) {
570                value = PurapConstants.CreditMemoStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocument.getStatusCode());
571            }
572            return value;
573        }
574    
575    
576        /**
577         * Main hook point for close/Reopen PO.
578         * 
579         * @see org.kuali.kfs.module.purap.document.service.PurapService#performLogicForCloseReopenPO(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument)
580         */
581        public void performLogicForCloseReopenPO(PurchasingAccountsPayableDocument purapDocument) {
582            LOG.debug("performLogicForCloseReopenPO() started");
583    
584            if (purapDocument instanceof PaymentRequestDocument) {
585                PaymentRequestDocument paymentRequest = (PaymentRequestDocument) purapDocument;
586    
587                if (paymentRequest.isClosePurchaseOrderIndicator() && PurapConstants.PurchaseOrderStatuses.OPEN.equals(paymentRequest.getPurchaseOrderDocument().getStatusCode())) {
588                    // get the po id and get the current po
589                    // check the current po: if status is not closed and there is no pending action... route close po as system user
590                    processCloseReopenPo((AccountsPayableDocumentBase) purapDocument, PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT);
591                }
592    
593            }
594            else if (purapDocument instanceof VendorCreditMemoDocument) {
595                VendorCreditMemoDocument creditMemo = (VendorCreditMemoDocument) purapDocument;
596    
597                if (creditMemo.isReopenPurchaseOrderIndicator() && PurapConstants.PurchaseOrderStatuses.CLOSED.equals(creditMemo.getPurchaseOrderDocument().getStatusCode())) {
598                    // get the po id and get the current PO
599                    // route 'Re-Open PO Document' if PO criteria meets requirements from business rules
600                    processCloseReopenPo((AccountsPayableDocumentBase) purapDocument, PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT);
601                }
602    
603            }
604            else {
605                throw new RuntimeException("Attempted to perform full entry logic for unhandled document type '" + purapDocument.getClass().getName() + "'");
606            }
607    
608        }
609    
610        /**
611         * Remove items that have not been "entered" which means no data has been added to them so no more processing needs to continue
612         * on these items.
613         * 
614         * @param apDocument  AccountsPayableDocument which contains list of items to be reviewed
615         */
616        public void deleteUnenteredItems(PurapItemOperations document) {
617            LOG.debug("deleteUnenteredItems() started");
618            
619            List<PurapEnterableItem> deletionList = new ArrayList<PurapEnterableItem>();
620            for (PurapEnterableItem item : (List<PurapEnterableItem>) document.getItems()) {
621                if (!item.isConsideredEntered()) {
622                    deletionList.add(item);
623                }
624            }
625            document.getItems().removeAll(deletionList);
626        }
627    
628        /**
629         * Actual method that will close or reopen a po.
630         * 
631         * @param apDocument  AccountsPayableDocument
632         * @param docType
633         */
634        @SuppressWarnings("unchecked")
635        public void processCloseReopenPo(AccountsPayableDocumentBase apDocument, String docType) {
636            LOG.debug("processCloseReopenPo() started");
637            
638            String action = null;
639            String newStatus = null;
640            // setup text for note that will be created, will either be closed or reopened
641            if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT.equals(docType)) {
642                action = "closed";
643                newStatus = PurchaseOrderStatuses.PENDING_CLOSE;
644            }
645            else if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT.equals(docType)) {
646                action = "reopened";
647                newStatus = PurchaseOrderStatuses.PENDING_REOPEN;
648            }
649            else {
650                String errorMessage = "Method processCloseReopenPo called using ID + '" + apDocument.getPurapDocumentIdentifier() + "' and invalid doc type '" + docType + "'";
651                LOG.error(errorMessage);
652                throw new RuntimeException(errorMessage);
653            }
654    
655    
656            Integer poId = apDocument.getPurchaseOrderIdentifier();
657            PurchaseOrderDocument purchaseOrderDocument = purchaseOrderService.getCurrentPurchaseOrder(poId);
658            if (!StringUtils.equalsIgnoreCase(purchaseOrderDocument.getDocumentHeader().getWorkflowDocument().getDocumentType(), docType)) {
659                // we are skipping the validation above because it would be too late to correct any errors (i.e. because in
660                // post-processing)
661                purchaseOrderService.createAndRoutePotentialChangeDocument(purchaseOrderDocument.getDocumentNumber(), docType, assemblePurchaseOrderNote(apDocument, docType, action), new ArrayList(), newStatus);
662            }
663    
664            /*
665             * if we made it here, route document has not errored out, so set appropriate indicator depending on what is being
666             * requested.
667             */
668            if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT.equals(docType)) {
669                apDocument.setClosePurchaseOrderIndicator(false);
670                
671                //add a note to the purchase order indicating it has been closed by a payment request document            
672                String userName = apDocument.getLastActionPerformedByPersonName();
673                StringBuffer poNote = new StringBuffer("");
674                poNote.append("PO was closed manually by ");            
675                poNote.append( userName );
676                poNote.append(" in approving PREQ with ID ");
677                poNote.append(apDocument.getDocumentNumber());
678                            
679                //save the note to the purchase order
680                try{
681                    Note noteObj = documentService.createNoteFromDocument(apDocument.getPurchaseOrderDocument(), poNote.toString());
682                    documentService.addNoteToDocument(apDocument.getPurchaseOrderDocument(), noteObj);
683                    noteService.save(noteObj);
684                }catch(Exception e){
685                    String errorMessage = "Error creating and saving close note for purchase order with document service";
686                    LOG.error("processCloseReopenPo() " + errorMessage, e);
687                    throw new RuntimeException(errorMessage, e);
688                }                        
689            }
690            else if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT.equals(docType)) {
691                apDocument.setReopenPurchaseOrderIndicator(false);
692            }
693    
694        }
695    
696        /**
697         * Generate a note for the close/reopen po method.
698         * 
699         * @param docType
700         * @param preqId
701         * @return Note to be saved
702         */
703        protected String assemblePurchaseOrderNote(AccountsPayableDocumentBase apDocument, String docType, String action) {
704            LOG.debug("assemblePurchaseOrderNote() started");
705    
706            String documentLabel = dataDictionaryService.getDocumentLabelByClass(apDocument.getClass());
707            StringBuffer closeReopenNote = new StringBuffer("");
708            String userName = GlobalVariables.getUserSession().getPerson().getName();
709            closeReopenNote.append(dataDictionaryService.getDocumentLabelByTypeName(KFSConstants.FinancialDocumentTypeCodes.PURCHASE_ORDER));
710            closeReopenNote.append(" will be manually ");
711            closeReopenNote.append(action);
712            closeReopenNote.append(" by ");
713            closeReopenNote.append(userName);
714            closeReopenNote.append(" when approving ");
715            closeReopenNote.append(documentLabel);
716            closeReopenNote.append(" with ");
717            closeReopenNote.append(dataDictionaryService.getAttributeLabel(apDocument.getClass(), PurapPropertyConstants.PURAP_DOC_ID));
718            closeReopenNote.append(" ");
719            closeReopenNote.append(apDocument.getPurapDocumentIdentifier());
720    
721            return closeReopenNote.toString();
722        }
723    
724        /**
725         * @see org.kuali.kfs.module.purap.document.service.PurapService#performLogicWithFakedUserSession(java.lang.String, org.kuali.kfs.module.purap.document.service.LogicContainer, java.lang.Object[])
726         */
727        public Object performLogicWithFakedUserSession(String requiredPersonPersonUserId, LogicContainer logicToRun, Object... objects) throws WorkflowException, Exception {
728            LOG.debug("performLogicWithFakedUserSession() started");
729    
730            if (StringUtils.isBlank(requiredPersonPersonUserId)) {
731                throw new RuntimeException("Attempted to perform logic with a fake user session with a blank user person id: '" + requiredPersonPersonUserId + "'");
732            }
733            if (ObjectUtils.isNull(logicToRun)) {
734                throw new RuntimeException("Attempted to perform logic with a fake user session with no logic to run");
735            }
736            UserSession actualUserSession = GlobalVariables.getUserSession();
737            try {
738                GlobalVariables.setUserSession(new UserSession(requiredPersonPersonUserId));
739                return logicToRun.runLogic(objects);
740            }
741            finally {
742                GlobalVariables.setUserSession(actualUserSession);
743            }
744        }
745    
746        /**
747         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#saveDocumentNoValidation(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
748         */
749        public void saveDocumentNoValidation(Document document) {
750            try {
751                // FIXME The following code of refreshing document header is a temporary fix for the issue that
752                // in some cases (seem random) the doc header fields are null; and if doc header is refreshed, the workflow doc becomes null.
753                // The root cause of this is that when some docs are retrieved manually using OJB criteria, ref objs such as doc header or workflow doc
754                // aren't retrieved; the solution would be to add these refreshing when documents are retrieved in those OJB methods.
755                if (document.getDocumentHeader() != null && document.getDocumentHeader().getDocumentNumber() == null) {
756                    KualiWorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument();
757                    document.refreshReferenceObject("documentHeader");               
758                    document.getDocumentHeader().setWorkflowDocument(workflowDocument);
759                }
760                documentService.saveDocument(document, DocumentSystemSaveEvent.class);
761                
762                // At this point, the work-flow status will not change for the current document, but the document status will.
763                // This causes the search indices for the document to become out of synch, and will show a different status type
764                // in the RICE lookup results screen.
765                WorkflowDocumentActions wrkflowDocActions = SpringContext.getBean(WorkflowDocumentActions.class);
766                wrkflowDocActions.indexDocument(Long.valueOf(document.getDocumentNumber()));
767            }
768            catch (WorkflowException we) {
769                String errorMsg = "Workflow error saving document # " + document.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
770                LOG.error(errorMsg, we);
771                throw new RuntimeException(errorMsg, we);
772            }
773            catch (NumberFormatException ne) {
774                String errorMsg = "Invalid document number format for document # " + document.getDocumentHeader().getDocumentNumber() + " " + ne.getMessage();
775                LOG.error(errorMsg, ne);
776                throw new RuntimeException(errorMsg, ne);
777            }
778        }
779        
780        public boolean isDocumentStoppedInRouteNode(PurchasingAccountsPayableDocument document, String nodeName) {
781            List<String> currentRouteLevels = new ArrayList<String>();
782            try {
783                KualiWorkflowDocument workflowDoc = document.getDocumentHeader().getWorkflowDocument();
784                currentRouteLevels = Arrays.asList(document.getDocumentHeader().getWorkflowDocument().getNodeNames());
785                if (currentRouteLevels.contains(nodeName) && workflowDoc.isApprovalRequested()) {
786                    return true;
787                }
788                return false;
789            }
790            catch (WorkflowException e) {
791                throw new RuntimeException(e);
792            }
793        }
794    
795        /**
796         * @see org.kuali.kfs.module.purap.document.service.PurapService#allowEncumberNextFiscalYear()
797         */
798        public boolean allowEncumberNextFiscalYear() {
799            LOG.debug("allowEncumberNextFiscalYear() started");
800    
801            java.util.Date today = dateTimeService.getCurrentDate();
802            java.util.Date closingDate = universityDateService.getLastDateOfFiscalYear(universityDateService.getCurrentFiscalYear());
803            int allowEncumberNext = (Integer.parseInt(parameterService.getParameterValue(RequisitionDocument.class, PurapRuleConstants.ALLOW_ENCUMBER_NEXT_YEAR_DAYS)));
804            int diffTodayClosing = dateTimeService.dateDiff(today, closingDate, false);
805    
806            if (ObjectUtils.isNotNull(closingDate) && ObjectUtils.isNotNull(today) && ObjectUtils.isNotNull(allowEncumberNext)) {
807                if ( LOG.isDebugEnabled() ) {
808                    LOG.debug("allowEncumberNextFiscalYear() today = " + dateTimeService.toDateString(today) + "; encumber next FY range = " + allowEncumberNext + " - " + dateTimeService.toDateTimeString(today));
809                }
810    
811                if (allowEncumberNext >= diffTodayClosing && diffTodayClosing >= KualiDecimal.ZERO.intValue()) {
812                    LOG.debug("allowEncumberNextFiscalYear() encumber next FY allowed; return true.");
813                    return true;
814                }
815            }
816            LOG.debug("allowEncumberNextFiscalYear() encumber next FY not allowed; return false.");
817            return false;
818        }
819    
820        /**
821         * @see org.kuali.kfs.module.purap.document.service.PurapService#getAllowedFiscalYears()
822         */
823        public List<Integer> getAllowedFiscalYears() {
824            List<Integer> allowedYears = new ArrayList<Integer>();
825            Integer currentFY = universityDateService.getCurrentFiscalYear();
826            allowedYears.add(currentFY);
827            if (allowEncumberNextFiscalYear()) {
828                allowedYears.add(currentFY + 1);
829            }
830            return allowedYears;
831        }
832    
833        /**
834         * @see org.kuali.kfs.module.purap.document.service.PurapService#isTodayWithinApoAllowedRange()
835         */
836        public boolean isTodayWithinApoAllowedRange() {
837            java.util.Date today = dateTimeService.getCurrentDate();
838            Integer currentFY = universityDateService.getCurrentFiscalYear();
839            java.util.Date closingDate = universityDateService.getLastDateOfFiscalYear(currentFY);
840            int allowApoDate = (Integer.parseInt(parameterService.getParameterValue(RequisitionDocument.class, PurapRuleConstants.ALLOW_APO_NEXT_FY_DAYS)));
841            int diffTodayClosing = dateTimeService.dateDiff(today, closingDate, true);
842    
843            return diffTodayClosing <= allowApoDate;
844        }
845            
846        /**
847         * 
848         * @see org.kuali.kfs.module.purap.document.service.PurapService#clearTax(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument)
849         */
850        public void clearTax(PurchasingAccountsPayableDocument purapDocument, boolean useTax){
851            for (PurApItem item : purapDocument.getItems()) {
852                if(useTax) {
853                    item.getUseTaxItems().clear();
854                } else {
855                    item.setItemTaxAmount(null);
856                }
857            }
858        }
859        
860        public void updateUseTaxIndicator(PurchasingAccountsPayableDocument purapDocument, boolean newUseTaxIndicatorValue) {
861            boolean currentUseTaxIndicator = purapDocument.isUseTaxIndicator();
862            if(currentUseTaxIndicator!=newUseTaxIndicatorValue) {
863                //i.e. if the indicator changed clear out the tax
864                clearTax(purapDocument, currentUseTaxIndicator);
865            }
866            purapDocument.setUseTaxIndicator(newUseTaxIndicatorValue);
867        }
868        
869        /**
870         * @see org.kuali.kfs.module.purap.document.service.PurapService#calculateTax(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument)
871         */
872        public void calculateTax(PurchasingAccountsPayableDocument purapDocument){
873            
874              boolean salesTaxInd = SpringContext.getBean(ParameterService.class).getIndicatorParameter(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
875              boolean useTaxIndicator = purapDocument.isUseTaxIndicator();
876              String deliveryState = getDeliveryState(purapDocument);
877              String deliveryPostalCode = getDeliveryPostalCode(purapDocument);
878              Date transactionTaxDate = purapDocument.getTransactionTaxDate();
879              
880              //calculate if sales tax enabled for purap
881              if( salesTaxInd || useTaxIndicator ){
882                  //iterate over items and calculate tax if taxable
883                  for(PurApItem item : purapDocument.getItems()){
884                      if( isTaxable(useTaxIndicator, deliveryState, item) ){
885                          calculateItemTax(useTaxIndicator, deliveryPostalCode, transactionTaxDate, item, item.getUseTaxClass(), purapDocument);
886                      }
887                  }
888              }
889          }
890          
891          public String getDeliveryState(PurchasingAccountsPayableDocument purapDocument){
892              if (purapDocument instanceof PurchasingDocument){
893                  PurchasingDocument document = (PurchasingDocument)purapDocument; 
894                  return document.getDeliveryStateCode();
895              }else if (purapDocument instanceof AccountsPayableDocument){
896                  AccountsPayableDocument document = (AccountsPayableDocument)purapDocument;
897                  if (document.getPurchaseOrderDocument() == null){
898                      throw new RuntimeException("PurchaseOrder document does not exists");
899                  }
900                  return document.getPurchaseOrderDocument().getDeliveryStateCode();
901              }
902              return null;
903          }
904          
905          protected String getDeliveryPostalCode(PurchasingAccountsPayableDocument purapDocument){
906              if (purapDocument instanceof PurchasingDocument){
907                  PurchasingDocument document = (PurchasingDocument)purapDocument; 
908                  return document.getDeliveryPostalCode();
909              }else if (purapDocument instanceof AccountsPayableDocument){
910                  AccountsPayableDocument docBase = (AccountsPayableDocument)purapDocument;
911                  if (docBase.getPurchaseOrderDocument() == null){
912                      throw new RuntimeException("PurchaseOrder document does not exists");
913                  }
914                  return docBase.getPurchaseOrderDocument().getDeliveryPostalCode();
915              }
916              return null;
917          }
918        
919        /**
920         * Determines if the item is taxable based on a decision tree.
921         * 
922         * @param useTaxIndicator
923         * @param deliveryState
924         * @param item
925         * @return
926         */
927        public boolean isTaxable(boolean useTaxIndicator, String deliveryState, PurApItem item){
928            
929            boolean taxable = false;
930            
931            if(item.getItemType().isTaxableIndicator() &&
932               ((ObjectUtils.isNull(item.getItemTaxAmount()) && useTaxIndicator == false) || useTaxIndicator) &&
933               (doesCommodityAllowCallToTaxService(item)) &&                      
934               (doesAccountAllowCallToTaxService(deliveryState, item)) ){
935                
936                taxable = true;
937            }
938            return taxable;
939        }
940    
941        /**
942         * 
943         * @see org.kuali.kfs.module.purap.document.service.PurapService#isTaxableForSummary(boolean, java.lang.String, org.kuali.kfs.module.purap.businessobject.PurApItem)
944         */
945        public boolean isTaxableForSummary(boolean useTaxIndicator, String deliveryState, PurApItem item){
946            
947            boolean taxable = false;
948            
949            if(item.getItemType().isTaxableIndicator() &&           
950               (doesCommodityAllowCallToTaxService(item)) &&                      
951               (doesAccountAllowCallToTaxService(deliveryState, item)) ){
952                
953                taxable = true;
954            }
955            return taxable;
956        }
957    
958        /**
959         * Determines if the the tax service should be called due to the commodity code.
960         * 
961         * @param item
962         * @return
963         */
964        protected boolean doesCommodityAllowCallToTaxService(PurApItem item) {
965            boolean callService = true;
966    
967            // only check for commodity code on above the line times (additional charges don't allow commodity code)
968            if (item.getItemType().isLineItemIndicator()) {
969                if (item instanceof PurchasingItem) {
970                    PurchasingItemBase purItem = (PurchasingItemBase) item;
971                    callService = isCommodityCodeTaxable(purItem.getCommodityCode());
972                }// if not a purchasing item, then pull item from PO
973                else if (item instanceof AccountsPayableItem) {
974                    AccountsPayableItem apItem = (AccountsPayableItem) item;
975                    PurchaseOrderItem poItem = apItem.getPurchaseOrderItem();
976                    if (ObjectUtils.isNotNull(poItem)) {
977                        callService = isCommodityCodeTaxable(poItem.getCommodityCode());
978                    }
979                }
980            }
981    
982            return callService;
983        }
984    
985        protected boolean isCommodityCodeTaxable(CommodityCode commodityCode){
986            boolean isTaxable = true;
987            
988            if(ObjectUtils.isNotNull(commodityCode)){                
989                
990                if(commodityCode.isSalesTaxIndicator() == false){
991                    //not taxable, so don't call service
992                    isTaxable = false;
993                }//if true we want to call service
994                
995            }//if null, return true
996    
997            return isTaxable;
998        }
999        
1000        /**
1001         * @see org.kuali.kfs.module.purap.document.service.PurapService#isDeliveryStateTaxable(java.lang.String)
1002         */
1003        public boolean isDeliveryStateTaxable(String deliveryState) {
1004            ParameterEvaluator parmEval = SpringContext.getBean(ParameterService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, TaxParameters.TAXABLE_DELIVERY_STATES, deliveryState);
1005            return parmEval.evaluationSucceeds();
1006        }
1007        
1008        /**
1009         * Checks if the account is taxable, based on the delivery state, fund/subfund groups, and object code level/consolidations.
1010         * 
1011         * @param deliveryState
1012         * @param item
1013         * @return
1014         */
1015        protected boolean doesAccountAllowCallToTaxService(String deliveryState, PurApItem item) {
1016            boolean callService = false;
1017            boolean deliveryStateTaxable = isDeliveryStateTaxable(deliveryState);       
1018    
1019            for (PurApAccountingLine acctLine : item.getSourceAccountingLines()) {
1020                if(isAccountingLineTaxable(acctLine, deliveryStateTaxable)){
1021                    callService = true;
1022                    break;
1023                }
1024            }
1025    
1026            return callService;
1027        }
1028        
1029        /**
1030         * @see org.kuali.kfs.module.purap.document.service.PurapService#isAccountingLineTaxable(org.kuali.kfs.module.purap.businessobject.PurApAccountingLine, boolean)
1031         */
1032        public boolean isAccountingLineTaxable(PurApAccountingLine acctLine, boolean deliveryStateTaxable){
1033            boolean isTaxable = false;
1034            String parameterSuffix = null;
1035            
1036            if (deliveryStateTaxable) {
1037                parameterSuffix = TaxParameters.FOR_TAXABLE_STATES_SUFFIX;
1038            }
1039            else {
1040                parameterSuffix = TaxParameters.FOR_NON_TAXABLE_STATES_SUFFIX;
1041            }
1042    
1043            // is account (fund/subfund) and object code (level/consolidation) taxable?
1044            if (isAccountTaxable(parameterSuffix, acctLine) && isObjectCodeTaxable(parameterSuffix, acctLine)) {
1045                isTaxable = true;
1046            }
1047            
1048            return isTaxable;
1049        }
1050        
1051        /**
1052         * Checks if the account fund/subfund groups are in a set of parameters taking into account allowed/denied constraints and
1053         * ultimately determines if taxable.
1054         * 
1055         * @param parameterSuffix
1056         * @param acctLine
1057         * @return
1058         */
1059        protected boolean isAccountTaxable(String parameterSuffix, PurApAccountingLine acctLine){
1060            
1061            boolean isAccountTaxable = false;
1062            String fundParam = TaxParameters.TAXABLE_FUND_GROUPS_PREFIX + parameterSuffix;
1063            String subFundParam = TaxParameters.TAXABLE_SUB_FUND_GROUPS_PREFIX + parameterSuffix;
1064            ParameterEvaluator fundParamEval = null;
1065            ParameterEvaluator subFundParamEval = null;
1066            
1067            if (ObjectUtils.isNull(acctLine.getAccount().getSubFundGroup())){
1068                acctLine.refreshNonUpdateableReferences();
1069            }
1070            
1071            fundParamEval = SpringContext.getBean(ParameterService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, fundParam, acctLine.getAccount().getSubFundGroup().getFundGroupCode());
1072            subFundParamEval = SpringContext.getBean(ParameterService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, subFundParam, acctLine.getAccount().getSubFundGroupCode());
1073    
1074            if( (isAllowedFound(fundParamEval) && (isAllowedFound(subFundParamEval) || isAllowedNotFound(subFundParamEval) || isDeniedNotFound(subFundParamEval))) ||
1075                (isAllowedNotFound(fundParamEval) && isAllowedFound(subFundParamEval)) ||
1076                (isDeniedFound(fundParamEval) && isAllowedFound(subFundParamEval)) ||
1077                (isDeniedNotFound(fundParamEval) && (isAllowedFound(subFundParamEval) || isAllowedNotFound(subFundParamEval) || isDeniedNotFound(subFundParamEval))) ){
1078                
1079                isAccountTaxable = true;
1080            }
1081            
1082            return isAccountTaxable;
1083        }
1084    
1085        /**
1086         * Checks if the object code level/consolidation groups are in a set of parameters taking into account allowed/denied constraints and
1087         * ultimately determines if taxable.
1088         * 
1089         * @param parameterSuffix
1090         * @param acctLine
1091         * @return
1092         */
1093        protected boolean isObjectCodeTaxable(String parameterSuffix, PurApAccountingLine acctLine){
1094            
1095            boolean isObjectCodeTaxable = false;
1096            String levelParam = TaxParameters.TAXABLE_OBJECT_LEVELS_PREFIX + parameterSuffix;
1097            String consolidationParam = TaxParameters.TAXABLE_OBJECT_CONSOLIDATIONS_PREFIX + parameterSuffix;
1098            ParameterEvaluator levelParamEval = null;
1099            ParameterEvaluator consolidationParamEval = null;
1100            
1101            //refresh financial object level
1102            acctLine.getObjectCode().refreshReferenceObject("financialObjectLevel");
1103            
1104            levelParamEval = SpringContext.getBean(ParameterService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, levelParam, acctLine.getObjectCode().getFinancialObjectLevelCode());
1105            consolidationParamEval = SpringContext.getBean(ParameterService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, consolidationParam, acctLine.getObjectCode().getFinancialObjectLevel().getFinancialConsolidationObjectCode());
1106    
1107            if( (isAllowedFound(levelParamEval) && (isAllowedFound(consolidationParamEval) || isAllowedNotFound(consolidationParamEval) || isDeniedNotFound(consolidationParamEval))) ||
1108                (isAllowedNotFound(levelParamEval) && isAllowedFound(consolidationParamEval)) ||
1109                (isDeniedFound(levelParamEval) && isAllowedFound(consolidationParamEval)) ||
1110                (isDeniedNotFound(levelParamEval) && (isAllowedFound(consolidationParamEval) || isAllowedNotFound(consolidationParamEval) || isDeniedNotFound(consolidationParamEval))) ){
1111                    
1112                isObjectCodeTaxable = true;
1113            }
1114    
1115            return isObjectCodeTaxable;
1116        }
1117    
1118        /**
1119         * Helper method to work with parameter evaluator to find, allowed and found in parameter value.
1120         * 
1121         * @param eval
1122         * @return
1123         */
1124        protected boolean isAllowedFound(ParameterEvaluator eval) {
1125            boolean exists = false;
1126    
1127            if (eval.evaluationSucceeds() && eval.constraintIsAllow()) {
1128                exists = true;
1129            }
1130    
1131            return exists;
1132        }
1133    
1134        /**
1135         * Helper method to work with parameter evaluator to find, allowed and not found in parameter value.
1136         * 
1137         * @param eval
1138         * @return
1139         */
1140        protected boolean isAllowedNotFound(ParameterEvaluator eval) {
1141            boolean exists = false;
1142    
1143            if (eval.evaluationSucceeds() == false && eval.constraintIsAllow()) {
1144                exists = true;
1145            }
1146    
1147            return exists;
1148        }
1149        
1150        /**
1151         * Helper method to work with parameter evaluator to find, denied and found in parameter value.
1152         * 
1153         * @param eval
1154         * @return
1155         */
1156        protected boolean isDeniedFound(ParameterEvaluator eval) {
1157            boolean exists = false;
1158    
1159            if (eval.evaluationSucceeds() == false && eval.constraintIsAllow() == false) {
1160                exists = true;
1161            }
1162    
1163            return exists;
1164        }
1165    
1166        /**
1167         * Helper method to work with parameter evaluator to find, denied and not found in parameter value.
1168         * 
1169         * @param eval
1170         * @return
1171         */
1172        protected boolean isDeniedNotFound(ParameterEvaluator eval) {
1173            boolean exists = false;
1174    
1175            if (eval.evaluationSucceeds() && eval.constraintIsAllow() == false) {
1176                exists = true;
1177            }
1178    
1179            return exists;
1180        }
1181    
1182        /**
1183         * @param useTaxIndicator
1184         * @param deliveryPostalCode
1185         * @param transactionTaxDate
1186         * @param item
1187         * @param itemUseTaxClass
1188         */
1189        @SuppressWarnings("unchecked")
1190        protected void calculateItemTax(boolean useTaxIndicator, 
1191                                      String deliveryPostalCode, 
1192                                      Date transactionTaxDate, 
1193                                      PurApItem item,
1194                                      Class itemUseTaxClass, 
1195                                      PurchasingAccountsPayableDocument purapDocument){
1196            
1197            if (!useTaxIndicator){
1198                if (!StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE) &&
1199                    !StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) {
1200                    KualiDecimal taxAmount = taxService.getTotalSalesTaxAmount(transactionTaxDate, deliveryPostalCode, item.getExtendedPrice());
1201                    item.setItemTaxAmount(taxAmount);
1202                }
1203            } else {
1204                KualiDecimal extendedPrice = item.getExtendedPrice();
1205                
1206                if(StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)){
1207                   KualiDecimal taxablePrice = getFullDiscountTaxablePrice(extendedPrice, purapDocument);
1208                   extendedPrice = taxablePrice;
1209                }
1210                List<TaxDetail> taxDetails = taxService.getUseTaxDetails(transactionTaxDate, deliveryPostalCode, extendedPrice);
1211                List<PurApItemUseTax> newUseTaxItems = new ArrayList<PurApItemUseTax>(); 
1212                if (taxDetails != null){
1213                    for (TaxDetail taxDetail : taxDetails) {
1214                        try {
1215                            PurApItemUseTax useTaxitem = (PurApItemUseTax)itemUseTaxClass.newInstance();
1216                            useTaxitem.setChartOfAccountsCode(taxDetail.getChartOfAccountsCode());
1217                            useTaxitem.setFinancialObjectCode(taxDetail.getFinancialObjectCode());
1218                            useTaxitem.setAccountNumber(taxDetail.getAccountNumber());
1219                            useTaxitem.setItemIdentifier(item.getItemIdentifier());
1220                            useTaxitem.setRateCode(taxDetail.getRateCode());
1221                            useTaxitem.setTaxAmount(taxDetail.getTaxAmount());
1222                            newUseTaxItems.add(useTaxitem);
1223                        }
1224                        catch (Exception e) {
1225                            /**
1226                             * Shallow.. This never happen - InstantiationException/IllegalAccessException
1227                             * To be safe, throw a runtime exception
1228                             */
1229                            throw new RuntimeException(e);
1230                        } 
1231                    }
1232                }
1233                item.setUseTaxItems(newUseTaxItems);
1234            }
1235        }
1236        
1237        public KualiDecimal getFullDiscountTaxablePrice(KualiDecimal extendedPrice, PurchasingAccountsPayableDocument purapDocument){
1238            KualiDecimal taxablePrice = KualiDecimal.ZERO;
1239            KualiDecimal taxableLineItemPrice = KualiDecimal.ZERO;
1240            KualiDecimal totalLineItemPrice = KualiDecimal.ZERO;
1241            boolean useTaxIndicator = purapDocument.isUseTaxIndicator();
1242            String deliveryState = getDeliveryState(purapDocument);
1243            
1244            // iterate over items and calculate tax if taxable
1245            for (PurApItem item : purapDocument.getItems()) {
1246                if (item.getItemType().isLineItemIndicator()){
1247                    //only when extended price exists
1248                    if(ObjectUtils.isNotNull(item.getExtendedPrice())){
1249                        if(isTaxable(useTaxIndicator, deliveryState, item)){
1250                            taxableLineItemPrice = taxableLineItemPrice.add(item.getExtendedPrice());
1251                            totalLineItemPrice = totalLineItemPrice.add(item.getExtendedPrice());
1252                        }else{
1253                            totalLineItemPrice = totalLineItemPrice.add(item.getExtendedPrice());
1254                        }
1255                    }
1256                }
1257            }
1258            
1259            //check nonzero so no divide by zero errors, and make sure extended price is not null
1260            if(totalLineItemPrice.isNonZero() && ObjectUtils.isNotNull(extendedPrice))
1261                taxablePrice = taxableLineItemPrice.divide(totalLineItemPrice).multiply(extendedPrice);
1262            
1263            return taxablePrice;
1264        }
1265    
1266        public void prorateForTradeInAndFullOrderDiscount(PurchasingAccountsPayableDocument purDoc) {
1267            
1268            if (purDoc instanceof VendorCreditMemoDocument){
1269                throw new RuntimeException("This method not applicable for VCM documents");
1270            }
1271            
1272            //TODO: are we throwing sufficient errors in this method?
1273            PurApItem fullOrderDiscount = null;
1274            PurApItem tradeIn = null;
1275            KualiDecimal totalAmount = KualiDecimal.ZERO;
1276            KualiDecimal totalTaxAmount = KualiDecimal.ZERO;
1277            
1278            List<PurApAccountingLine> distributedAccounts = null;
1279            List<SourceAccountingLine> summaryAccounts = null;
1280    
1281            // iterate through below the line and grab FoD and TrdIn.
1282            for (PurApItem item : purDoc.getItems()) {
1283                if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) {
1284                    fullOrderDiscount = item;
1285                }
1286                else if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE)) {
1287                    tradeIn = item;
1288                }
1289            }
1290            // If Discount is not null or zero get proration list for all non misc items and set (if not empty?)
1291            if (fullOrderDiscount != null && 
1292                fullOrderDiscount.getExtendedPrice() != null && 
1293                fullOrderDiscount.getExtendedPrice().isNonZero()) {
1294                
1295                // empty
1296                GlobalVariables.getMessageList().add("Full order discount accounts cleared and regenerated");
1297                fullOrderDiscount.getSourceAccountingLines().clear();
1298                //total amount is pretax dollars
1299                totalAmount = purDoc.getTotalDollarAmountAboveLineItems().subtract(purDoc.getTotalTaxAmountAboveLineItems());
1300                totalTaxAmount = purDoc.getTotalTaxAmountAboveLineItems();
1301                
1302                //Before we generate account summary, we should update the account amounts first.
1303                purapAccountingService.updateAccountAmounts(purDoc);
1304                
1305                //calculate tax
1306                boolean salesTaxInd = SpringContext.getBean(KualiConfigurationService.class).getIndicatorParameter(PurapConstants.PURAP_NAMESPACE, "Document", PurapParameterConstants.ENABLE_SALES_TAX_IND);
1307                boolean useTaxIndicator = purDoc.isUseTaxIndicator();
1308                
1309                if(salesTaxInd == true && (ObjectUtils.isNull(fullOrderDiscount.getItemTaxAmount()) && useTaxIndicator == false)){            
1310                    KualiDecimal discountAmount = fullOrderDiscount.getExtendedPrice();                
1311                    KualiDecimal discountTaxAmount = discountAmount.divide(totalAmount).multiply(totalTaxAmount);
1312                
1313                    fullOrderDiscount.setItemTaxAmount(discountTaxAmount);
1314                }
1315    
1316                //generate summary
1317                summaryAccounts = purapAccountingService.generateSummary(PurApItemUtils.getAboveTheLineOnly(purDoc.getItems()));
1318    
1319                if (summaryAccounts.size() == 0) {
1320                    if (purDoc.shouldGiveErrorForEmptyAccountsProration()) {
1321                        GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "full order discount");
1322                    }
1323                } else {                
1324                    //prorate accounts
1325                    distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount.add(totalTaxAmount), 2, fullOrderDiscount.getAccountingLineClass());
1326                    
1327                    for (PurApAccountingLine distributedAccount : distributedAccounts) {
1328                        BigDecimal percent = distributedAccount.getAccountLinePercent();
1329                        BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue()));
1330                        distributedAccount.setAccountLinePercent(roundedPercent);
1331                    }
1332                                                                            
1333                    //update amounts on distributed accounts
1334                    purapAccountingService.updateAccountAmountsWithTotal(distributedAccounts, fullOrderDiscount.getTotalAmount());                    
1335    
1336                    fullOrderDiscount.setSourceAccountingLines(distributedAccounts);
1337                }
1338            } else if(fullOrderDiscount != null && 
1339                     (fullOrderDiscount.getExtendedPrice() == null || fullOrderDiscount.getExtendedPrice().isZero())) {
1340               fullOrderDiscount.getSourceAccountingLines().clear();
1341            }
1342            
1343            // If tradeIn is not null or zero get proration list for all non misc items and set (if not empty?)
1344            if (tradeIn != null && tradeIn.getExtendedPrice() != null && tradeIn.getExtendedPrice().isNonZero()) {
1345                
1346                tradeIn.getSourceAccountingLines().clear();
1347    
1348                totalAmount = purDoc.getTotalDollarAmountForTradeIn();
1349                KualiDecimal tradeInTotalAmount = tradeIn.getTotalAmount();
1350                //Before we generate account summary, we should update the account amounts first.
1351                purapAccountingService.updateAccountAmounts(purDoc);
1352    
1353                //Before generating the summary, lets replace the object code in a cloned accounts collection sothat we can 
1354                //consolidate all the modified object codes during summary generation.
1355                List clonedTradeInItems = new ArrayList();
1356                List objectSubTypesRequiringQty = SpringContext.getBean(KualiConfigurationService.class).getParameterValues(PurapConstants.PURAP_NAMESPACE, "Document", PurapParameterConstants.OBJECT_SUB_TYPES_REQUIRING_QUANTITY);
1357                List purchasingObjectSubTypes = SpringContext.getBean(KualiConfigurationService.class).getParameterValues("KFS-CAB", "Document", PurapParameterConstants.PURCHASING_OBJECT_SUB_TYPES);
1358    
1359                 String tradeInCapitalObjectCode = SpringContext.getBean(ParameterService.class).getParameterValue(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_ASSET");
1360                 String tradeInCapitalLeaseObjCd = SpringContext.getBean(ParameterService.class).getParameterValue(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_LEASE");
1361                            
1362                for(PurApItem item : purDoc.getTradeInItems()){
1363                   PurApItem cloneItem = (PurApItem)ObjectUtils.deepCopy(item);
1364                   List<PurApAccountingLine> sourceAccountingLines = cloneItem.getSourceAccountingLines();
1365                   for(PurApAccountingLine accountingLine : sourceAccountingLines){
1366                      if(objectSubTypesRequiringQty.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())){
1367                             accountingLine.setFinancialObjectCode(tradeInCapitalObjectCode);
1368                      }else if(purchasingObjectSubTypes.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())){
1369                            accountingLine.setFinancialObjectCode(tradeInCapitalLeaseObjCd);
1370                      }
1371                   }
1372                   clonedTradeInItems.add(cloneItem);
1373                }  
1374                
1375                
1376                summaryAccounts = purapAccountingService.generateSummary(clonedTradeInItems);
1377                if (summaryAccounts.size() == 0) {
1378                    if (purDoc.shouldGiveErrorForEmptyAccountsProration()) {
1379                        GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "trade in");    
1380                    }
1381                }
1382                else {
1383                    distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, 2, tradeIn.getAccountingLineClass());
1384                    for (PurApAccountingLine distributedAccount : distributedAccounts) {
1385                        BigDecimal percent = distributedAccount.getAccountLinePercent();
1386                        BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue()));
1387                        distributedAccount.setAccountLinePercent(roundedPercent);
1388                        // set the accountAmount same as tradeIn amount not line item's amount
1389                        resetAccountAmount(distributedAccount, tradeInTotalAmount);
1390                    }
1391                    tradeIn.setSourceAccountingLines(distributedAccounts);
1392                }
1393            }
1394        }
1395    
1396        private void resetAccountAmount(PurApAccountingLine distributedAccount, KualiDecimal tradeInTotalAmount) {
1397            BigDecimal pct = distributedAccount.getAccountLinePercent();
1398            BigDecimal amount = tradeInTotalAmount.bigDecimalValue().multiply(pct).divide(new BigDecimal(100));
1399            distributedAccount.setAmount(new KualiDecimal(amount));
1400        }
1401    
1402        public void clearAllTaxes(PurchasingAccountsPayableDocument purapDoc){
1403            if (purapDoc.getItems() != null){
1404                for (int i = 0; i < purapDoc.getItems().size(); i++) {
1405                    PurApItem item = purapDoc.getItems().get(i);
1406                    if (purapDoc.isUseTaxIndicator()) {
1407                        item.setUseTaxItems(new ArrayList<PurApItemUseTax>());
1408                    }
1409                    else {
1410                        item.setItemTaxAmount(null);
1411                    }
1412                }
1413            }
1414        }
1415        
1416        /**
1417         * Determines if the item type specified conflict with the Account tax policy.
1418         * 
1419         * @param purchasingDocument purchasing document to check
1420         * @param item item to check if in conflict with tax policy
1421         * @return true if item is in conflict, false otherwise
1422         */
1423        public boolean isItemTypeConflictWithTaxPolicy(PurchasingDocument purchasingDocument, PurApItem item) {
1424            boolean conflict = false;
1425            
1426            String deliveryState = getDeliveryState(purchasingDocument);
1427            if (item.getItemType().isLineItemIndicator() ) {        
1428                if ( item.getItemType().isTaxableIndicator() ) {
1429                    if ( isTaxDisabledForVendor(purchasingDocument)) {
1430                        conflict = true;
1431                    }
1432                }
1433                // only check account tax policy if accounting line exists            
1434                if ( !item.getSourceAccountingLines().isEmpty() ) {
1435                    if ( !doesAccountAllowCallToTaxService(deliveryState, item)  ) {
1436                        conflict = true;
1437                    }
1438                }
1439            }
1440            return conflict;
1441        }
1442        
1443        /**
1444         * Determines if tax is disabled for vendor, in default always returns false
1445         * @param purapDocument the PurchasingDocument with a vendor to check
1446         * @return true if tax is disabled, false if it is not - in foundation KFS, tax is never disabled
1447         */
1448        protected boolean isTaxDisabledForVendor( PurchasingDocument purapDocument ) {
1449            return false;
1450        }
1451        
1452        public PurapAccountingService getPurapAccountingService() {
1453            return purapAccountingService;
1454        }
1455    
1456        public void setPurapAccountingService(PurapAccountingService purapAccountingService) {
1457            this.purapAccountingService = purapAccountingService;
1458        }
1459    }
1460