View Javadoc
1   /*
2    * Copyright 2007 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.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.text.StrBuilder;
21  import org.kuali.ole.module.purap.*;
22  import org.kuali.ole.module.purap.PurapConstants.InvoiceStatuses;
23  import org.kuali.ole.module.purap.PurapConstants.ItemTypeCodes;
24  import org.kuali.ole.module.purap.PurapConstants.PRQSDocumentsStrings;
25  import org.kuali.ole.module.purap.PurapParameterConstants.NRATaxParameters;
26  import org.kuali.ole.module.purap.businessobject.*;
27  import org.kuali.ole.module.purap.document.*;
28  import org.kuali.ole.module.purap.document.dataaccess.InvoiceDao;
29  import org.kuali.ole.module.purap.document.service.*;
30  import org.kuali.ole.module.purap.document.validation.event.AttributedContinuePurapEvent;
31  import org.kuali.ole.module.purap.exception.PurError;
32  import org.kuali.ole.module.purap.service.PurapAccountingService;
33  import org.kuali.ole.module.purap.service.PurapGeneralLedgerService;
34  import org.kuali.ole.module.purap.util.PurApItemUtils;
35  import org.kuali.ole.module.purap.util.VendorGroupingHelper;
36  import org.kuali.ole.select.OleSelectConstant;
37  import org.kuali.ole.sys.OLEConstants;
38  import org.kuali.ole.sys.OLEPropertyConstants;
39  import org.kuali.ole.sys.businessobject.AccountingLine;
40  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
41  import org.kuali.ole.sys.context.SpringContext;
42  import org.kuali.ole.sys.service.BankService;
43  import org.kuali.ole.sys.service.FinancialSystemWorkflowHelperService;
44  import org.kuali.ole.sys.service.UniversityDateService;
45  import org.kuali.ole.sys.service.impl.OleParameterConstants;
46  import org.kuali.ole.vnd.VendorConstants;
47  import org.kuali.ole.vnd.businessobject.PaymentTermType;
48  import org.kuali.ole.vnd.businessobject.VendorAddress;
49  import org.kuali.ole.vnd.businessobject.VendorDetail;
50  import org.kuali.ole.vnd.document.service.VendorService;
51  import org.kuali.rice.core.api.config.property.ConfigurationService;
52  import org.kuali.rice.core.api.datetime.DateTimeService;
53  import org.kuali.rice.core.api.util.type.KualiDecimal;
54  import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
55  import org.kuali.rice.coreservice.api.parameter.Parameter;
56  import org.kuali.rice.coreservice.api.parameter.ParameterKey;
57  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
58  import org.kuali.rice.kew.api.KewApiServiceLocator;
59  import org.kuali.rice.kew.api.WorkflowDocument;
60  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
61  import org.kuali.rice.kew.api.document.search.DocumentSearchResult;
62  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
63  import org.kuali.rice.kew.api.exception.WorkflowException;
64  import org.kuali.rice.kim.api.identity.Person;
65  import org.kuali.rice.kns.service.DataDictionaryService;
66  import org.kuali.rice.krad.bo.DocumentHeader;
67  import org.kuali.rice.krad.bo.Note;
68  import org.kuali.rice.krad.exception.ValidationException;
69  import org.kuali.rice.krad.service.BusinessObjectService;
70  import org.kuali.rice.krad.service.DocumentService;
71  import org.kuali.rice.krad.service.KualiRuleService;
72  import org.kuali.rice.krad.service.NoteService;
73  import org.kuali.rice.krad.util.GlobalVariables;
74  import org.kuali.rice.krad.util.KRADPropertyConstants;
75  import org.kuali.rice.krad.util.ObjectUtils;
76  import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
77  import org.springframework.transaction.annotation.Transactional;
78  
79  import java.math.BigDecimal;
80  import java.sql.Date;
81  import java.sql.Timestamp;
82  import java.util.*;
83  
84  /**
85   * This class provides services of use to a payment request document
86   */
87  @Transactional
88  public class InvoiceServiceImpl implements InvoiceService {
89      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InvoiceServiceImpl.class);
90  
91      protected DateTimeService dateTimeService;
92      protected DocumentService documentService;
93      protected NoteService noteService;
94      protected PurapService purapService;
95      protected InvoiceDao invoiceDao;
96      protected ParameterService parameterService;
97      protected ConfigurationService configurationService;
98      protected NegativeInvoiceApprovalLimitService negativeInvoiceApprovalLimitService;
99      protected PurapAccountingService purapAccountingService;
100     protected BusinessObjectService businessObjectService;
101     protected PurApWorkflowIntegrationService purapWorkflowIntegrationService;
102     protected WorkflowDocumentService workflowDocumentService;
103     protected AccountsPayableService accountsPayableService;
104     protected VendorService vendorService;
105     protected DataDictionaryService dataDictionaryService;
106     protected UniversityDateService universityDateService;
107     protected BankService bankService;
108     protected PurchaseOrderService purchaseOrderService;
109     protected FinancialSystemWorkflowHelperService financialSystemWorkflowHelperService;
110     protected KualiRuleService kualiRuleService;
111     protected boolean currencyTypeIndicator;
112     /**
113      * NOTE: unused
114      *
115      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtractByCM(String, org.kuali.ole.module.purap.document.VendorCreditMemoDocument)
116      */
117     @Override
118     @Deprecated
119     public Collection<InvoiceDocument> getInvoicesToExtractByCM(String campusCode, VendorCreditMemoDocument cmd) {
120         LOG.debug("getInvoicesByCM() started");
121         Date currentSqlDateMidnight = dateTimeService.getCurrentSqlDateMidnight();
122         List<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(campusCode, null, null, cmd.getVendorHeaderGeneratedIdentifier(), cmd.getVendorDetailAssignedIdentifier(), currentSqlDateMidnight);
123 
124         return filterInvoiceByAppDocStatus(invoiceIterator,
125                 InvoiceStatuses.APPDOC_AUTO_APPROVED,
126                 InvoiceStatuses.APPDOC_DEPARTMENT_APPROVED);
127     }
128 
129 
130     /**
131      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtractByVendor(String,
132      *      org.kuali.ole.module.purap.util.VendorGroupingHelper, java.sql.Date)
133      */
134     @Override
135     public Collection<InvoiceDocument> getInvoicesToExtractByVendor(String campusCode, VendorGroupingHelper vendor, Date onOrBeforeInvoicePayDate) {
136         LOG.debug("getInvoicesByVendor() started");
137         Collection<InvoiceDocument> invoiceDocuments = invoiceDao.getInvoicesToExtractForVendor(campusCode, vendor, onOrBeforeInvoicePayDate);
138 
139         return filterInvoiceByAppDocStatus(invoiceDocuments,
140                 InvoiceStatuses.APPDOC_AUTO_APPROVED,
141                 InvoiceStatuses.APPDOC_DEPARTMENT_APPROVED);
142     }
143 
144     /**
145      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtract(java.sql.Date)
146      */
147     @Override
148     public Collection<InvoiceDocument> getInvoicesToExtract(Date onOrBeforeInvoicePayDate) {
149         LOG.debug("getInvoicesToExtract() started");
150 
151         Collection<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(false, null, onOrBeforeInvoicePayDate);
152         return filterInvoiceByAppDocStatus(invoiceIterator,
153                 InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
154     }
155 
156     /**
157      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesToExtractSpecialPayments(String,
158      *      java.sql.Date)
159      */
160     @Override
161     public Collection<InvoiceDocument> getInvoicesToExtractSpecialPayments(String chartCode, Date onOrBeforeInvoicePayDate) {
162         LOG.debug("getInvoicesToExtractSpecialPayments() started");
163 
164         Collection<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(true, chartCode, onOrBeforeInvoicePayDate);
165         return filterInvoiceByAppDocStatus(invoiceIterator,
166                 InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
167     }
168 
169     /**
170      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getImmediateInvoicesToExtract(String)
171      */
172     @Override
173     public Collection<InvoiceDocument> getImmediateInvoicesToExtract(String chartCode) {
174         LOG.debug("getImmediateInvoicesToExtract() started");
175 
176         Collection<InvoiceDocument> invoiceIterator = invoiceDao.getImmediateInvoicesToExtract(chartCode);
177         return filterInvoiceByAppDocStatus(invoiceIterator,
178                 InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
179     }
180 
181     /**
182      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoiceToExtractByChart(String,
183      *      java.sql.Date)
184      */
185     @Override
186     public Collection<InvoiceDocument> getInvoiceToExtractByChart(String chartCode, Date onOrBeforeInvoicePayDate) {
187         LOG.debug("getInvoiceToExtractByChart() started");
188 
189         Collection<InvoiceDocument> invoiceIterator = invoiceDao.getInvoicesToExtract(false, chartCode, onOrBeforeInvoicePayDate);
190         return filterInvoiceByAppDocStatus(invoiceIterator,
191                 InvoiceStatuses.STATUSES_ALLOWED_FOR_EXTRACTION);
192     }
193 
194     /**
195      * @see org.kuali.ole.module.purap.document.service.InvoiceService#autoApproveInvoices()
196      */
197     @Override
198     public boolean autoApproveInvoices() {
199         if (LOG.isInfoEnabled()) {
200             LOG.info("Starting autoApproveInvoices.");
201         }
202         boolean hadErrorAtLeastOneError = true;
203         // should objects from existing user session be copied over
204         Date todayAtMidnight = dateTimeService.getCurrentSqlDateMidnight();
205 
206         List<String> docNumbers = invoiceDao.getEligibleForAutoApproval(todayAtMidnight);
207         docNumbers = filterInvoiceByAppDocStatus(docNumbers, InvoiceStatuses.PRQS_STATUSES_FOR_AUTO_APPROVE);
208         if (LOG.isInfoEnabled()) {
209             LOG.info(" -- Initial filtering complete, returned " + new Integer(docNumbers.size()).toString() + " docs.");
210         }
211 
212         List<InvoiceDocument> docs = new ArrayList<InvoiceDocument>();
213         for (String docNumber : docNumbers) {
214             InvoiceDocument prqs = getInvoiceByDocumentNumber(docNumber);
215             if (ObjectUtils.isNotNull(prqs)) {
216                 docs.add(prqs);
217             }
218         }
219         if (LOG.isInfoEnabled()) {
220             LOG.info(" -- Initial filtering complete, returned " + new Integer((docs == null ? 0 : docs.size())).toString() + " docs.");
221         }
222         if (docs != null) {
223             String samt = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD,PurapParameterConstants.PURAP_DEFAULT_NEGATIVE_PAYMENT_REQUEST_APPROVAL_LIMIT);
224             KualiDecimal defaultMinimumLimit = new KualiDecimal(samt);
225             if (LOG.isInfoEnabled()) {
226                 LOG.info(" -- Using default limit value of " + defaultMinimumLimit.toString() + ".");
227             }
228             for (InvoiceDocument invoiceDocument : docs) {
229                 hadErrorAtLeastOneError |= !autoApproveInvoice(invoiceDocument, defaultMinimumLimit);
230             }
231         }
232         return hadErrorAtLeastOneError;
233     }
234 
235     /**
236      * NOTE: in the event of auto-approval failure, this method may throw a RuntimeException, indicating to Spring transactional
237      * management that the transaction should be rolled back.
238      *
239      * @see org.kuali.ole.module.purap.document.service.InvoiceService#autoApproveInvoice(String,
240      *      org.kuali.rice.core.api.util.type.KualiDecimal)
241      */
242     @Override
243     public boolean autoApproveInvoice(String docNumber, KualiDecimal defaultMinimumLimit) {
244         InvoiceDocument invoiceDocument = null;
245         try {
246             invoiceDocument = (InvoiceDocument) documentService.getByDocumentHeaderId(docNumber);
247             if (invoiceDocument.isHoldIndicator() || invoiceDocument.isInvoiceCancelIndicator() || !Arrays.asList(InvoiceStatuses.PRQS_STATUSES_FOR_AUTO_APPROVE).contains(invoiceDocument.getApplicationDocumentStatus())) {
248                 // this condition is based on the conditions that InvoiceDaoOjb.getEligibleDocumentNumbersForAutoApproval()
249                 // uses to query
250                 // the database. Rechecking these conditions to ensure that the document is eligible for auto-approval, because
251                 // we're not running things
252                 // within the same transaction anymore and changes could have occurred since we called that method that make this
253                 // document not auto-approvable
254 
255                 // note that this block does not catch all race conditions
256                 // however, this error condition is not enough to make us return an error code, so just skip the document
257                 LOG.warn("Invoice Document " + invoiceDocument.getDocumentNumber() + " could not be auto-approved because it has either been placed on hold, " + " requested cancel, or does not have one of the PRQS statuses for auto-approve.");
258                 return true;
259             }
260             if (autoApproveInvoice(invoiceDocument, defaultMinimumLimit)) {
261                 if (LOG.isInfoEnabled()) {
262                     LOG.info("Auto-approval for payment request successful.  Doc number: " + docNumber);
263                 }
264                 return true;
265             } else {
266                 LOG.error("Invoice Document " + docNumber + " could not be auto-approved.");
267                 return false;
268             }
269         } catch (WorkflowException we) {
270             LOG.error("Exception encountered when retrieving document number " + docNumber + ".", we);
271             // throw a runtime exception up so that we can force a rollback
272             throw new RuntimeException("Exception encountered when retrieving document number " + docNumber + ".", we);
273         }
274     }
275 
276     /**
277      * NOTE: in the event of auto-approval failure, this method may throw a RuntimeException, indicating to Spring transactional
278      * management that the transaction should be rolled back.
279      *
280      * @see org.kuali.ole.module.purap.document.service.InvoiceService#autoApproveInvoice(org.kuali.ole.module.purap.document.InvoiceDocument,
281      *      org.kuali.rice.core.api.util.type.KualiDecimal)
282      */
283     @Override
284     public boolean autoApproveInvoice(InvoiceDocument doc, KualiDecimal defaultMinimumLimit) {
285         if (isEligibleForAutoApproval(doc, defaultMinimumLimit)) {
286             try {
287                 // Much of the rice frameworks assumes that document instances that are saved via DocumentService.saveDocument are
288                 // those
289                 // that were dynamically created by PojoFormBase (i.e., the Document instance wasn't created from OJB). We need to
290                 // make
291                 // a deep copy and materialize collections to fulfill that assumption so that collection elements will delete
292                 // properly
293 
294                 // TODO: maybe rewriting PurapService.calculateItemTax could be rewritten so that the a deep copy doesn't need to be
295                 // made
296                 // by taking advantage of OJB's managed array lists
297                 try {
298                     ObjectUtils.materializeUpdateableCollections(doc);
299                     for (InvoiceItem item : (List<InvoiceItem>) doc.getItems()) {
300                         ObjectUtils.materializeUpdateableCollections(item);
301                     }
302                 } catch (Exception ex) {
303                     throw new RuntimeException(ex);
304                 }
305                 doc = (InvoiceDocument) ObjectUtils.deepCopy(doc);
306                 //purapService.updateStatus(doc, InvoiceStatuses.AUTO_APPROVED);
307                 doc.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_AUTO_APPROVED);
308 
309                 documentService.blanketApproveDocument(doc, "auto-approving: Total is below threshold.", null);
310             } catch (WorkflowException we) {
311                 LOG.error("Exception encountered when approving document number " + doc.getDocumentNumber() + ".", we);
312                 // throw a runtime exception up so that we can force a rollback
313                 throw new RuntimeException("Exception encountered when approving document number " + doc.getDocumentNumber() + ".", we);
314             }
315         }
316         return true;
317     }
318 
319     /**
320      * NOTE: in the event of auto-approval failure, this method may throw a RuntimeException, indicating to Spring transactional
321      * management that the transaction should be rolled back.
322      *
323      * @see org.kuali.ole.module.purap.document.service.PaymentRequestService#autoApprovePaymentRequest(java.lang.String,
324      *      org.kuali.rice.core.api.util.type.KualiDecimal)
325      */
326     @Override
327     public boolean autoApprovePaymentRequest(String docNumber) {
328         PaymentRequestDocument paymentRequestDocument = null;
329         try {
330             paymentRequestDocument = (PaymentRequestDocument) documentService.getByDocumentHeaderId(docNumber);
331             if (paymentRequestDocument.isHoldIndicator() || paymentRequestDocument.isPaymentRequestedCancelIndicator() || !Arrays.asList(PurapConstants.PaymentRequestStatuses.PREQ_STATUSES_FOR_AUTO_APPROVE).contains(paymentRequestDocument.getApplicationDocumentStatus())) {
332                 // this condition is based on the conditions that PaymentRequestDaoOjb.getEligibleDocumentNumbersForAutoApproval()
333                 // uses to query
334                 // the database. Rechecking these conditions to ensure that the document is eligible for auto-approval, because
335                 // we're not running things
336                 // within the same transaction anymore and changes could have occurred since we called that method that make this
337                 // document not auto-approvable
338 
339                 // note that this block does not catch all race conditions
340                 // however, this error condition is not enough to make us return an error code, so just skip the document
341                 LOG.warn("Payment Request Document " + paymentRequestDocument.getDocumentNumber() + " could not be auto-approved because it has either been placed on hold, " + " requested cancel, or does not have one of the PREQ statuses for auto-approve.");
342                 return true;
343             }
344             if (autoApprovePaymentRequest(paymentRequestDocument)) {
345                 if (LOG.isInfoEnabled()) {
346                     LOG.info("Auto-approval for payment request successful.  Doc number: " + docNumber);
347                 }
348                 return true;
349             } else {
350                 LOG.error("Payment Request Document " + docNumber + " could not be auto-approved.");
351                 return false;
352             }
353         } catch (WorkflowException we) {
354             LOG.error("Exception encountered when retrieving document number " + docNumber + ".", we);
355             // throw a runtime exception up so that we can force a rollback
356             throw new RuntimeException("Exception encountered when retrieving document number " + docNumber + ".", we);
357         }
358     }
359 
360 
361     /**
362      * NOTE: in the event of auto-approval failure, this method may throw a RuntimeException, indicating to Spring transactional
363      * management that the transaction should be rolled back.
364      *
365      * @see org.kuali.ole.module.purap.document.service.PaymentRequestService#autoApprovePaymentRequest(org.kuali.ole.module.purap.document.PaymentRequestDocument,
366      *      org.kuali.rice.core.api.util.type.KualiDecimal)
367      */
368     public boolean autoApprovePaymentRequest(PaymentRequestDocument doc) {
369         try {
370             // Much of the rice frameworks assumes that document instances that are saved via DocumentService.saveDocument are
371             // those
372             // that were dynamically created by PojoFormBase (i.e., the Document instance wasn't created from OJB). We need to
373             // make
374             // a deep copy and materialize collections to fulfill that assumption so that collection elements will delete
375             // properly
376 
377             // TODO: maybe rewriting PurapService.calculateItemTax could be rewritten so that the a deep copy doesn't need to be
378             // made
379             // by taking advantage of OJB's managed array lists
380             try {
381                 ObjectUtils.materializeUpdateableCollections(doc);
382                 for (PaymentRequestItem item : (List<PaymentRequestItem>) doc.getItems()) {
383                     ObjectUtils.materializeUpdateableCollections(item);
384                 }
385             } catch (Exception ex) {
386                 throw new RuntimeException(ex);
387             }
388             doc = (PaymentRequestDocument) ObjectUtils.deepCopy(doc);
389             //purapService.updateStatus(doc, PaymentRequestStatuses.AUTO_APPROVED);
390             doc.updateAndSaveAppDocStatus(PurapConstants.PaymentRequestStatuses.APPDOC_AUTO_APPROVED);
391 
392             documentService.blanketApproveDocument(doc, "auto-approving: Document Created from Invoice.", null);
393         } catch (WorkflowException we) {
394             LOG.error("Exception encountered when approving document number " + doc.getDocumentNumber() + ".", we);
395             // throw a runtime exception up so that we can force a rollback
396             throw new RuntimeException("Exception encountered when approving document number " + doc.getDocumentNumber() + ".", we);
397         }
398         return true;
399     }
400 
401     /**
402      * Determines whether or not a payment request document can be automatically approved. FYI - If fiscal reviewers are allowed to
403      * save account changes without the full account validation running then this method must call full account validation to make
404      * sure auto approver is not blanket approving an invalid document according the the accounts on the items
405      *
406      * @param document            The payment request document to be determined whether it can be automatically approved.
407      * @param defaultMinimumLimit The amount to be used as the minimum amount if no limit was found or the default is less than the
408      *                            limit.
409      * @return boolean true if the payment request document is eligible for auto approval.
410      */
411     protected boolean isEligibleForAutoApproval(InvoiceDocument document, KualiDecimal defaultMinimumLimit) {
412         // Check if vendor is foreign.
413         if (document.getVendorDetail().getCurrencyType()!=null){
414             if(document.getVendorDetail().getCurrencyType().getCurrencyType().equalsIgnoreCase(OleSelectConstant.CURRENCY_TYPE_NAME)){
415                 currencyTypeIndicator=true;
416             }
417             else{
418                 currencyTypeIndicator=false;
419             }
420         }
421         if (!currencyTypeIndicator) {
422             if (LOG.isInfoEnabled()) {
423                 LOG.info(" -- PayReq [" + document.getDocumentNumber() + "] skipped due to a Foreign Vendor.");
424             }
425             return false;
426         }
427 
428         // check to make sure the payment request isn't scheduled to stop in tax review.
429         if (purapWorkflowIntegrationService.willDocumentStopAtGivenFutureRouteNode(document, InvoiceStatuses.NODE_VENDOR_TAX_REVIEW)) {
430             if (LOG.isInfoEnabled()) {
431                 LOG.info(" -- PayReq [" + document.getDocumentNumber() + "] skipped due to requiring Tax Review.");
432             }
433             return false;
434         }
435 
436         // Change to not auto approve if positive approval required indicator set to Yes
437         if (document.isInvoicePositiveApprovalIndicator()) {
438             if (LOG.isInfoEnabled()) {
439                 LOG.info(" -- PayReq [" + document.getDocumentNumber()
440                         + "] skipped due to a Positive Approval Required Indicator set to Yes.");
441             }
442             return false;
443         }
444 
445         // This minimum will be set to the minimum limit derived from all
446         // accounting lines on the document. If no limit is determined, the
447         // default will be used.
448         KualiDecimal minimumAmount = null;
449 
450         // Iterate all source accounting lines on the document, deriving a
451         // minimum limit from each according to chart, chart and account, and
452         // chart and organization.
453         for (SourceAccountingLine line : purapAccountingService.generateSummary((List<PurApItem>)document.getItems())) {
454             // check to make sure the account is in the auto approve exclusion list
455             Map<String, Object> autoApproveMap = new HashMap<String, Object>();
456             autoApproveMap.put("chartOfAccountsCode", line.getChartOfAccountsCode());
457             autoApproveMap.put("accountNumber", line.getAccountNumber());
458             autoApproveMap.put("active", true);
459             AutoApproveExclude autoApproveExclude = businessObjectService.findByPrimaryKey(AutoApproveExclude.class, autoApproveMap);
460             if (autoApproveExclude != null) {
461                 if (LOG.isInfoEnabled()) {
462                     LOG.info(" -- PayReq [" + document.getDocumentNumber() + "] skipped due to source accounting line "
463                             + line.getSequenceNumber() + " using Chart/Account [" + line.getChartOfAccountsCode() + "-"
464                             + line.getAccountNumber() + "], which is excluded in the Auto Approve Exclusions table.");
465                 }
466                 return false;
467             }
468 
469             minimumAmount = getMinimumLimitAmount(negativeInvoiceApprovalLimitService.findByChart(line.getChartOfAccountsCode()), minimumAmount);
470             minimumAmount = getMinimumLimitAmount(negativeInvoiceApprovalLimitService.findByChartAndAccount(line.getChartOfAccountsCode(), line.getAccountNumber()), minimumAmount);
471             minimumAmount = getMinimumLimitAmount(negativeInvoiceApprovalLimitService.findByChartAndOrganization(line.getChartOfAccountsCode(), line.getOrganizationReferenceId()), minimumAmount);
472         }
473 
474         // If Receiving required is set, it's not needed to check the negative payment request approval limit
475         if (document.isReceivingDocumentRequiredIndicator()) {
476             if (LOG.isInfoEnabled()) {
477                 LOG.info(" -- PayReq ["
478                         + document.getDocumentNumber()
479                         + "] auto-approved (ignored dollar limit) due to Receiving Document Required Indicator set to Yes.");
480             }
481             return true;
482         }
483 
484         // If no limit was found or the default is less than the limit, the default limit is used.
485         if (ObjectUtils.isNull(minimumAmount) || defaultMinimumLimit.compareTo(minimumAmount) < 0) {
486             minimumAmount = defaultMinimumLimit;
487         }
488 
489         // The document is eligible for auto-approval if the document total is below the limit.
490         if (document.getFinancialSystemDocumentHeader().getFinancialDocumentTotalAmount().isLessThan(minimumAmount)) {
491             if (LOG.isInfoEnabled()) {
492                 LOG.info(" -- PayReq ["
493                         + document.getDocumentNumber()
494                         + "] auto-approved due to document Total ["
495                         + document.getFinancialSystemDocumentHeader().getFinancialDocumentTotalAmount()
496                         + "] being less than "
497                         + (minimumAmount == defaultMinimumLimit ? "Default Auto-Approval Limit "
498                         : "Configured Auto-Approval Limit ") + "of "
499                         + (minimumAmount == null ? "null" : minimumAmount.toString()) + ".");
500             }
501             return true;
502         }
503 
504         if (LOG.isInfoEnabled()) {
505             LOG.info(" -- PayReq ["
506                     + document.getDocumentNumber()
507                     + "] skipped due to document Total ["
508                     + document.getFinancialSystemDocumentHeader().getFinancialDocumentTotalAmount()
509                     + "] being greater than "
510                     + (minimumAmount == defaultMinimumLimit ? "Default Auto-Approval Limit "
511                     : "Configured Auto-Approval Limit ") + "of "
512                     + (minimumAmount == null ? "null" : minimumAmount.toString()) + ".");
513         }
514 
515         return false;
516     }
517 
518     /**
519      * This method iterates a collection of negative payment request approval limits and returns the minimum of a given minimum
520      * amount and the least among the limits in the collection.
521      *
522      * @param limits        The collection of NegativeInvoiceApprovalLimit to be used in determining the minimum limit amount.
523      * @param minimumAmount The amount to be compared with the collection of NegativeInvoiceApprovalLimit to determine the
524      *                      minimum limit amount.
525      * @return The minimum of the given minimum amount and the least among the limits in the collection.
526      */
527     protected KualiDecimal getMinimumLimitAmount(Collection<NegativeInvoiceApprovalLimit> limits, KualiDecimal minimumAmount) {
528         for (NegativeInvoiceApprovalLimit limit : limits) {
529             KualiDecimal amount = limit.getNegativeInvoiceApprovalLimitAmount();
530             if (null == minimumAmount) {
531                 minimumAmount = amount;
532             } else if (minimumAmount.isGreaterThan(amount)) {
533                 minimumAmount = amount;
534             }
535         }
536         return minimumAmount;
537     }
538 
539     /**
540      * Retrieves a list of payment request documents with the given vendor id and invoice number.
541      *
542      * @param vendorHeaderGeneratedId The vendor header generated id.
543      * @param vendorDetailAssignedId  The vendor detail assigned id.
544      * @return List of payment request document.
545      */
546     @Override
547     public List getInvoicesByVendorNumber(Integer vendorHeaderGeneratedId, Integer vendorDetailAssignedId) {
548         LOG.debug("getActiveInvoicesByVendorNumber() started");
549         return invoiceDao.getActiveInvoicesByVendorNumber(vendorHeaderGeneratedId, vendorDetailAssignedId);
550     }
551 
552     /**
553      * Retrieves a list of payment request documents with the given vendor id and invoice number.
554      *
555      * @param vendorHeaderGeneratedId The vendor header generated id.
556      * @param vendorDetailAssignedId  The vendor detail assigned id.
557      * @param invoiceNumber           The invoice number as entered by AP.
558      * @return List of payment request document.
559      */
560     @Override
561     public List getInvoicesByVendorNumberInvoiceNumber(Integer vendorHeaderGeneratedId, Integer vendorDetailAssignedId, String invoiceNumber) {
562         LOG.debug("getActiveInvoicesByVendorNumberInvoiceNumber() started");
563         return invoiceDao.getActiveInvoicesByVendorNumberInvoiceNumber(vendorHeaderGeneratedId, vendorDetailAssignedId, invoiceNumber);
564     }
565 
566     /**
567      * @see org.kuali.ole.module.purap.document.service.InvoiceService#invoiceDuplicateMessages(org.kuali.ole.module.purap.document.InvoiceDocument)
568      */
569     @Override
570     public HashMap<String, String> invoiceDuplicateMessages(InvoiceDocument document) {
571         HashMap<String, String> msgs;
572         msgs = new HashMap<String, String>();
573 
574         Integer purchaseOrderId = document.getPurchaseOrderIdentifier();
575 
576         if (ObjectUtils.isNotNull(document.getInvoiceDate())) {
577             if (purapService.isDateAYearBeforeToday(document.getInvoiceDate())) {
578                 msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_INVOICE_DATE_A_YEAR_OR_MORE_PAST));
579             }
580         }
581         PurchaseOrderDocument po = document.getPurchaseOrderDocument();
582 
583         if (po != null) {
584             Integer vendorDetailAssignedId = po.getVendorDetailAssignedIdentifier();
585             Integer vendorHeaderGeneratedId = po.getVendorHeaderGeneratedIdentifier();
586 
587             List<InvoiceDocument> prqss = new ArrayList();
588 
589             List<InvoiceDocument> prqssDuplicates = getInvoicesByVendorNumber(vendorHeaderGeneratedId, vendorDetailAssignedId);
590             for (InvoiceDocument duplicatePRQS : prqssDuplicates) {
591                 if (duplicatePRQS.getInvoiceNumber().toUpperCase().equals(document.getInvoiceNumber().toUpperCase())) {
592                     // found the duplicate row... so add to the prqss list...
593                     prqss.add(duplicatePRQS);
594                 }
595             }
596 
597             if (prqss.size() > 0) {
598                 boolean addedMessage = false;
599                 boolean foundCanceledPostApprove = false; // cancelled
600             //    boolean foundCanceledPreApprove = false; // voided
601                 for (InvoiceDocument testPRQS : prqss) {
602                     if (StringUtils.equals(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_POST_AP_APPROVE)) {
603                         foundCanceledPostApprove |= true;
604                     } /*else if (StringUtils.equals(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_IN_PROCESS)) {
605                         foundCanceledPreApprove |= true;
606                     }*/ else {
607                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE));
608                         addedMessage = true;
609                         break;
610                     }
611                 }
612                 // Custom error message for duplicates related to cancelled/voided PRQSs
613                 if (!addedMessage) {
614                     /*if (foundCanceledPostApprove && foundCanceledPreApprove) {
615                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_CANCELLEDORVOIDED));
616                     } else if (foundCanceledPreApprove) {
617                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_VOIDED));
618                     } */
619                     if (foundCanceledPostApprove) {
620                         // messages.add("errors.duplicate.vendor.invoice.cancelled");
621                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_CANCELLED));
622                     }
623                 }
624             }
625 
626             // Check that the invoice date and invoice total amount entered are not on any existing non-cancelled PRQSs for this PO
627             prqss = getInvoicesByPOIdInvoiceAmountInvoiceDate(purchaseOrderId, document.getVendorInvoiceAmount(), document.getInvoiceDate());
628             if (prqss.size() > 0) {
629                 boolean addedMessage = false;
630                 boolean foundCanceledPostApprove = false; // cancelled
631              //   boolean foundCanceledPreApprove = false; // voided
632                 msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT));
633                 for (InvoiceDocument testPRQS : prqss) {
634                     if (StringUtils.equalsIgnoreCase(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_POST_AP_APPROVE)) {
635                         foundCanceledPostApprove |= true;
636                     } /*else if (StringUtils.equalsIgnoreCase(testPRQS.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_CANCELLED_IN_PROCESS)) {
637                         foundCanceledPreApprove |= true;
638                     }*/ else {
639                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT));
640                         addedMessage = true;
641                         break;
642                     }
643                 }
644 
645                 // Custom error message for duplicates related to cancelled/voided PRQSs
646                 if (!addedMessage) {
647                    /* if (foundCanceledPostApprove && foundCanceledPreApprove) {
648                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLEDORVOIDED));
649                     } else if (foundCanceledPreApprove) {
650                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_VOIDED));
651                         addedMessage = true;
652                     }*/
653                     if (foundCanceledPostApprove) {
654                         msgs.put(PRQSDocumentsStrings.DUPLICATE_INVOICE_QUESTION, configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_INVOICE_DATE_AMOUNT_CANCELLED));
655                         addedMessage = true;
656                     }
657 
658                 }
659             }
660         }
661         return msgs;
662     }
663 
664     /**
665      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoiceByDocumentNumber(String)
666      */
667     @Override
668     public InvoiceDocument getInvoiceByDocumentNumber(String documentNumber) {
669         LOG.debug("getInvoiceByDocumentNumber() started");
670 
671         if (ObjectUtils.isNotNull(documentNumber)) {
672             try {
673                 InvoiceDocument doc = (InvoiceDocument) documentService.getByDocumentHeaderId(documentNumber);
674                 return doc;
675             } catch (WorkflowException e) {
676                 String errorMessage = "Error getting payment request document from document service";
677                 LOG.error("getInvoiceByDocumentNumber() " + errorMessage, e);
678                 throw new RuntimeException(errorMessage, e);
679             }
680         }
681         return null;
682     }
683 
684     /**
685      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoiceById(Integer)
686      */
687     @Override
688     public InvoiceDocument getInvoiceById(Integer poDocId) {
689         return getInvoiceByDocumentNumber(invoiceDao.getDocumentNumberByInvoiceId(poDocId));
690     }
691 
692     /**
693      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesByPurchaseOrderId(Integer)
694      */
695     @Override
696     public List<InvoiceDocument> getInvoicesByPurchaseOrderId(Integer poDocId) {
697         List<InvoiceDocument> prqss = new ArrayList<InvoiceDocument>();
698         List<String> docNumbers = invoiceDao.getDocumentNumbersByPurchaseOrderId(poDocId);
699         for (String docNumber : docNumbers) {
700             InvoiceDocument prqs = getInvoiceByDocumentNumber(docNumber);
701             if (ObjectUtils.isNotNull(prqs)) {
702                 prqss.add(prqs);
703             }
704         }
705         return prqss;
706     }
707 
708     /**
709      * @see org.kuali.ole.module.purap.document.service.InvoiceService#getInvoicesByPOIdInvoiceAmountInvoiceDate(Integer,
710      *      org.kuali.rice.core.api.util.type.KualiDecimal, java.sql.Date)
711      */
712     @Override
713     public List<InvoiceDocument> getInvoicesByPOIdInvoiceAmountInvoiceDate(Integer poId, KualiDecimal invoiceAmount, Date invoiceDate) {
714         LOG.debug("getInvoicesByPOIdInvoiceAmountInvoiceDate() started");
715         return invoiceDao.getActiveInvoicesByPOIdInvoiceAmountInvoiceDate(poId, invoiceAmount, invoiceDate);
716     }
717 
718     /**
719      * @see org.kuali.ole.module.purap.document.service.InvoiceService#isInvoiceDateAfterToday(java.sql.Date)
720      */
721     @Override
722     public boolean isInvoiceDateAfterToday(Date invoiceDate) {
723         // Check invoice date to make sure it is today or before
724         Calendar now = Calendar.getInstance();
725         now.set(Calendar.HOUR, 11);
726         now.set(Calendar.MINUTE, 59);
727         now.set(Calendar.SECOND, 59);
728         now.set(Calendar.MILLISECOND, 59);
729         Timestamp nowTime = new Timestamp(now.getTimeInMillis());
730         Calendar invoiceDateC = Calendar.getInstance();
731         invoiceDateC.setTime(invoiceDate);
732         // set time to midnight
733         invoiceDateC.set(Calendar.HOUR, 0);
734         invoiceDateC.set(Calendar.MINUTE, 0);
735         invoiceDateC.set(Calendar.SECOND, 0);
736         invoiceDateC.set(Calendar.MILLISECOND, 0);
737         Timestamp invoiceDateTime = new Timestamp(invoiceDateC.getTimeInMillis());
738         return ((invoiceDateTime.compareTo(nowTime)) > 0);
739     }
740 
741     /**
742      * @see org.kuali.ole.module.purap.document.service.InvoiceService#calculatePayDate(java.sql.Date,
743      *      org.kuali.ole.vnd.businessobject.PaymentTermType)
744      */
745     @Override
746     public Date calculatePayDate(Date invoiceDate, PaymentTermType terms) {
747         LOG.debug("calculatePayDate() started");
748         // calculate the invoice + processed calendar
749         // invoice date mandatory validations will be done at submit , in future calculate will come under submit
750         if (invoiceDate == null) {
751             return null;
752         }
753         Calendar invoicedDateCalendar = dateTimeService.getCalendar(invoiceDate);
754         Calendar processedDateCalendar = dateTimeService.getCurrentCalendar();
755 
756         // add default number of days to processed
757 
758         String defaultDays = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD,PurapParameterConstants.PURAP_PRQS_PAY_DATE_DEFAULT_NUMBER_OF_DAYS);
759         processedDateCalendar.add(Calendar.DAY_OF_MONTH, Integer.parseInt(defaultDays));
760 
761         if (ObjectUtils.isNull(terms) || StringUtils.isEmpty(terms.getVendorPaymentTermsCode())) {
762             invoicedDateCalendar.add(Calendar.DAY_OF_MONTH, PurapConstants.PRQS_PAY_DATE_EMPTY_TERMS_DEFAULT_DAYS);
763             return returnLaterDate(invoicedDateCalendar, processedDateCalendar);
764         }
765 
766         Integer discountDueNumber = terms.getVendorDiscountDueNumber();
767         Integer netDueNumber = terms.getVendorNetDueNumber();
768         if (ObjectUtils.isNotNull(discountDueNumber)) {
769             String discountDueTypeDescription = terms.getVendorDiscountDueTypeDescription();
770             paymentTermsDateCalculation(discountDueTypeDescription, invoicedDateCalendar, discountDueNumber);
771         } else if (ObjectUtils.isNotNull(netDueNumber)) {
772             String netDueTypeDescription = terms.getVendorNetDueTypeDescription();
773             paymentTermsDateCalculation(netDueTypeDescription, invoicedDateCalendar, netDueNumber);
774         } else {
775             throw new RuntimeException("Neither discount or net number were specified for this payment terms type");
776         }
777 
778         // return the later date
779         return returnLaterDate(invoicedDateCalendar, processedDateCalendar);
780     }
781 
782     /**
783      * Returns whichever date is later, the invoicedDateCalendar or the processedDateCalendar.
784      *
785      * @param invoicedDateCalendar  One of the dates to be used in determining which date is later.
786      * @param processedDateCalendar The other date to be used in determining which date is later.
787      * @return The date which is the later of the two given dates in the input parameters.
788      */
789     protected Date returnLaterDate(Calendar invoicedDateCalendar, Calendar processedDateCalendar) {
790         if (invoicedDateCalendar.after(processedDateCalendar)) {
791             return new Date(invoicedDateCalendar.getTimeInMillis());
792         } else {
793             return new Date(processedDateCalendar.getTimeInMillis());
794         }
795     }
796 
797     /**
798      * Calculates the paymentTermsDate given the dueTypeDescription, invoicedDateCalendar and the dueNumber.
799      *
800      * @param dueTypeDescription   The due type description of the payment term.
801      * @param invoicedDateCalendar The Calendar object of the invoice date.
802      * @param dueNumber            Either the vendorDiscountDueNumber or the vendorDiscountDueNumber of the payment term.
803      */
804     protected void paymentTermsDateCalculation(String dueTypeDescription, Calendar invoicedDateCalendar, Integer dueNumber) {
805 
806         if (StringUtils.equals(dueTypeDescription, PurapConstants.PRQS_PAY_DATE_DATE)) {
807             // date specified set to date in next month
808             invoicedDateCalendar.add(Calendar.MONTH, 1);
809             invoicedDateCalendar.set(Calendar.DAY_OF_MONTH, dueNumber.intValue());
810         } else if (StringUtils.equals(PurapConstants.PRQS_PAY_DATE_DAYS, dueTypeDescription)) {
811             // days specified go forward that number
812             invoicedDateCalendar.add(Calendar.DAY_OF_MONTH, dueNumber.intValue());
813         } else {
814             // improper string
815             throw new RuntimeException("missing payment terms description or not properly enterred on payment term maintenance doc");
816         }
817     }
818 
819     /**
820      * @see org.kuali.ole.module.purap.document.service.InvoiceService#calculateInvoice(org.kuali.ole.module.purap.document.InvoiceDocument,
821      *      boolean)
822      */
823     @Override
824     public void calculateInvoice(InvoiceDocument invoice, boolean updateDiscount) {
825         LOG.debug("calculateInvoice() started");
826 
827         // general calculation, i.e. for the whole prqs document
828         if (ObjectUtils.isNull(invoice.getInvoicePayDate())) {
829             invoice.setInvoicePayDate(calculatePayDate(invoice.getInvoiceDate(), invoice.getVendorPaymentTerms()));
830         }
831 
832         distributeAccounting(invoice);
833 
834         purapService.calculateTax(invoice);
835 
836         // do proration for full order and trade in
837         purapService.prorateForTradeInAndFullOrderDiscount(invoice);
838 
839         // do proration for payment terms discount
840         if (updateDiscount) {
841             calculateDiscount(invoice);
842         }
843 
844         distributeAccounting(invoice);
845     }
846 
847 
848     /**
849      * Calculates the discount item for this invoice.
850      *
851      * @param invoiceDocument The payment request document whose discount to be calculated.
852      */
853     protected void calculateDiscount(InvoiceDocument invoiceDocument) {
854         InvoiceItem discountItem = findDiscountItem(invoiceDocument);
855         // find out if we really need the discount item
856         PaymentTermType pt = invoiceDocument.getVendorPaymentTerms();
857         if ((pt != null) && (pt.getVendorPaymentTermsPercent() != null) && (BigDecimal.ZERO.compareTo(pt.getVendorPaymentTermsPercent()) != 0)) {
858             if (discountItem == null) {
859                 // set discountItem and add to items
860                 // this is probably not the best way of doing it but should work for now if we start excluding discount from below
861                 // we will need to manually add
862                 purapService.addBelowLineItems(invoiceDocument);
863 
864                 // fix up below the line items
865                 removeIneligibleAdditionalCharges(invoiceDocument);
866 
867                 discountItem = findDiscountItem(invoiceDocument);
868             }
869 
870             // Deleted the discountItem.getExtendedPrice() null and isZero
871             InvoiceItem fullOrderItem = findFullOrderDiscountItem(invoiceDocument);
872             KualiDecimal fullOrderAmount = KualiDecimal.ZERO;
873             KualiDecimal fullOrderTaxAmount = KualiDecimal.ZERO;
874 
875             if (fullOrderItem != null) {
876                 fullOrderAmount = (ObjectUtils.isNotNull(fullOrderItem.getExtendedPrice())) ? fullOrderItem.getExtendedPrice() : KualiDecimal.ZERO;
877                 fullOrderTaxAmount = (ObjectUtils.isNotNull(fullOrderItem.getItemTaxAmount())) ? fullOrderItem.getItemTaxAmount() : KualiDecimal.ZERO;
878             }
879             KualiDecimal totalCost = invoiceDocument.getTotalPreTaxDollarAmountAboveLineItems().add(fullOrderAmount);
880             PurApItem tradeInItem = invoiceDocument.getTradeInItem();
881             if (ObjectUtils.isNotNull(tradeInItem)) {
882                 totalCost = totalCost.subtract(tradeInItem.getTotalAmount());
883             }
884             BigDecimal discountAmount = pt.getVendorPaymentTermsPercent().multiply(totalCost.bigDecimalValue()).multiply(new BigDecimal(PurapConstants.PRQS_DISCOUNT_MULT));
885 
886             // do we really need to set both, not positive, but probably won't hurt
887             discountItem.setItemUnitPrice(discountAmount.setScale(4, KualiDecimal.ROUND_BEHAVIOR));
888             discountItem.setExtendedPrice(new KualiDecimal(discountAmount));
889 
890             // set tax amount
891             boolean salesTaxInd = parameterService.getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
892             boolean useTaxIndicator = invoiceDocument.isUseTaxIndicator();
893 
894             if (salesTaxInd == true && useTaxIndicator == false) {
895                 KualiDecimal totalTax = invoiceDocument.getTotalTaxAmountAboveLineItems().add(fullOrderTaxAmount);
896                 BigDecimal discountTaxAmount = null;
897                 if (totalCost.isNonZero()) {
898                     discountTaxAmount = discountAmount.divide(totalCost.bigDecimalValue()).multiply(totalTax.bigDecimalValue());
899                 } else {
900                     discountTaxAmount = BigDecimal.ZERO;
901                 }
902 
903                 discountItem.setItemTaxAmount(new KualiDecimal(discountTaxAmount.setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
904             }
905 
906             // set document
907             discountItem.setPurapDocument(invoiceDocument);
908         } else { // no discount
909             if (discountItem != null) {
910                 invoiceDocument.getItems().remove(discountItem);
911             }
912         }
913 
914     }
915 
916 
917     @Override
918     public void clearTax(InvoiceDocument document) {
919         // remove all existing tax items added by previous calculation
920         removeTaxItems(document);
921         // reset values
922         document.setTaxClassificationCode(null);
923         document.setTaxFederalPercent(null);
924         document.setTaxStatePercent(null);
925         document.setTaxCountryCode(null);
926         document.setTaxNQIId(null);
927 
928         document.setTaxForeignSourceIndicator(false);
929         document.setTaxExemptTreatyIndicator(false);
930         document.setTaxOtherExemptIndicator(false);
931         document.setTaxGrossUpIndicator(false);
932         document.setTaxUSAIDPerDiemIndicator(false);
933         document.setTaxSpecialW4Amount(null);
934 
935     }
936 
937     /**
938      * @see org.kuali.ole.module.purap.document.service.InvoiceService#calculateTaxArea(org.kuali.ole.module.purap.document.InvoiceDocument)
939      */
940     @Override
941     public void calculateTaxArea(InvoiceDocument prqs) {
942         LOG.debug("calculateTaxArea() started");
943 
944         // remove all existing tax items added by previous calculation
945         removeTaxItems(prqs);
946 
947         // don't need to calculate tax items if TaxClassificationCode is N (Non_Reportable)
948         if (StringUtils.equalsIgnoreCase(prqs.getTaxClassificationCode(), "N")) {
949             return;
950         }
951 
952         // reserve the grand total excluding any tax amount, to be used as the base to compute all tax items
953         // if we don't reserve this, the pre-tax total could be changed as new tax items are added
954         BigDecimal taxableAmount = prqs.getGrandPreTaxTotal().bigDecimalValue();
955 
956         // generate and add state tax gross up item and its accounting line, update total amount,
957         // if gross up indicator is true and tax rate is non-zero
958         if (prqs.getTaxGrossUpIndicator() && prqs.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
959             PurApItem stateGrossItem = addTaxItem(prqs, ItemTypeCodes.ITEM_TYPE_STATE_GROSS_CODE, taxableAmount);
960         }
961 
962         // generate and add state tax item and its accounting line, update total amount, if tax rate is non-zero
963         if (prqs.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
964             PurApItem stateTaxItem = addTaxItem(prqs, ItemTypeCodes.ITEM_TYPE_STATE_TAX_CODE, taxableAmount);
965         }
966 
967         // generate and add federal tax gross up item and its accounting line, update total amount,
968         // if gross up indicator is true and tax rate is non-zero
969         if (prqs.getTaxGrossUpIndicator() && prqs.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
970             PurApItem federalGrossItem = addTaxItem(prqs, ItemTypeCodes.ITEM_TYPE_FEDERAL_GROSS_CODE, taxableAmount);
971         }
972 
973         // generate and add federal tax item and its accounting line, update total amount, if tax rate is non-zero
974         if (prqs.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
975             PurApItem federalTaxItem = addTaxItem(prqs, ItemTypeCodes.ITEM_TYPE_FEDERAL_TAX_CODE, taxableAmount);
976         }
977 
978         // FIXME if user request to add zero tax lines and remove them after tax approval,
979         // then remove the conditions above when adding the tax lines, and
980         // add a branch in InvoiceDocument.processNodeChange to call PurapService.deleteUnenteredItems
981     }
982 
983     /**
984      * Removes all existing NRA tax items from the specified payment request.
985      *
986      * @param prqs The payment request from which all tax items are to be removed.
987      */
988     protected void removeTaxItems(InvoiceDocument prqs) {
989         List<PurApItem> items = prqs.getItems();
990         for (int i = 0; i < items.size(); i++) {
991             PurApItem item = items.get(i);
992             String code = item.getItemTypeCode();
993             if (ItemTypeCodes.ITEM_TYPE_FEDERAL_TAX_CODE.equals(code) || ItemTypeCodes.ITEM_TYPE_STATE_TAX_CODE.equals(code) || ItemTypeCodes.ITEM_TYPE_FEDERAL_GROSS_CODE.equals(code) || ItemTypeCodes.ITEM_TYPE_STATE_GROSS_CODE.equals(code)) {
994                 items.remove(i--);
995             }
996         }
997     }
998 
999     /**
1000      * Generates a NRA tax item and adds to the specified payment request, according to the specified item type code.
1001      *
1002      * @param prqs          The payment request the tax item will be added to.
1003      * @param itemTypeCode  The item type code for the tax item.
1004      * @param taxableAmount The amount to which tax is computed against.
1005      * @return A fully populated PurApItem instance representing NRA tax amount data for the specified payment request.
1006      */
1007     protected PurApItem addTaxItem(InvoiceDocument prqs, String itemTypeCode, BigDecimal taxableAmount) {
1008         PurApItem taxItem = null;
1009 
1010         try {
1011             taxItem = (PurApItem) prqs.getItemClass().newInstance();
1012         } catch (IllegalAccessException e) {
1013             throw new IllegalArgumentException("Unable to access itemClass", e);
1014         } catch (InstantiationException e) {
1015             throw new IllegalArgumentException("Unable to instantiate itemClass", e);
1016         }
1017 
1018         // add item to prqs before adding the accounting line
1019         taxItem.setItemTypeCode(itemTypeCode);
1020         prqs.addItem(taxItem);
1021 
1022         // generate and add tax accounting line
1023         PurApAccountingLine taxLine = addTaxAccountingLine(taxItem, taxableAmount);
1024 
1025         // set extended price amount as now it's calculated when accounting line is generated
1026         taxItem.setItemUnitPrice(taxLine.getAmount().bigDecimalValue());
1027         taxItem.setExtendedPrice(taxLine.getAmount());
1028 
1029         // use item type description as the item description
1030         ItemType itemType = new ItemType();
1031         itemType.setItemTypeCode(itemTypeCode);
1032         itemType = (ItemType) businessObjectService.retrieve(itemType);
1033         taxItem.setItemType(itemType);
1034         taxItem.setItemDescription(itemType.getItemTypeDescription());
1035 
1036         return taxItem;
1037     }
1038 
1039     /**
1040      * Generates a PurAP accounting line and adds to the specified tax item.
1041      *
1042      * @param taxItem       The specified tax item the accounting line will be associated with.
1043      * @param taxableAmount The amount to which tax is computed against.
1044      * @return A fully populated PurApAccountingLine instance for the specified tax item.
1045      */
1046     protected PurApAccountingLine addTaxAccountingLine(PurApItem taxItem, BigDecimal taxableAmount) {
1047         InvoiceDocument prqs = taxItem.getPurapDocument();
1048         PurApAccountingLine taxLine = null;
1049 
1050         try {
1051             taxLine = (PurApAccountingLine) taxItem.getAccountingLineClass().newInstance();
1052         } catch (IllegalAccessException e) {
1053             throw new IllegalArgumentException("Unable to access sourceAccountingLineClass", e);
1054         } catch (InstantiationException e) {
1055             throw new IllegalArgumentException("Unable to instantiate sourceAccountingLineClass", e);
1056         }
1057 
1058         // tax item type indicators
1059         boolean isFederalTax = ItemTypeCodes.ITEM_TYPE_FEDERAL_TAX_CODE.equals(taxItem.getItemTypeCode());
1060         boolean isFederalGross = ItemTypeCodes.ITEM_TYPE_FEDERAL_GROSS_CODE.equals(taxItem.getItemTypeCode());
1061         boolean isStateTax = ItemTypeCodes.ITEM_TYPE_STATE_TAX_CODE.equals(taxItem.getItemTypeCode());
1062         boolean isStateGross = ItemTypeCodes.ITEM_TYPE_STATE_GROSS_CODE.equals(taxItem.getItemTypeCode());
1063         boolean isFederal = isFederalTax || isFederalGross; // true for federal tax/gross; false for state tax/gross
1064         boolean isGross = isFederalGross || isStateGross; // true for federal/state gross, false for federal/state tax
1065 
1066         // obtain accounting line info according to tax item type code
1067         String taxChart = null;
1068         String taxAccount = null;
1069         String taxObjectCode = null;
1070 
1071         if (isGross) {
1072             // for gross up tax items, use prqs's first item's first accounting line, which shall exist at this point
1073             AccountingLine line1 = prqs.getFirstAccount();
1074             taxChart = line1.getChartOfAccountsCode();
1075             taxAccount = line1.getAccountNumber();
1076             taxObjectCode = line1.getFinancialObjectCode();
1077         } else if (isFederalTax) {
1078             // for federal tax item, get chart, account, object code info from parameters
1079             taxChart = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD,NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX);
1080             taxAccount = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD,NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX);
1081             taxObjectCode = parameterService.getSubParameterValueAsString(InvoiceDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, prqs.getTaxClassificationCode());
1082             if (StringUtils.isBlank(taxChart) || StringUtils.isBlank(taxAccount) || StringUtils.isBlank(taxObjectCode)) {
1083                 LOG.error("Unable to retrieve federal tax parameters.");
1084                 throw new RuntimeException("Unable to retrieve federal tax parameters.");
1085             }
1086         } else if (isStateTax) {
1087             // for state tax item, get chart, account, object code info from parameters
1088             taxChart = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD,NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX);
1089             taxAccount = getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD,NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX);
1090             taxObjectCode = parameterService.getSubParameterValueAsString(InvoiceDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, prqs.getTaxClassificationCode());
1091             if (StringUtils.isBlank(taxChart) || StringUtils.isBlank(taxAccount) || StringUtils.isBlank(taxObjectCode)) {
1092                 LOG.error("Unable to retrieve state tax parameters.");
1093                 throw new RuntimeException("Unable to retrieve state tax parameters.");
1094             }
1095         }
1096 
1097         // calculate tax amount according to gross up indicator and federal/state tax type
1098         /*
1099          * The formula of tax and gross up amount are as follows: if (not gross up) gross not existing taxFederal/State = - amount *
1100          * rateFederal/State otherwise gross up grossFederal/State = amount * rateFederal/State / (1 - rateFederal - rateState) tax
1101          * = - gross
1102          */
1103 
1104         // pick federal/state tax rate
1105         BigDecimal taxPercentFederal = prqs.getTaxFederalPercent();
1106         BigDecimal taxPercentState = prqs.getTaxStatePercent();
1107         BigDecimal taxPercent = isFederal ? taxPercentFederal : taxPercentState;
1108 
1109         // divider value according to gross up or not
1110         BigDecimal taxDivider = new BigDecimal(100);
1111         if (prqs.getTaxGrossUpIndicator()) {
1112             taxDivider = taxDivider.subtract(taxPercentFederal.add(taxPercentState));
1113         }
1114 
1115         // tax = amount * rate / divider
1116         BigDecimal taxAmount = taxableAmount.multiply(taxPercent);
1117         taxAmount = taxAmount.divide(taxDivider, 5, BigDecimal.ROUND_HALF_UP);
1118 
1119         // tax is always negative, since it reduces the total amount; while gross up is always the positive of tax
1120         if (!isGross) {
1121             taxAmount = taxAmount.negate();
1122         }
1123 
1124         // populate necessary accounting line fields
1125         taxLine.setDocumentNumber(prqs.getDocumentNumber());
1126         taxLine.setSequenceNumber(prqs.getNextSourceLineNumber());
1127         taxLine.setChartOfAccountsCode(taxChart);
1128         taxLine.setAccountNumber(taxAccount);
1129         taxLine.setFinancialObjectCode(taxObjectCode);
1130         taxLine.setAmount(new KualiDecimal(taxAmount));
1131 
1132         // add the accounting line to the item
1133         taxLine.setItemIdentifier(taxItem.getItemIdentifier());
1134         taxLine.setPurapItem(taxItem);
1135         taxItem.getSourceAccountingLines().add(taxLine);
1136 
1137         return taxLine;
1138     }
1139 
1140     /**
1141      * Finds the discount item of the payment request document.
1142      *
1143      * @param invoiceDocument The payment request document to be used to find the discount item.
1144      * @return The discount item if it exists.
1145      */
1146     protected InvoiceItem findDiscountItem(InvoiceDocument invoiceDocument) {
1147         InvoiceItem discountItem = null;
1148         for (InvoiceItem prqsItem : (List<InvoiceItem>) invoiceDocument.getItems()) {
1149             if (StringUtils.equals(prqsItem.getItemTypeCode(), ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)) {
1150                 discountItem = prqsItem;
1151                 break;
1152             }
1153         }
1154         return discountItem;
1155     }
1156 
1157     /**
1158      * Finds the full order discount item of the payment request document.
1159      *
1160      * @param invoiceDocument The payment request document to be used to find the full order discount item.
1161      * @return The discount item if it exists.
1162      */
1163     protected InvoiceItem findFullOrderDiscountItem(InvoiceDocument invoiceDocument) {
1164         InvoiceItem discountItem = null;
1165         for (InvoiceItem prqsItem : (List<InvoiceItem>) invoiceDocument.getItems()) {
1166             if (StringUtils.equals(prqsItem.getItemTypeCode(), ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) {
1167                 discountItem = prqsItem;
1168                 break;
1169             }
1170         }
1171         return discountItem;
1172     }
1173 
1174     /**
1175      * Distributes accounts for a payment request document.
1176      *
1177      * @param invoiceDocument
1178      */
1179     protected void distributeAccounting(InvoiceDocument invoiceDocument) {
1180         // update the account amounts before doing any distribution
1181         purapAccountingService.updateAccountAmounts(invoiceDocument);
1182 
1183         String accountDistributionMethod = invoiceDocument.getAccountDistributionMethod();
1184 
1185         for (InvoiceItem item : (List<InvoiceItem>) invoiceDocument.getItems()) {
1186             KualiDecimal totalAmount = KualiDecimal.ZERO;
1187             List<PurApAccountingLine> distributedAccounts = null;
1188             List<SourceAccountingLine> summaryAccounts = null;
1189             Set excludedItemTypeCodes = new HashSet();
1190             excludedItemTypeCodes.add(ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE);
1191 
1192             // skip above the line
1193             if (item.getItemType().isLineItemIndicator()) {
1194                 continue;
1195             }
1196 
1197             if ((item.getSourceAccountingLines().isEmpty()) && (ObjectUtils.isNotNull(item.getExtendedPrice())) && (KualiDecimal.ZERO.compareTo(item.getExtendedPrice()) != 0)) {
1198                 if ((StringUtils.equals(ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE, item.getItemType().getItemTypeCode())) && (invoiceDocument.getGrandTotal() != null) && ((KualiDecimal.ZERO.compareTo(invoiceDocument.getGrandTotal()) != 0))) {
1199 
1200                     // No discount is applied to other item types other than item line
1201                     // See KFSMI-5210 for details
1202 
1203                     // total amount should be the line item total, not the grand total
1204                     totalAmount = invoiceDocument.getLineItemTotal();
1205 
1206                     // prorate item line accounts only
1207                     Set includedItemTypeCodes = new HashSet();
1208                     includedItemTypeCodes.add(ItemTypeCodes.ITEM_TYPE_ITEM_CODE);
1209                     includedItemTypeCodes.add(ItemTypeCodes.ITEM_TYPE_SERVICE_CODE);
1210 
1211                     summaryAccounts = purapAccountingService.generateSummaryIncludeItemTypesAndNoZeroTotals(invoiceDocument.getItems(), includedItemTypeCodes);
1212                     //if summaryAccount is empty then do not call generateAccountDistributionForProration as
1213                     //there is a check in that method to throw NPE if accounts percents == 0..
1214                     //OLEMI-8487
1215                     if (summaryAccounts != null) {
1216                         distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, PurapConstants.PRORATION_SCALE, InvoiceAccount.class);
1217                     }
1218                     // OLE-3405 : disabling the distribution method choice
1219 //                    if (PurapConstants.AccountDistributionMethodCodes.SEQUENTIAL_CODE.equalsIgnoreCase(accountDistributionMethod)) {
1220 //                        purapAccountingService.updatePreqAccountAmountsWithTotal(distributedAccounts, item.getTotalAmount());
1221 //
1222 //                    } else {
1223 //
1224 //                        boolean rulePassed = true;
1225 //                        // check any business rules
1226 //                        rulePassed &= kualiRuleService.applyRules(new PurchasingAccountsPayableItemPreCalculateEvent(invoiceDocument, item));
1227 //
1228 //                        if (rulePassed) {
1229 //                            purapAccountingService.updatePreqProporationalAccountAmountsWithTotal(distributedAccounts, item.getTotalAmount());
1230 //                        }
1231 //                    }
1232                 } else {
1233                     PurchaseOrderItem poi = item.getPurchaseOrderItem();
1234                     if ((poi != null) && (poi.getSourceAccountingLines() != null) && (!(poi.getSourceAccountingLines().isEmpty())) && (poi.getExtendedPrice() != null) && ((KualiDecimal.ZERO.compareTo(poi.getExtendedPrice())) != 0)) {
1235                         // use accounts from purchase order item matching this item
1236                         // account list of current item is already empty
1237                         item.generateAccountListFromPoItemAccounts(poi.getSourceAccountingLines());
1238                     } else {
1239                         totalAmount = invoiceDocument.getPurchaseOrderDocument().getTotalDollarAmountAboveLineItems();
1240                         purapAccountingService.updateAccountAmounts(invoiceDocument.getPurchaseOrderDocument());
1241                         summaryAccounts = purapAccountingService.generateSummary(PurApItemUtils.getAboveTheLineOnly(invoiceDocument.getPurchaseOrderDocument().getItems()));
1242                         distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, new Integer("6"), InvoiceAccount.class);
1243                     }
1244                 }
1245                 if (CollectionUtils.isNotEmpty(distributedAccounts) && CollectionUtils.isEmpty(item.getSourceAccountingLines())) {
1246                     item.setSourceAccountingLines(distributedAccounts);
1247                 }
1248             }
1249             // update the item
1250             purapAccountingService.updateItemAccountAmounts(item);
1251         }
1252 
1253         // update again now that distribute is finished. (Note: we may not need this anymore now that I added updateItem line above
1254         //leave the call below since we need to this when sequential method is used on the document.
1255         purapAccountingService.updateAccountAmounts(invoiceDocument);
1256     }
1257 
1258     /**
1259      * @see org.kuali.ole.module.purap.document.service.InvoiceService#addHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument,
1260      *      String)
1261      */
1262     @Override
1263     public InvoiceDocument addHoldOnInvoice(InvoiceDocument document, String note) throws Exception {
1264         // save the note
1265         Note noteObj = documentService.createNoteFromDocument(document, note);
1266         document.addNote(noteObj);
1267         noteService.save(noteObj);
1268 
1269         // MSU Contribution OLEMI-8456 DT 3822 OLECNTRB-959
1270 
1271         document.setHoldIndicator(true);
1272         document.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
1273         purapService.saveDocumentNoValidation(document);
1274 
1275         return document;
1276     }
1277 
1278     /**
1279      * @see org.kuali.ole.module.purap.document.service.InvoiceService#removeHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, String)
1280      */
1281     @Override
1282     public InvoiceDocument removeHoldOnInvoice(InvoiceDocument document, String note) throws Exception {
1283         // save the note
1284         Note noteObj = documentService.createNoteFromDocument(document, note);
1285         document.addNote(noteObj);
1286         noteService.save(noteObj);
1287 
1288         // MSU Contribution OLEMI-8456 DT 3822 OLECNTRB-959
1289 
1290         document.setHoldIndicator(false);
1291         document.setLastActionPerformedByPersonId(null);
1292         purapService.saveDocumentNoValidation(document);
1293 
1294         return document;
1295     }
1296 
1297     /**
1298      * @see org.kuali.ole.module.purap.document.service.InvoiceService#addHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument,
1299      *      String)
1300      */
1301     @Override
1302     public void requestCancelOnInvoice(InvoiceDocument document, String note) throws Exception {
1303         // save the note
1304         Note noteObj = documentService.createNoteFromDocument(document, note);
1305         document.addNote(noteObj);
1306         noteService.save(noteObj);
1307 
1308         // MSU Contribution OLEMI-8456 DT 3822 OLECNTRB-959
1309         document.setInvoiceCancelIndicator(true);
1310         document.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
1311         document.setAccountsPayableRequestCancelIdentifier(GlobalVariables.getUserSession().getPerson().getPrincipalId());
1312         purapService.saveDocumentNoValidation(document);
1313     }
1314 
1315     /**
1316      * @see org.kuali.ole.module.purap.document.service.InvoiceService#removeHoldOnInvoice(org.kuali.ole.module.purap.document.InvoiceDocument, String)
1317      */
1318     @Override
1319     public void removeRequestCancelOnInvoice(InvoiceDocument document, String note) throws Exception {
1320         // save the note
1321         Note noteObj = documentService.createNoteFromDocument(document, note);
1322         document.addNote(noteObj);
1323         noteService.save(noteObj);
1324 
1325         clearRequestCancelFields(document);
1326 
1327         purapService.saveDocumentNoValidation(document);
1328 
1329     }
1330 
1331     /**
1332      * Clears the request cancel fields.
1333      *
1334      * @param document The payment request document whose request cancel fields to be cleared.
1335      */
1336     protected void clearRequestCancelFields(InvoiceDocument document) {
1337         document.setInvoiceCancelIndicator(false);
1338         document.setLastActionPerformedByPersonId(null);
1339         document.setAccountsPayableRequestCancelIdentifier(null);
1340     }
1341 
1342     /**
1343      * @see org.kuali.ole.module.purap.document.service.InvoiceService#isExtracted(org.kuali.ole.module.purap.document.InvoiceDocument)
1344      */
1345     @Override
1346     public boolean isExtracted(InvoiceDocument document) {
1347         return (ObjectUtils.isNull(document.getExtractedTimestamp()) ? false : true);
1348     }
1349 
1350     protected boolean isBeingAdHocRouted(InvoiceDocument document) {
1351         return financialSystemWorkflowHelperService.isAdhocApprovalRequestedForPrincipal(document.getDocumentHeader().getWorkflowDocument(), GlobalVariables.getUserSession().getPrincipalId());
1352     }
1353 
1354     /**
1355      * @see org.kuali.ole.module.purap.document.service.InvoiceService#cancelExtractedInvoice(org.kuali.ole.module.purap.document.InvoiceDocument,
1356      *      String)
1357      */
1358     @Override
1359     public void cancelExtractedInvoice(InvoiceDocument invoice, String note) {
1360         LOG.debug("cancelExtractedInvoice() started");
1361         if (InvoiceStatuses.CANCELLED_STATUSES.contains(invoice.getApplicationDocumentStatus())) {
1362             LOG.debug("cancelExtractedInvoice() ended");
1363             return;
1364         }
1365 
1366         try {
1367             Note cancelNote = documentService.createNoteFromDocument(invoice, note);
1368             invoice.addNote(cancelNote);
1369             noteService.save(cancelNote);
1370         } catch (Exception e) {
1371             throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE, e);
1372         }
1373 
1374         // cancel extracted should not reopen PO
1375         invoice.setReopenPurchaseOrderIndicator(false);
1376 
1377         getAccountsPayableService().cancelAccountsPayableDocument(invoice, ""); // Performs save, so
1378         // no explicit save
1379         // is necessary
1380         if (LOG.isDebugEnabled()) {
1381             LOG.debug("cancelExtractedInvoice() PRQS " + invoice.getPurapDocumentIdentifier() + " Cancelled Without Workflow");
1382             LOG.debug("cancelExtractedInvoice() ended");
1383         }
1384     }
1385 
1386     /**
1387      * @see org.kuali.ole.module.purap.document.service.InvoiceService#resetExtractedInvoice(org.kuali.ole.module.purap.document.InvoiceDocument,
1388      *      String)
1389      */
1390     @Override
1391     public void resetExtractedInvoice(InvoiceDocument invoice, String note) {
1392         LOG.debug("resetExtractedInvoice() started");
1393         if (InvoiceStatuses.CANCELLED_STATUSES.contains(invoice.getApplicationDocumentStatus())) {
1394             LOG.debug("resetExtractedInvoice() ended");
1395             return;
1396         }
1397         invoice.setExtractedTimestamp(null);
1398         invoice.setPaymentPaidTimestamp(null);
1399         String noteText = "This Invoice is being reset for extraction by PDP " + note;
1400         try {
1401             Note resetNote = documentService.createNoteFromDocument(invoice, noteText);
1402             invoice.addNote(resetNote);
1403             noteService.save(resetNote);
1404         } catch (Exception e) {
1405             throw new RuntimeException(PurapConstants.REQ_UNABLE_TO_CREATE_NOTE + " " + e);
1406         }
1407         purapService.saveDocumentNoValidation(invoice);
1408         if (LOG.isDebugEnabled()) {
1409             LOG.debug("resetExtractedInvoice() PRQS " + invoice.getPurapDocumentIdentifier() + " Reset from Extracted status");
1410         }
1411     }
1412 
1413     /**
1414      * @see org.kuali.ole.module.purap.document.service.InvoiceService#populateInvoice(org.kuali.ole.module.purap.document.InvoiceDocument)
1415      */
1416     /*@Override
1417     public void populateInvoice(InvoiceDocument invoiceDocument) {
1418 
1419         PurchaseOrderDocument purchaseOrderDocument = invoiceDocument.getPurchaseOrderDocument();
1420 
1421         // make a call to search for expired/closed accounts
1422         HashMap<String, ExpiredOrClosedAccountEntry> expiredOrClosedAccountList = getAccountsPayableService().getExpiredOrClosedAccountList(invoiceDocument);
1423 
1424         invoiceDocument.populateInvoiceFromPurchaseOrders(invoiceDocument, expiredOrClosedAccountList);
1425 
1426        // invoiceDocument.getDocumentHeader().setDocumentDescription(createPreqDocumentDescription(invoiceDocument.getPurchaseOrderIdentifier(), invoiceDocument.getVendorName()));
1427 
1428         // write a note for expired/closed accounts if any exist and add a message stating there were expired/closed accounts at the
1429         // top of the document
1430         getAccountsPayableService().generateExpiredOrClosedAccountNote(invoiceDocument, expiredOrClosedAccountList);
1431 
1432         // set indicator so a message is displayed for accounts that were replaced due to expired/closed status
1433         if (!expiredOrClosedAccountList.isEmpty()) {
1434             invoiceDocument.setContinuationAccountIndicator(true);
1435         }
1436 
1437         // add discount item
1438         calculateDiscount(invoiceDocument);
1439         // distribute accounts (i.e. proration)
1440         distributeAccounting(invoiceDocument);
1441 
1442         // set bank code to default bank code in the system parameter
1443         Bank defaultBank = bankService.getDefaultBankByDocType(invoiceDocument.getClass());
1444         if (defaultBank != null) {
1445             invoiceDocument.setBankCode(defaultBank.getBankCode());
1446             invoiceDocument.setBank(defaultBank);
1447         }
1448     }*/
1449 
1450     /**
1451      * @see org.kuali.ole.module.purap.document.service.InvoiceService#createPreqDocumentDescription(Integer,
1452      *      String)
1453      */
1454     @Override
1455     public String createPreqDocumentDescription(Integer purchaseOrderIdentifier, String vendorName) {
1456         StringBuffer descr = new StringBuffer("");
1457         descr.append("PO: ");
1458         descr.append(purchaseOrderIdentifier);
1459         descr.append(" Vendor: ");
1460         descr.append(StringUtils.trimToEmpty(vendorName));
1461 
1462         int noteTextMaxLength = dataDictionaryService.getAttributeMaxLength(DocumentHeader.class, KRADPropertyConstants.DOCUMENT_DESCRIPTION).intValue();
1463         if (noteTextMaxLength >= descr.length()) {
1464             return descr.toString();
1465         } else {
1466             return descr.toString().substring(0, noteTextMaxLength);
1467         }
1468     }
1469 
1470     /**
1471      * @see org.kuali.ole.module.purap.document.service.InvoiceService#populateAndSaveInvoice(org.kuali.ole.module.purap.document.InvoiceDocument)
1472      */
1473     @Override
1474     public void populateAndSaveInvoice(InvoiceDocument prqs) throws WorkflowException {
1475         try {
1476             prqs.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_IN_PROCESS);
1477             documentService.saveDocument(prqs, AttributedContinuePurapEvent.class);
1478         } catch (ValidationException ve) {
1479             prqs.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_INITIATE);
1480         } catch (WorkflowException we) {
1481             prqs.updateAndSaveAppDocStatus(InvoiceStatuses.APPDOC_INITIATE);
1482 
1483             String errorMsg = "Error saving document # " + prqs.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
1484             LOG.error(errorMsg, we);
1485             throw new RuntimeException(errorMsg, we);
1486         }
1487     }
1488 
1489     /**
1490      * If the full document entry has been completed and the status of the related purchase order document is closed, return true,
1491      * otherwise return false.
1492      *
1493      * @param apDoc The AccountsPayableDocument to be determined whether its purchase order should be reversed.
1494      * @return boolean true if the purchase order should be reversed.
1495      * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#shouldPurchaseOrderBeReversed
1496      *      (org.kuali.ole.module.purap.document.AccountsPayableDocument)
1497      */
1498     @Override
1499     public boolean shouldPurchaseOrderBeReversed(AccountsPayableDocument apDoc) {
1500         PurchaseOrderDocument po = apDoc.getPurchaseOrderDocument();
1501         if (ObjectUtils.isNull(po)) {
1502             throw new RuntimeException("po should never be null on PRQS");
1503         }
1504         // if past full entry and already closed return true
1505         if (purapService.isFullDocumentEntryCompleted(apDoc) && StringUtils.equalsIgnoreCase(PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED, po.getApplicationDocumentStatus())) {
1506             return true;
1507         }
1508         return false;
1509     }
1510 
1511     /**
1512      * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#getPersonForCancel(org.kuali.ole.module.purap.document.AccountsPayableDocument)
1513      */
1514     @Override
1515     public Person getPersonForCancel(AccountsPayableDocument apDoc) {
1516         InvoiceDocument prqsDoc = (InvoiceDocument) apDoc;
1517         Person user = null;
1518         if (prqsDoc.isInvoiceCancelIndicator()) {
1519             user = prqsDoc.getLastActionPerformedByUser();
1520         }
1521         return user;
1522     }
1523 
1524     /**
1525      * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#takePurchaseOrderCancelAction(org.kuali.ole.module.purap.document.AccountsPayableDocument)
1526      */
1527     @Override
1528     public void takePurchaseOrderCancelAction(AccountsPayableDocument apDoc) {
1529         InvoiceDocument prqsDocument = (InvoiceDocument) apDoc;
1530         if (prqsDocument.isReopenPurchaseOrderIndicator()) {
1531             String docType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT;
1532             purchaseOrderService.createAndRoutePotentialChangeDocument(prqsDocument.getPurchaseOrderDocument().getDocumentNumber(), docType, "reopened by Credit Memo " + apDoc.getPurapDocumentIdentifier() + "cancel", new ArrayList(), PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_REOPEN);
1533         }
1534     }
1535 
1536     /**
1537      * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#updateStatusByNode(String,
1538      *      org.kuali.ole.module.purap.document.AccountsPayableDocument)
1539      */
1540     @Override
1541     public String updateStatusByNode(String currentNodeName, AccountsPayableDocument apDoc) {
1542         return updateStatusByNode(currentNodeName, (InvoiceDocument) apDoc);
1543     }
1544 
1545     /**
1546      * Updates the status of the payment request document.
1547      *
1548      * @param currentNodeName The current node name.
1549      * @param prqsDoc         The payment request document whose status to be updated.
1550      * @return The canceled status code.
1551      */
1552     protected String updateStatusByNode(String currentNodeName, InvoiceDocument prqsDoc) {
1553         // remove request cancel if necessary
1554         clearRequestCancelFields(prqsDoc);
1555 
1556         // update the status on the document
1557 
1558         String cancelledStatus = "";
1559         if (StringUtils.isEmpty(currentNodeName)) {
1560             // if empty probably not coming from workflow
1561             cancelledStatus = InvoiceStatuses.APPDOC_CANCELLED_POST_AP_APPROVE;
1562         } else {
1563             cancelledStatus = InvoiceStatuses.getInvoiceAppDocDisapproveStatuses().get(currentNodeName);
1564         }
1565 
1566         if (StringUtils.isNotBlank(cancelledStatus)) {
1567             try {
1568                 prqsDoc.updateAndSaveAppDocStatus(cancelledStatus);
1569             } catch (WorkflowException we) {
1570                 throw new RuntimeException("Unable to save the route status data for document: " + prqsDoc.getDocumentNumber(), we);
1571             }
1572             purapService.saveDocumentNoValidation(prqsDoc);
1573         } else {
1574             logAndThrowRuntimeException("No status found to set for document being disapproved in node '" + currentNodeName + "'");
1575         }
1576         return cancelledStatus;
1577     }
1578 
1579     /**
1580      * @see org.kuali.ole.module.purap.document.service.InvoiceService#markPaid(org.kuali.ole.module.purap.document.InvoiceDocument,
1581      *      java.sql.Date)
1582      */
1583     @Override
1584     public void markPaid(InvoiceDocument pr, Date processDate) {
1585         LOG.debug("markPaid() started");
1586 
1587         pr.setPaymentPaidTimestamp(new Timestamp(processDate.getTime()));
1588         purapService.saveDocumentNoValidation(pr);
1589     }
1590 
1591     /**
1592      * @see org.kuali.ole.module.purap.document.service.InvoiceService#hasDiscountItem(org.kuali.ole.module.purap.document.InvoiceDocument)
1593      */
1594     @Override
1595     public boolean hasDiscountItem(InvoiceDocument prqs) {
1596         return ObjectUtils.isNotNull(findDiscountItem(prqs));
1597     }
1598 
1599     /**
1600      * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#poItemEligibleForAp(org.kuali.ole.module.purap.document.AccountsPayableDocument,
1601      *      org.kuali.ole.module.purap.businessobject.PurchaseOrderItem)
1602      */
1603     @Override
1604     public boolean poItemEligibleForAp(AccountsPayableDocument apDoc, PurchaseOrderItem poi) {
1605         if (ObjectUtils.isNull(poi)) {
1606             throw new RuntimeException("item null in purchaseOrderItemEligibleForPayment ... this should never happen");
1607         }
1608         // if the po item is not active... skip it
1609         if (!poi.isItemActiveIndicator()) {
1610             return false;
1611         }
1612 
1613         ItemType poiType = poi.getItemType();
1614         if (ObjectUtils.isNull(poiType)) {
1615             return false;
1616         }
1617 
1618         if (poiType.isQuantityBasedGeneralLedgerIndicator()) {
1619             if (poi.getItemQuantity().isGreaterThan(poi.getItemInvoicedTotalQuantity())) {
1620                 return true;
1621             }
1622             return false;
1623         } else { // not quantity based
1624             // As long as it contains a number (whether it's 0, negative or positive number), we'll
1625             // have to return true. This is so that the OutstandingEncumberedAmount and the
1626             // Original Amount from PO column would appear on the page for Trade In.
1627             if (poi.getItemOutstandingEncumberedAmount() != null) {
1628                 return true;
1629             }
1630             return false;
1631         }
1632     }
1633 
1634     @Override
1635     public void removeIneligibleAdditionalCharges(InvoiceDocument document) {
1636 
1637         List<InvoiceItem> itemsToRemove = new ArrayList<InvoiceItem>();
1638 
1639         for (InvoiceItem item : (List<InvoiceItem>) document.getItems()) {
1640 
1641             // if no extended price and its an order discount or trade in, remove
1642             if (ObjectUtils.isNull(item.getPurchaseOrderItemUnitPrice()) && (ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE.equals(item.getItemTypeCode()) || ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE.equals(item.getItemTypeCode()))) {
1643                 itemsToRemove.add(item);
1644                 continue;
1645             }
1646 
1647             // if a payment terms discount exists but not set on teh doc, remove
1648             if (StringUtils.equals(item.getItemTypeCode(), ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE)) {
1649                 PaymentTermType pt = document.getVendorPaymentTerms();
1650                 if ((pt != null) && (pt.getVendorPaymentTermsPercent() != null) && (BigDecimal.ZERO.compareTo(pt.getVendorPaymentTermsPercent()) != 0)) {
1651                     // discount ok
1652                 } else {
1653                     // remove discount
1654                     itemsToRemove.add(item);
1655                 }
1656                 continue;
1657             }
1658 
1659         }
1660 
1661         // remove items marked for removal
1662         for (InvoiceItem item : itemsToRemove) {
1663             document.getItems().remove(item);
1664         }
1665     }
1666 
1667     @Override
1668     public void changeVendor(InvoiceDocument prqs, Integer headerId, Integer detailId) {
1669 
1670         VendorDetail primaryVendor = vendorService.getVendorDetail(prqs.getOriginalVendorHeaderGeneratedIdentifier(), prqs.getOriginalVendorDetailAssignedIdentifier());
1671 
1672         if (primaryVendor == null) {
1673             LOG.error("useAlternateVendor() primaryVendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
1674             throw new PurError("AlternateVendor: VendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
1675         }
1676 
1677         // set vendor detail
1678         VendorDetail vd = vendorService.getVendorDetail(headerId, detailId);
1679         if (vd == null) {
1680             LOG.error("changeVendor() VendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
1681             throw new PurError("changeVendor: VendorDetail from database for header id " + headerId + " and detail id " + detailId + "is null");
1682         }
1683         prqs.setVendorDetail(vd);
1684         prqs.setVendorName(vd.getVendorName());
1685         prqs.setVendorNumber(vd.getVendorNumber());
1686         prqs.setVendorHeaderGeneratedIdentifier(vd.getVendorHeaderGeneratedIdentifier());
1687         prqs.setVendorDetailAssignedIdentifier(vd.getVendorDetailAssignedIdentifier());
1688         prqs.setVendorPaymentTermsCode(vd.getVendorPaymentTermsCode());
1689         prqs.setVendorShippingPaymentTermsCode(vd.getVendorShippingPaymentTermsCode());
1690         prqs.setVendorShippingTitleCode(vd.getVendorShippingTitleCode());
1691         prqs.refreshReferenceObject("vendorPaymentTerms");
1692         prqs.refreshReferenceObject("vendorShippingPaymentTerms");
1693 
1694         // Set vendor address
1695         String deliveryCampus = prqs.getPurchaseOrderDocument().getDeliveryCampusCode();
1696         VendorAddress va = vendorService.getVendorDefaultAddress(headerId, detailId, VendorConstants.AddressTypes.REMIT, deliveryCampus);
1697         if (va == null) {
1698             va = vendorService.getVendorDefaultAddress(headerId, detailId, VendorConstants.AddressTypes.PURCHASE_ORDER, deliveryCampus);
1699         }
1700         if (va == null) {
1701             LOG.error("changeVendor() VendorAddress from database for header id " + headerId + " and detail id " + detailId + "is null");
1702             throw new PurError("changeVendor  VendorAddress from database for header id " + headerId + " and detail id " + detailId + "is null");
1703         }
1704 
1705         if (prqs != null) {
1706             setVendorAddress(va, prqs);
1707         } else {
1708             LOG.error("changeVendor(): Null link back to the Purchase Order.");
1709             throw new PurError("Null link back to the Purchase Order.");
1710         }
1711 
1712         // change document description
1713         //    prqs.getDocumentHeader().setDocumentDescription(createPreqDocumentDescription(prqs.getPurchaseOrderIdentifier(), prqs.getVendorName()));
1714     }
1715 
1716     /**
1717      * Set the Vendor address of the given ID.
1718      *
1719      * @param va  ID of the address to set
1720      * @param inv Invoice to set in
1721      * @return New Invoice to use
1722      */
1723     protected void setVendorAddress(VendorAddress va, InvoiceDocument inv) {
1724 
1725         if (va != null) {
1726             inv.setVendorAddressGeneratedIdentifier(va.getVendorAddressGeneratedIdentifier());
1727             inv.setVendorAddressInternationalProvinceName(va.getVendorAddressInternationalProvinceName());
1728             inv.setVendorLine1Address(va.getVendorLine1Address());
1729             inv.setVendorLine2Address(va.getVendorLine2Address());
1730             inv.setVendorCityName(va.getVendorCityName());
1731             inv.setVendorStateCode(va.getVendorStateCode());
1732             inv.setVendorPostalCode(va.getVendorZipCode());
1733             inv.setVendorCountryCode(va.getVendorCountryCode());
1734         }
1735 
1736     }
1737 
1738     /**
1739      * Records the specified error message into the Log file and throws a runtime exception.
1740      *
1741      * @param errorMessage the error message to be logged.
1742      */
1743     protected void logAndThrowRuntimeException(String errorMessage) {
1744         this.logAndThrowRuntimeException(errorMessage, null);
1745     }
1746 
1747     /**
1748      * Records the specified error message into the Log file and throws the specified runtime exception.
1749      *
1750      * @param errorMessage the specified error message.
1751      * @param e            the specified runtime exception.
1752      */
1753     protected void logAndThrowRuntimeException(String errorMessage, Exception e) {
1754         if (ObjectUtils.isNotNull(e)) {
1755             LOG.error(errorMessage, e);
1756             throw new RuntimeException(errorMessage, e);
1757         } else {
1758             LOG.error(errorMessage);
1759             throw new RuntimeException(errorMessage);
1760         }
1761     }
1762 
1763     /**
1764      * The given document here actually needs to be a Invoice.
1765      *
1766      * @see org.kuali.ole.module.purap.document.service.AccountsPayableDocumentSpecificService#generateGLEntriesCreateAccountsPayableDocument(org.kuali.ole.module.purap.document.AccountsPayableDocument)
1767      */
1768     @Override
1769     public void generateGLEntriesCreateAccountsPayableDocument(AccountsPayableDocument apDocument) {
1770         //  if(apDocument.)
1771 
1772         InvoiceDocument invoice = (InvoiceDocument) apDocument;
1773         //System.out.println(" generateGLEntriesCreateAccountsPayableDocument apDocument" + ((InvoiceDocument) apDocument).getDocumentType());
1774         // JHK: this is not being injected because it would cause a circular reference in the Spring definitions
1775         SpringContext.getBean(PurapGeneralLedgerService.class).generateEntriesCreateInvoice(invoice);
1776     }
1777 
1778     /**
1779      * @see org.kuali.ole.module.purap.document.service.InvoiceService#hasActiveInvoicesForPurchaseOrder(Integer)
1780      */
1781     @Override
1782     public boolean hasActiveInvoicesForPurchaseOrder(Integer purchaseOrderIdentifier) {
1783 
1784         boolean hasActivePreqs = false;
1785         List<String> docNumbers = null;
1786         WorkflowDocument workflowDocument = null;
1787 
1788         docNumbers = invoiceDao.getActiveInvoiceDocumentNumbersForPurchaseOrder(purchaseOrderIdentifier);
1789         docNumbers = filterInvoiceByAppDocStatus(docNumbers, InvoiceStatuses.STATUSES_POTENTIALLY_ACTIVE);
1790 
1791         for (String docNumber : docNumbers) {
1792             try {
1793                 workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, GlobalVariables.getUserSession().getPerson());
1794             } catch (WorkflowException we) {
1795                 throw new RuntimeException(we);
1796             }
1797             // if the document is not in a non-active status then return true and stop evaluation
1798             if (!(workflowDocument.isCanceled() || workflowDocument.isException())) {
1799                 hasActivePreqs = true;
1800                 break;
1801             }
1802         }
1803         return hasActivePreqs;
1804     }
1805 
1806     /**
1807      * Since Invoice does not have the app doc status, perform an additional lookup
1808      * through doc search by using list of Invoice Doc numbers.  Query appDocStatus
1809      * from workflow document and filter against the provided status
1810      * <p/>
1811      * DocumentSearch allows for multiple docNumber lookup by docId|docId|docId conversion
1812      *
1813      * @param lookupDocNumbers
1814      * @param appDocStatus
1815      * @return
1816      */
1817     protected List<String> filterInvoiceByAppDocStatus(List<String> lookupDocNumbers, String... appDocStatus) {
1818         boolean valid = false;
1819 
1820         final String DOC_NUM_DELIM = "|";
1821         StrBuilder routerHeaderIdBuilder = new StrBuilder().appendWithSeparators(lookupDocNumbers, DOC_NUM_DELIM);
1822 
1823         List<String> invoiceDocNumbers = new ArrayList<String>();
1824 
1825         DocumentSearchCriteria.Builder documentSearchCriteriaDTO = DocumentSearchCriteria.Builder.create();
1826         documentSearchCriteriaDTO.setDocumentId(routerHeaderIdBuilder.toString());
1827         documentSearchCriteriaDTO.setDocumentTypeName(PurapConstants.PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT);
1828         documentSearchCriteriaDTO.setApplicationDocumentStatuses(Arrays.asList(appDocStatus));
1829 
1830         DocumentSearchResults reqDocumentsList = KewApiServiceLocator.getWorkflowDocumentService().documentSearch(
1831                 "", documentSearchCriteriaDTO.build());
1832 
1833         for (DocumentSearchResult reqDocument : reqDocumentsList.getSearchResults()) {
1834             ///use the appDocStatus from the KeyValueDTO result to look up custom status
1835             if (Arrays.asList(appDocStatus).contains(reqDocument.getDocument().getApplicationDocumentStatus())) {
1836                 //found the matching status, retrieve the routeHeaderId and add to the list
1837                 invoiceDocNumbers.add(reqDocument.getDocument().getDocumentId());
1838             }
1839 
1840         }
1841 
1842         return invoiceDocNumbers;
1843     }
1844 
1845     /**
1846      * Wrapper class to the filterInvoiceByAppDocStatus
1847      * <p/>
1848      * This class first extract the payment request document numbers from the Invoice Collections,
1849      * then perform the filterInvoiceByAppDocStatus function.  Base on the filtered payment request
1850      * doc number, reconstruct the filtered Invoice Collection
1851      *
1852      * @param invoiceDocuments
1853      * @param appDocStatus
1854      * @return
1855      */
1856     protected Collection<InvoiceDocument> filterInvoiceByAppDocStatus(Collection<InvoiceDocument> invoiceDocuments, String... appDocStatus) {
1857         List<String> invoiceDocNumbers = new ArrayList<String>();
1858         for (InvoiceDocument invoice : invoiceDocuments) {
1859             invoiceDocNumbers.add(invoice.getDocumentNumber());
1860         }
1861 
1862         List<String> filteredInvoiceDocNumbers = filterInvoiceByAppDocStatus(invoiceDocNumbers, appDocStatus);
1863 
1864         Collection<InvoiceDocument> filteredInvoiceDocuments = new ArrayList<InvoiceDocument>();
1865         //add to filtered collection if it is in the filtered payment request doc number list
1866         for (InvoiceDocument invoice : invoiceDocuments) {
1867             if (filteredInvoiceDocNumbers.contains(invoice.getDocumentNumber())) {
1868                 filteredInvoiceDocuments.add(invoice);
1869             }
1870         }
1871         return filteredInvoiceDocuments;
1872     }
1873 
1874 
1875     /**
1876      * Wrapper class to the filterInvoiceByAppDocStatus (Collection<InvoiceDocument>)
1877      * <p/>
1878      * This class first construct the Invoice Collection from the iterator, and then process through
1879      * filterInvoiceByAppDocStatus
1880      *
1881      * @param invoiceIterator
1882      * @param appDocStatus
1883      * @return
1884      */
1885     protected Iterator<InvoiceDocument> filterInvoiceByAppDocStatus(Iterator<InvoiceDocument> invoiceIterator, String... appDocStatus) {
1886         Collection<InvoiceDocument> invoiceDocuments = new ArrayList<InvoiceDocument>();
1887         for (; invoiceIterator.hasNext(); ) {
1888             invoiceDocuments.add(invoiceIterator.next());
1889         }
1890 
1891         return filterInvoiceByAppDocStatus(invoiceDocuments, appDocStatus).iterator();
1892     }
1893 
1894     /**
1895      * @see org.kuali.ole.module.purap.document.service.InvoiceService#processInvoiceInReceivingStatus()
1896      */
1897     @Override
1898     public void processInvoiceInReceivingStatus() {
1899         List<String> docNumbers = invoiceDao.getInvoiceInReceivingStatus();
1900         docNumbers = filterInvoiceByAppDocStatus(docNumbers, InvoiceStatuses.APPDOC_AWAITING_RECEIVING_REVIEW);
1901 
1902         List<InvoiceDocument> prqssAwaitingReceiving = new ArrayList<InvoiceDocument>();
1903         for (String docNumber : docNumbers) {
1904             InvoiceDocument prqs = getInvoiceByDocumentNumber(docNumber);
1905             if (ObjectUtils.isNotNull(prqs)) {
1906                 prqssAwaitingReceiving.add(prqs);
1907             }
1908         }
1909         if (ObjectUtils.isNotNull(prqssAwaitingReceiving)) {
1910             for (InvoiceDocument prqsDoc : prqssAwaitingReceiving) {
1911                 if (prqsDoc.isReceivingRequirementMet()) {
1912                     try {
1913                         documentService.approveDocument(prqsDoc, "Approved by Receiving Required PRQS job", null);
1914                     } catch (WorkflowException e) {
1915                         LOG.error("processInvoiceInReceivingStatus() Error approving payment request document from awaiting receiving", e);
1916                         throw new RuntimeException("Error approving payment request document from awaiting receiving", e);
1917                     }
1918                 }
1919             }
1920         }
1921     }
1922 
1923     /**
1924      * @see org.kuali.ole.module.purap.document.service.InvoiceService#allowBackpost(org.kuali.ole.module.purap.document.InvoiceDocument)
1925      */
1926     @Override
1927     public boolean allowBackpost(InvoiceDocument invoiceDocument) {
1928         int allowBackpost = (Integer.parseInt(getParameter(OLEConstants.OptionalModuleNamespaces.PURCHASING_ACCOUNTS_PAYABLE, OLEConstants.InvoiceDocument.CMPNT_CD,PurapRuleConstants.ALLOW_BACKPOST_DAYS)));
1929 
1930         Calendar today = dateTimeService.getCurrentCalendar();
1931         Integer currentFY = universityDateService.getCurrentUniversityDate().getUniversityFiscalYear();
1932         java.util.Date priorClosingDateTemp = universityDateService.getLastDateOfFiscalYear(currentFY - 1);
1933         Calendar priorClosingDate = Calendar.getInstance();
1934         priorClosingDate.setTime(priorClosingDateTemp);
1935 
1936         // adding 1 to set the date to midnight the day after backpost is allowed so that prqss allow backpost on the last day
1937         Calendar allowBackpostDate = Calendar.getInstance();
1938         allowBackpostDate.setTime(priorClosingDate.getTime());
1939         allowBackpostDate.add(Calendar.DATE, allowBackpost + 1);
1940 
1941         Calendar prqsInvoiceDate = Calendar.getInstance();
1942         prqsInvoiceDate.setTime(invoiceDocument.getInvoiceDate() != null ? invoiceDocument.getInvoiceDate() : new java.util.Date());
1943 
1944         // if today is after the closing date but before/equal to the allowed backpost date and the invoice date is for the
1945         // prior year, set the year to prior year
1946         if ((today.compareTo(priorClosingDate) > 0) && (today.compareTo(allowBackpostDate) <= 0) && (prqsInvoiceDate.compareTo(priorClosingDate) <= 0)) {
1947             LOG.debug("allowBackpost() within range to allow backpost; posting entry to period 12 of previous FY");
1948             return true;
1949         }
1950 
1951         LOG.debug("allowBackpost() not within range to allow backpost; posting entry to current FY");
1952         return false;
1953     }
1954 
1955     @Override
1956     public boolean isPurchaseOrderValidForInvoiceDocumentCreation(InvoiceDocument invoiceDocument, PurchaseOrderDocument po) {
1957         Integer POID = invoiceDocument.getPurchaseOrderIdentifier();
1958 
1959         boolean valid = true;
1960 
1961         PurchaseOrderDocument purchaseOrderDocument = invoiceDocument.getPurchaseOrderDocument();
1962         if (ObjectUtils.isNull(purchaseOrderDocument)) {
1963             GlobalVariables.getMessageMap().putError(PurapPropertyConstants.PURCHASE_ORDER_IDENTIFIER, PurapKeyConstants.ERROR_PURCHASE_ORDER_NOT_EXIST);
1964             valid &= false;
1965         } else if (purchaseOrderDocument.isPendingActionIndicator()) {
1966             GlobalVariables.getMessageMap().putError(PurapPropertyConstants.PURCHASE_ORDER_IDENTIFIER, PurapKeyConstants.ERROR_PURCHASE_PENDING_ACTION);
1967             valid &= false;
1968         } else if (!StringUtils.equals(purchaseOrderDocument.getApplicationDocumentStatus(), PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN)) {
1969             GlobalVariables.getMessageMap().putError(PurapPropertyConstants.PURCHASE_ORDER_IDENTIFIER, PurapKeyConstants.ERROR_PURCHASE_ORDER_NOT_OPEN);
1970             valid &= false;
1971             // if the PO is pending and it is not a Retransmit, we cannot generate a Invoice for it
1972         } else {
1973             // Verify that there exists at least 1 item left to be invoiced
1974             // valid &= encumberedItemExistsForInvoicing(purchaseOrderDocument);
1975         }
1976 
1977         return valid;
1978     }
1979 
1980     @Override
1981     public boolean encumberedItemExistsForInvoicing(PurchaseOrderDocument document) {
1982         boolean zeroDollar = true;
1983         GlobalVariables.getMessageMap().clearErrorPath();
1984         GlobalVariables.getMessageMap().addToErrorPath(OLEPropertyConstants.DOCUMENT);
1985         for (PurchaseOrderItem poi : (List<PurchaseOrderItem>) document.getItems()) {
1986             // Quantity-based items
1987             if (poi.getItemType().isLineItemIndicator() && poi.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
1988                 KualiDecimal encumberedQuantity = poi.getItemOutstandingEncumberedQuantity() == null ? KualiDecimal.ZERO : poi.getItemOutstandingEncumberedQuantity();
1989                 if (encumberedQuantity.compareTo(KualiDecimal.ZERO) == 1) {
1990                     zeroDollar = false;
1991                     break;
1992                 }
1993             }
1994             // Service Items or Below-the-line Items
1995             else if (poi.getItemType().isAmountBasedGeneralLedgerIndicator() || poi.getItemType().isAdditionalChargeIndicator()) {
1996                 KualiDecimal encumberedAmount = poi.getItemOutstandingEncumberedAmount() == null ? KualiDecimal.ZERO : poi.getItemOutstandingEncumberedAmount();
1997                 if (encumberedAmount.compareTo(KualiDecimal.ZERO) == 1) {
1998                     zeroDollar = false;
1999                     break;
2000                 }
2001             }
2002         }
2003 
2004         return !zeroDollar;
2005     }
2006 
2007     public void setDateTimeService(DateTimeService dateTimeService) {
2008         this.dateTimeService = dateTimeService;
2009     }
2010 
2011     public void setParameterService(ParameterService parameterService) {
2012         this.parameterService = parameterService;
2013     }
2014 
2015     public void setConfigurationService(ConfigurationService configurationService) {
2016         this.configurationService = configurationService;
2017     }
2018 
2019     public void setDocumentService(DocumentService documentService) {
2020         this.documentService = documentService;
2021     }
2022 
2023     public void setNoteService(NoteService noteService) {
2024         this.noteService = noteService;
2025     }
2026 
2027     public void setPurapService(PurapService purapService) {
2028         this.purapService = purapService;
2029     }
2030 
2031     public void setInvoiceDao(InvoiceDao invoiceDao) {
2032         this.invoiceDao = invoiceDao;
2033     }
2034 
2035     public void setNegativeInvoiceApprovalLimitService(NegativeInvoiceApprovalLimitService negativeInvoiceApprovalLimitService) {
2036         this.negativeInvoiceApprovalLimitService = negativeInvoiceApprovalLimitService;
2037     }
2038 
2039     public void setPurapAccountingService(PurapAccountingService purapAccountingService) {
2040         this.purapAccountingService = purapAccountingService;
2041     }
2042 
2043     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
2044         this.businessObjectService = businessObjectService;
2045     }
2046 
2047     public void setPurapWorkflowIntegrationService(PurApWorkflowIntegrationService purapWorkflowIntegrationService) {
2048         this.purapWorkflowIntegrationService = purapWorkflowIntegrationService;
2049     }
2050 
2051     public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
2052         this.workflowDocumentService = workflowDocumentService;
2053     }
2054 
2055     public void setAccountsPayableService(AccountsPayableService accountsPayableService) {
2056         this.accountsPayableService = accountsPayableService;
2057     }
2058 
2059     public void setVendorService(VendorService vendorService) {
2060         this.vendorService = vendorService;
2061     }
2062 
2063     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
2064         this.dataDictionaryService = dataDictionaryService;
2065     }
2066 
2067     public void setUniversityDateService(UniversityDateService universityDateService) {
2068         this.universityDateService = universityDateService;
2069     }
2070 
2071 
2072     public void setBankService(BankService bankService) {
2073         this.bankService = bankService;
2074     }
2075 
2076 
2077     public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
2078         this.purchaseOrderService = purchaseOrderService;
2079     }
2080 
2081     public void setFinancialSystemWorkflowHelperService(FinancialSystemWorkflowHelperService financialSystemWorkflowHelperService) {
2082         this.financialSystemWorkflowHelperService = financialSystemWorkflowHelperService;
2083     }
2084 
2085 
2086     public void setKualiRuleService(KualiRuleService kualiRuleService) {
2087         this.kualiRuleService = kualiRuleService;
2088     }
2089 
2090     /**
2091      * Gets the accountsPayableService attribute.
2092      *
2093      * @return Returns the accountsPayableService
2094      */
2095 
2096     public AccountsPayableService getAccountsPayableService() {
2097         return SpringContext.getBean(AccountsPayableService.class);
2098     }
2099 
2100     public String getParameter(String namespaceCode, String componentCode, String parameterName) {
2101         ParameterKey parameterKey = ParameterKey.create(OLEConstants.OLE_CMPNT,
2102                 namespaceCode, componentCode, parameterName);
2103         Parameter parameter = CoreServiceApiServiceLocator.getParameterRepositoryService().getParameter(parameterKey);
2104         return parameter != null ? parameter.getValue() : null;
2105     }
2106 
2107     public boolean getParameterBoolean(String namespaceCode, String componentCode, String parameterName) {
2108         ParameterKey parameterKey = ParameterKey.create(OLEConstants.OLE_CMPNT,
2109                 namespaceCode, componentCode, parameterName);
2110         Parameter parameter = CoreServiceApiServiceLocator.getParameterRepositoryService().getParameter(parameterKey);
2111         if (parameter != null && parameter.getValue().equalsIgnoreCase("y")) {
2112             return true;
2113         }
2114         return false;
2115     }
2116 }