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