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