View Javadoc
1   /*
2    * Copyright 2006 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.module.purap.document.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.lang.text.StrBuilder;
20  import org.kuali.ole.coa.businessobject.Account;
21  import org.kuali.ole.coa.service.AccountService;
22  import org.kuali.ole.deliver.batch.OleMailer;
23  import org.kuali.ole.integration.purap.CapitalAssetSystem;
24  import org.kuali.ole.module.purap.*;
25  import org.kuali.ole.module.purap.PurapConstants.*;
26  import org.kuali.ole.module.purap.batch.AutoCloseRecurringOrdersStep;
27  import org.kuali.ole.module.purap.businessobject.*;
28  import org.kuali.ole.module.purap.document.*;
29  import org.kuali.ole.module.purap.document.dataaccess.PurchaseOrderDao;
30  import org.kuali.ole.module.purap.document.service.*;
31  import org.kuali.ole.module.purap.edi.PurchaseOrderEdi;
32  import org.kuali.ole.module.purap.pdf.PurchaseOrderParameters;
33  import org.kuali.ole.module.purap.pdf.PurchaseOrderPdf;
34  import org.kuali.ole.module.purap.pdf.PurchaseOrderTransmitParameters;
35  import org.kuali.ole.module.purap.util.PurApObjectUtils;
36  import org.kuali.ole.module.purap.util.ThresholdHelper;
37  import org.kuali.ole.module.purap.util.ThresholdHelper.ThresholdSummary;
38  import org.kuali.ole.select.service.OleTransmissionService;
39  import org.kuali.ole.sys.OLEConstants;
40  import org.kuali.ole.sys.OLEPropertyConstants;
41  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
42  import org.kuali.ole.sys.context.SpringContext;
43  import org.kuali.ole.sys.document.FinancialSystemTransactionalDocumentBase;
44  import org.kuali.ole.sys.document.validation.event.AttributedRouteDocumentEvent;
45  import org.kuali.ole.sys.document.validation.event.DocumentSystemSaveEvent;
46  import org.kuali.ole.sys.service.impl.OleParameterConstants;
47  import org.kuali.ole.vnd.VendorConstants;
48  import org.kuali.ole.vnd.VendorConstants.AddressTypes;
49  import org.kuali.ole.vnd.businessobject.*;
50  import org.kuali.ole.vnd.document.service.VendorService;
51  import org.kuali.rice.core.api.config.property.ConfigContext;
52  import org.kuali.rice.core.api.config.property.ConfigurationService;
53  import org.kuali.rice.core.api.datetime.DateTimeService;
54  import org.kuali.rice.core.api.mail.*;
55  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
56  import org.kuali.rice.core.api.util.type.KualiDecimal;
57  import org.kuali.rice.coreservice.api.parameter.Parameter;
58  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
59  import org.kuali.rice.kew.api.KewApiConstants;
60  import org.kuali.rice.kew.api.KewApiServiceLocator;
61  import org.kuali.rice.kew.api.WorkflowDocument;
62  import org.kuali.rice.kew.api.action.ActionRequestType;
63  import org.kuali.rice.kew.api.document.WorkflowDocumentService;
64  import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue;
65  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
66  import org.kuali.rice.kew.api.document.search.DocumentSearchResult;
67  import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
68  import org.kuali.rice.kew.api.exception.WorkflowException;
69  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
70  import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
71  import org.kuali.rice.kew.service.KEWServiceLocator;
72  import org.kuali.rice.kim.api.identity.Person;
73  import org.kuali.rice.kim.api.identity.PersonService;
74  import org.kuali.rice.kns.document.MaintenanceDocument;
75  import org.kuali.rice.kns.maintenance.Maintainable;
76  import org.kuali.rice.kns.service.DataDictionaryService;
77  import org.kuali.rice.kns.util.KNSGlobalVariables;
78  import org.kuali.rice.krad.bo.AdHocRoutePerson;
79  import org.kuali.rice.krad.bo.AdHocRouteRecipient;
80  import org.kuali.rice.krad.bo.Note;
81  import org.kuali.rice.krad.datadictionary.exception.UnknownDocumentTypeException;
82  import org.kuali.rice.krad.document.Document;
83  import org.kuali.rice.krad.document.DocumentBase;
84  import org.kuali.rice.krad.exception.ValidationException;
85  import org.kuali.rice.krad.rules.rule.event.RouteDocumentEvent;
86  import org.kuali.rice.krad.service.*;
87  import org.kuali.rice.krad.util.GlobalVariables;
88  import org.kuali.rice.krad.util.MessageMap;
89  import org.kuali.rice.krad.util.ObjectUtils;
90  import org.springframework.transaction.annotation.Transactional;
91  
92  import java.io.ByteArrayOutputStream;
93  import java.io.File;
94  import java.sql.Timestamp;
95  import java.text.ParseException;
96  import java.util.*;
97  
98  @Transactional
99  public class PurchaseOrderServiceImpl implements PurchaseOrderService {
100     private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurchaseOrderServiceImpl.class);
101 
102     protected BusinessObjectService businessObjectService;
103     protected DateTimeService dateTimeService;
104     protected DocumentService documentService;
105     protected NoteService noteService;
106     protected PurapService purapService;
107     protected PrintService printService;
108     protected PurchaseOrderDao purchaseOrderDao;
109     protected WorkflowDocumentService workflowDocumentService;
110     protected ConfigurationService kualiConfigurationService;
111     protected KualiRuleService kualiRuleService;
112     protected VendorService vendorService;
113     protected RequisitionService requisitionService;
114     protected PurApWorkflowIntegrationService purapWorkflowIntegrationService;
115     protected MaintenanceDocumentService maintenanceDocumentService;
116     protected ParameterService parameterService;
117     protected PersonService personService;
118     protected MailService mailService;
119     protected B2BPurchaseOrderService b2bPurchaseOrderService;
120     protected DataDictionaryService dataDictionaryService;
121     protected String documentNumber;
122     protected String fromEmailAddress;
123     protected List fileNameList;
124     protected String toEmailAddress;
125 
126     protected static final boolean TRANSMISSION_IS_RETRANSMIT = true;
127     protected static final boolean TRANSMISSION_IS_NOT_RETRANSMIT = !TRANSMISSION_IS_RETRANSMIT;
128 
129     @Override
130     public boolean isPurchaseOrderOpenForProcessing(Integer poId) {
131         return isPurchaseOrderOpenForProcessing(getCurrentPurchaseOrder(poId));
132     }
133 
134     @Override
135     public boolean isPurchaseOrderOpenForProcessing(PurchaseOrderDocument purchaseOrderDocument) {
136         boolean can = PurchaseOrderStatuses.APPDOC_OPEN.equals(purchaseOrderDocument.getApplicationDocumentStatus());
137         can = can && purchaseOrderDocument.isPurchaseOrderCurrentIndicator() && !purchaseOrderDocument.isPendingActionIndicator();
138 
139         // can't be any PREQ or CM that have not completed fullDocumentEntry
140         if (can) {
141             List<PaymentRequestView> preqViews = purchaseOrderDocument.getRelatedViews().getRelatedPaymentRequestViews();
142             if (preqViews != null) {
143                 for (PaymentRequestView preqView : preqViews) {
144                     if (!purapService.isPaymentRequestFullDocumentEntryCompleted(preqView.getApplicationDocumentStatus())) {
145                         return false;
146                     }
147                 }
148             }
149             List<CreditMemoView> cmViews = purchaseOrderDocument.getRelatedViews().getRelatedCreditMemoViews();
150             if (cmViews != null) {
151                 for (CreditMemoView cmView : cmViews) {
152                     if (!purapService.isVendorCreditMemoFullDocumentEntryCompleted(cmView.getApplicationDocumentStatus())) {
153                         return false;
154                     }
155                 }
156             }
157             List<InvoiceView> invViews = purchaseOrderDocument.getRelatedViews().getRelatedInvoiceViews();
158             if (invViews != null) {
159                 for (InvoiceView invView : invViews) {
160                     if (!purapService.isInvoiceFullDocumentEntryCompleted(invView.getApplicationDocumentStatus())) {
161                         return false;
162                     }
163                 }
164             }
165 
166         }
167 
168         // passed all conditions; return true
169         return can;
170     }
171 
172     @Override
173     public boolean isCommodityCodeRequiredOnPurchaseOrder() {
174         boolean enableCommodityCode = parameterService.getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_COMMODITY_CODE_IND);
175         if (!enableCommodityCode) {
176             return false;
177         } else {
178             return parameterService.getParameterValueAsBoolean(PurchaseOrderDocument.class, PurapRuleConstants.ITEMS_REQUIRE_COMMODITY_CODE_IND);
179         }
180     }
181 
182     /**
183      * Sets the error map to a new, empty error map before calling saveDocumentNoValidation to save the document.
184      *
185      * @param document The purchase order document to be saved.
186      */
187     protected void saveDocumentNoValidationUsingClearMessageMap(PurchaseOrderDocument document) {
188         MessageMap errorHolder = GlobalVariables.getMessageMap();
189         GlobalVariables.setMessageMap(new MessageMap());
190         try {
191             purapService.saveDocumentNoValidation(document);
192         } finally {
193             GlobalVariables.setMessageMap(errorHolder);
194         }
195     }
196 
197     /**
198      * Calls the saveDocument method of documentService to save the document.
199      *
200      * @param document The document to be saved.
201      */
202     protected void saveDocumentStandardSave(PurchaseOrderDocument document) {
203         try {
204             documentService.saveDocument(document);
205         } catch (WorkflowException we) {
206             String errorMsg = "Workflow Error saving document # " + document.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
207             LOG.error(errorMsg, we);
208             throw new RuntimeException(errorMsg, we);
209         }
210     }
211 
212     @Override
213     public PurchasingCapitalAssetItem createCamsItem(PurchasingDocument purDoc, PurApItem purapItem) {
214         PurchasingCapitalAssetItem camsItem = new PurchaseOrderCapitalAssetItem();
215         camsItem.setItemIdentifier(purapItem.getItemIdentifier());
216         // If the system type is INDIVIDUAL then for each of the capital asset items, we need a system attached to it.
217         if (purDoc.getCapitalAssetSystemTypeCode().equals(PurapConstants.CapitalAssetTabStrings.INDIVIDUAL_ASSETS)) {
218             CapitalAssetSystem resultSystem = new PurchaseOrderCapitalAssetSystem();
219             camsItem.setPurchasingCapitalAssetSystem(resultSystem);
220         }
221         camsItem.setPurchasingDocument(purDoc);
222 
223         return camsItem;
224     }
225 
226     @Override
227     public CapitalAssetSystem createCapitalAssetSystem() {
228         CapitalAssetSystem resultSystem = new PurchaseOrderCapitalAssetSystem();
229         return resultSystem;
230     }
231 
232     /**
233      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#createAutomaticPurchaseOrderDocument(org.kuali.ole.module.purap.document.RequisitionDocument)
234      */
235     @Override
236     public void createAutomaticPurchaseOrderDocument(RequisitionDocument reqDocument) {
237         String newSessionUserId = OLEConstants.SYSTEM_USER;
238         try {
239 
240             LogicContainer logicToRun = new LogicContainer() {
241                 @Override
242                 public Object runLogic(Object[] objects) throws Exception {
243                     RequisitionDocument doc = (RequisitionDocument) objects[0];
244                     // update REQ data
245                     doc.setPurchaseOrderAutomaticIndicator(Boolean.TRUE);
246                     // create PO and populate with default data
247                     PurchaseOrderDocument po = generatePurchaseOrderFromRequisition(doc);
248                     po.setDefaultValuesForAPO();
249                     //check for print transmission method.. if print is selected
250                     //the doc status needs to be "Pending To Print"..
251                     checkForPrintTransmission(po);
252                     po.setContractManagerCode(PurapConstants.APO_CONTRACT_MANAGER);
253 
254                     //documentService.routeDocument(po, null, null);
255 
256                     final DocumentAttributeIndexingQueue documentAttributeIndexingQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue();
257                     documentAttributeIndexingQueue.indexDocument(po.getDocumentNumber());
258 
259                     return null;
260                 }
261             };
262             purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[]{reqDocument});
263         } catch (WorkflowException e) {
264             String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
265             LOG.error(errorMsg, e);
266             throw new RuntimeException(errorMsg, e);
267         } catch (Exception e) {
268             throw new RuntimeException(e);
269         }
270     }
271 
272     /**
273      * checks for print option and if chosen then sets the app doc status to
274      * Pending To Print.
275      *
276      * @param po
277      */
278     protected void checkForPrintTransmission(PurchaseOrderDocument po) throws WorkflowException {
279         if (PurapConstants.POTransmissionMethods.PRINT.equals(po.getPurchaseOrderRetransmissionMethodCode())) {
280             po.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_PRINT);
281         }
282     }
283 
284     /**
285      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#createPurchaseOrderDocument(org.kuali.ole.module.purap.document.RequisitionDocument,
286      *      java.lang.String, java.lang.Integer)
287      */
288     @Override
289     public PurchaseOrderDocument createPurchaseOrderDocument(RequisitionDocument reqDocument, String newSessionUserId, Integer contractManagerCode) {
290         try {
291             LogicContainer logicToRun = new LogicContainer() {
292                 @Override
293                 public Object runLogic(Object[] objects) throws Exception {
294                     RequisitionDocument doc = (RequisitionDocument) objects[0];
295                     PurchaseOrderDocument po = generatePurchaseOrderFromRequisition(doc);
296                     Integer cmCode = (Integer) objects[1];
297                     po.setContractManagerCode(cmCode);
298                     purapService.saveDocumentNoValidation(po);
299                     return po;
300                 }
301             };
302             return (PurchaseOrderDocument) purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[]{reqDocument, contractManagerCode});
303         } catch (WorkflowException e) {
304             String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
305             LOG.error(errorMsg, e);
306             throw new RuntimeException(errorMsg, e);
307         } catch (Exception e) {
308             throw new RuntimeException(e);
309         }
310     }
311 
312     /**
313      * Create Purchase Order and populate with data from Requisition and other default data
314      *
315      * @param reqDocument The requisition document from which we create the purchase order document.
316      * @return The purchase order document created by this method.
317      * @throws WorkflowException
318      */
319     protected PurchaseOrderDocument generatePurchaseOrderFromRequisition(RequisitionDocument reqDocument) throws WorkflowException {
320         PurchaseOrderDocument poDocument = null;
321         poDocument = (PurchaseOrderDocument) documentService.getNewDocument(PurchaseOrderDocTypes.PURCHASE_ORDER_DOCUMENT);
322         poDocument.populatePurchaseOrderFromRequisition(reqDocument);
323 
324         poDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_IN_PROCESS);
325 
326         poDocument.setPurchaseOrderCurrentIndicator(true);
327         poDocument.setPendingActionIndicator(false);
328         /*poDocument.setLicensingRequirementIndicator(reqDocument.isLicensingRequirementIndicator());*/
329        /* poDocument.setLicensingRequirementCode(reqDocument.getLicensingRequirementCode());*/
330 
331         if (RequisitionSources.B2B.equals(poDocument.getRequisitionSourceCode())) {
332             String paramName = PurapParameterConstants.DEFAULT_B2B_VENDOR_CHOICE;
333             String paramValue = parameterService.getParameterValueAsString(PurchaseOrderDocument.class, paramName);
334             poDocument.setPurchaseOrderVendorChoiceCode(paramValue);
335         }
336 
337         if (ObjectUtils.isNotNull(poDocument.getVendorContract())) {
338             poDocument.setVendorPaymentTermsCode(poDocument.getVendorContract().getVendorPaymentTermsCode());
339             poDocument.setVendorShippingPaymentTermsCode(poDocument.getVendorContract().getVendorShippingPaymentTermsCode());
340             poDocument.setVendorShippingTitleCode(poDocument.getVendorContract().getVendorShippingTitleCode());
341         } else {
342             VendorDetail vendor = vendorService.getVendorDetail(poDocument.getVendorHeaderGeneratedIdentifier(), poDocument.getVendorDetailAssignedIdentifier());
343             if (ObjectUtils.isNotNull(vendor)) {
344                 poDocument.setVendorPaymentTermsCode(vendor.getVendorPaymentTermsCode());
345                 poDocument.setVendorShippingPaymentTermsCode(vendor.getVendorShippingPaymentTermsCode());
346                 poDocument.setVendorShippingTitleCode(vendor.getVendorShippingTitleCode());
347             }
348         }
349 
350         if (!PurapConstants.RequisitionSources.B2B.equals(poDocument.getRequisitionSourceCode())) {
351             purapService.addBelowLineItems(poDocument);
352         }
353         poDocument.fixItemReferences();
354 
355         return poDocument;
356     }
357 
358     /**
359      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#getInternalPurchasingDollarLimit(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
360      */
361     @Override
362     public KualiDecimal getInternalPurchasingDollarLimit(PurchaseOrderDocument document) {
363         if ((document.getVendorContract() != null) && (document.getContractManager() != null)) {
364             KualiDecimal contractDollarLimit = vendorService.getApoLimitFromContract(document.getVendorContract().getVendorContractGeneratedIdentifier(), document.getChartOfAccountsCode(), document.getOrganizationCode());
365             // FIXME somehow data fields such as contractManagerDelegationDollarLimit in reference object contractManager didn't get
366             // retrieved
367             // (are null) as supposed to be (this happens whether or not proxy is set to true), even though contractManager is not
368             // null;
369             // so here we have to manually refresh the contractManager to retrieve the fields
370             if (document.getContractManager().getContractManagerDelegationDollarLimit() == null) {
371                 document.refreshReferenceObject(PurapPropertyConstants.CONTRACT_MANAGER);
372             }
373             KualiDecimal contractManagerLimit = document.getContractManager().getContractManagerDelegationDollarLimit();
374             if ((contractDollarLimit != null) && (contractManagerLimit != null)) {
375                 if (contractDollarLimit.compareTo(contractManagerLimit) > 0) {
376                     return contractDollarLimit;
377                 } else {
378                     return contractManagerLimit;
379                 }
380             } else if (contractDollarLimit != null) {
381                 return contractDollarLimit;
382             } else {
383                 return contractManagerLimit;
384             }
385         } else if ((document.getVendorContract() == null) && (document.getContractManager() != null)) {
386             // FIXME As above, here we have to manually refresh the contractManager to retrieve its field
387             if (document.getContractManager().getContractManagerDelegationDollarLimit() == null) {
388                 document.refreshReferenceObject(PurapPropertyConstants.CONTRACT_MANAGER);
389             }
390             return document.getContractManager().getContractManagerDelegationDollarLimit();
391         } else if ((document.getVendorContract() != null) && (document.getContractManager() == null)) {
392             return purapService.getApoLimit(document.getVendorContract().getVendorContractGeneratedIdentifier(), document.getChartOfAccountsCode(), document.getOrganizationCode());
393         } else {
394             String errorMsg = "No internal purchase order dollar limit found for purchase order '" + document.getPurapDocumentIdentifier() + "'.";
395             LOG.warn(errorMsg);
396             return null;
397         }
398     }
399 
400     /**
401      * Loops through the collection of error messages and adding each of them to the error map.
402      *
403      * @param errorKey The resource key used to retrieve the error text from the error message resource bundle.
404      * @param errors   The collection of error messages.
405      */
406     protected void addStringErrorMessagesToMessageMap(String errorKey, Collection<String> errors) {
407         if (ObjectUtils.isNotNull(errors)) {
408             for (String error : errors) {
409                 LOG.error("Adding error message using error key '" + errorKey + "' with text '" + error + "'");
410                 GlobalVariables.getMessageMap().putError(OLEConstants.GLOBAL_ERRORS, errorKey, error);
411             }
412         }
413     }
414 
415 
416     /**
417      * TODO RELEASE 3 - QUOTE
418      *
419      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#printPurchaseOrderQuoteRequestsListPDF(org.kuali.ole.module.purap.document.PurchaseOrderDocument,
420      *      java.io.ByteArrayOutputStream)
421      */
422     @Override
423     public boolean printPurchaseOrderQuoteRequestsListPDF(String documentNumber, ByteArrayOutputStream baosPDF) {
424         PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
425         String environment = kualiConfigurationService.getPropertyValueAsString(OLEConstants.ENVIRONMENT_KEY);
426         Collection<String> generatePDFErrors = printService.generatePurchaseOrderQuoteRequestsListPdf(po, baosPDF);
427 
428         if (generatePDFErrors.size() > 0) {
429             addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
430             return false;
431         } else {
432             return true;
433         }
434     }
435 
436     /**
437      * TODO RELEASE 3 - QUOTE
438      *
439      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#printPurchaseOrderQuotePDF(org.kuali.ole.module.purap.document.PurchaseOrderDocument,
440      *      org.kuali.ole.module.purap.businessobject.PurchaseOrderVendorQuote, java.io.ByteArrayOutputStream)
441      */
442     @Override
443     public boolean printPurchaseOrderQuotePDF(PurchaseOrderDocument po, PurchaseOrderVendorQuote povq, ByteArrayOutputStream baosPDF) {
444         String environment = kualiConfigurationService.getPropertyValueAsString(OLEConstants.ENVIRONMENT_KEY);
445         Collection<String> generatePDFErrors = printService.generatePurchaseOrderQuotePdf(po, povq, baosPDF, environment);
446 
447         if (generatePDFErrors.size() > 0) {
448             addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
449             return false;
450         } else {
451             return true;
452         }
453     }
454 
455     /**
456      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#performPurchaseOrderFirstTransmitViaPrinting(java.lang.String,
457      *      java.io.ByteArrayOutputStream)
458      */
459     @Override
460     public void performPurchaseOrderFirstTransmitViaPrinting(String documentNumber, ByteArrayOutputStream baosPDF) {
461         PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
462         String environment = kualiConfigurationService.getPropertyValueAsString(OLEConstants.ENVIRONMENT_KEY);
463         Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdf(po, baosPDF, environment, null);
464         if (!generatePDFErrors.isEmpty()) {
465             addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
466             throw new ValidationException("printing purchase order for first transmission failed");
467         }
468         if (ObjectUtils.isNotNull(po.getPurchaseOrderFirstTransmissionTimestamp())) {
469             // should not call this method for first transmission if document has already been transmitted
470             String errorMsg = "Method to perform first transmit was called on document (doc id " + documentNumber + ") with already filled in 'first transmit date'";
471             LOG.error(errorMsg);
472             throw new RuntimeException(errorMsg);
473         }
474         Timestamp currentDate = dateTimeService.getCurrentTimestamp();
475         po.setPurchaseOrderFirstTransmissionTimestamp(currentDate);
476         po.setPurchaseOrderLastTransmitTimestamp(currentDate);
477         po.setOverrideWorkflowButtons(Boolean.FALSE);
478         boolean performedAction = purapWorkflowIntegrationService.takeAllActionsForGivenCriteria(po, "Action taken automatically as part of document initial print transmission", PurapConstants.PurchaseOrderStatuses.NODE_DOCUMENT_TRANSMISSION, GlobalVariables.getUserSession().getPerson(), null);
479         if (!performedAction) {
480             Person systemUserPerson = getPersonService().getPersonByPrincipalName(OLEConstants.SYSTEM_USER);
481             purapWorkflowIntegrationService.takeAllActionsForGivenCriteria(po, "Action taken automatically as part of document initial print transmission by user " + GlobalVariables.getUserSession().getPerson().getName(), PurapConstants.PurchaseOrderStatuses.NODE_DOCUMENT_TRANSMISSION, systemUserPerson, OLEConstants.SYSTEM_USER);
482         }
483         po.setOverrideWorkflowButtons(Boolean.TRUE);
484         if (!po.getApplicationDocumentStatus().equals(PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN)) {
485             attemptSetupOfInitialOpenOfDocument(po);
486         }
487         purapService.saveDocumentNoValidation(po);
488     }
489 
490     /**
491      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#performPurchaseOrderPreviewPrinting(java.lang.String,
492      *      java.io.ByteArrayOutputStream)
493      */
494     @Override
495     public void performPurchaseOrderPreviewPrinting(String documentNumber, ByteArrayOutputStream baosPDF) {
496         performPrintPurchaseOrderPDFOnly(documentNumber, baosPDF);
497     }
498 
499     /**
500      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#performPrintPurchaseOrderPDFOnly(java.lang.String,
501      *      java.io.ByteArrayOutputStream)
502      */
503     @Override
504     public void performPrintPurchaseOrderPDFOnly(String documentNumber, ByteArrayOutputStream baosPDF) {
505         PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
506         String environment = kualiConfigurationService.getPropertyValueAsString(OLEConstants.ENVIRONMENT_KEY);
507         Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdf(po, baosPDF, environment, null);
508         if (!generatePDFErrors.isEmpty()) {
509             addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
510             throw new ValidationException("printing purchase order for first transmission failed");
511         }
512     }
513 
514     /**
515      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#retransmitPurchaseOrderPDF(org.kuali.ole.module.purap.document.PurchaseOrderDocument,
516      *      java.io.ByteArrayOutputStream)
517      */
518     @Override
519     public void retransmitPurchaseOrderPDF(PurchaseOrderDocument po, ByteArrayOutputStream baosPDF) {
520 
521         String environment = kualiConfigurationService.getPropertyValueAsString(OLEConstants.ENVIRONMENT_KEY);
522         List<PurchaseOrderItem> items = po.getItems();
523         List<PurchaseOrderItem> retransmitItems = new ArrayList<PurchaseOrderItem>();
524         for (PurchaseOrderItem item : items) {
525             if (item.isItemSelectedForRetransmitIndicator()) {
526                 retransmitItems.add(item);
527             }
528         }
529         Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdfForRetransmission(po, baosPDF, environment, retransmitItems);
530 
531         if (generatePDFErrors.size() > 0) {
532             addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
533             throw new ValidationException("found errors while trying to print po with doc id " + po.getDocumentNumber());
534         }
535         po.setPurchaseOrderLastTransmitTimestamp(dateTimeService.getCurrentTimestamp());
536         purapService.saveDocumentNoValidation(po);
537     }
538 
539     /**
540      * This method creates a new Purchase Order Document using the given document type based off the given source document. This
541      * method will return null if the source document given is null.<br>
542      * <br>
543      * ** THIS METHOD DOES NOT SAVE EITHER THE GIVEN SOURCE DOCUMENT OR THE NEW DOCUMENT CREATED
544      *
545      * @param sourceDocument - document the new Purchase Order Document should be based off of in terms of data
546      * @param docType        - document type of the potential new Purchase Order Document
547      * @return the new Purchase Order Document of the given document type or null if the given source document is null
548      * @throws WorkflowException if a new document cannot be created using the given type
549      */
550     protected PurchaseOrderDocument createPurchaseOrderDocumentFromSourceDocument(PurchaseOrderDocument sourceDocument, String docType) throws WorkflowException {
551         if (ObjectUtils.isNull(sourceDocument)) {
552             String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc that is null";
553             LOG.error(errorMsg);
554             throw new RuntimeException(errorMsg);
555         }
556 
557         PurchaseOrderDocument newPurchaseOrderChangeDocument = (PurchaseOrderDocument) documentService.getNewDocument(docType);
558         newPurchaseOrderChangeDocument.setAccountDistributionMethod(sourceDocument.getAccountDistributionMethod());
559 
560         Set classesToExclude = new HashSet();
561         Class sourceObjectClass = FinancialSystemTransactionalDocumentBase.class;
562         classesToExclude.add(sourceObjectClass);
563         while (sourceObjectClass.getSuperclass() != null) {
564             sourceObjectClass = sourceObjectClass.getSuperclass();
565             classesToExclude.add(sourceObjectClass);
566         }
567         PurApObjectUtils.populateFromBaseWithSuper(sourceDocument, newPurchaseOrderChangeDocument, PurapConstants.uncopyableFieldsForPurchaseOrder(), classesToExclude);
568         newPurchaseOrderChangeDocument.getDocumentHeader().setDocumentDescription(sourceDocument.getDocumentHeader().getDocumentDescription());
569         newPurchaseOrderChangeDocument.getDocumentHeader().setOrganizationDocumentNumber(sourceDocument.getDocumentHeader().getOrganizationDocumentNumber());
570         newPurchaseOrderChangeDocument.getDocumentHeader().setExplanation(sourceDocument.getDocumentHeader().getExplanation());
571         newPurchaseOrderChangeDocument.setPurchaseOrderCurrentIndicator(false);
572         newPurchaseOrderChangeDocument.setPendingActionIndicator(false);
573 
574         // TODO f2f: what is this doing?
575         // Need to find a way to make the ManageableArrayList to expand and populating the items and
576         // accounts, otherwise it will complain about the account on item 1 is missing.
577         for (PurApItem item : (List<PurApItem>) newPurchaseOrderChangeDocument.getItems()) {
578             item.getSourceAccountingLines().iterator();
579             // we only need to do this once to apply to all items, so we can break out of the loop now
580             SequenceAccessorService sas = SpringContext.getBean(SequenceAccessorService.class);
581             Integer itemIdentifier = sas.getNextAvailableSequenceNumber("PO_ITM_ID", PurApItem.class).intValue();
582             item.setItemIdentifier(itemIdentifier);
583         }
584 
585         updateCapitalAssetRelatedCollections(newPurchaseOrderChangeDocument);
586         newPurchaseOrderChangeDocument.refreshNonUpdateableReferences();
587 
588         return newPurchaseOrderChangeDocument;
589     }
590 
591     protected void updateCapitalAssetRelatedCollections(PurchaseOrderDocument newDocument) {
592 
593         for (PurchasingCapitalAssetItem capitalAssetItem : newDocument.getPurchasingCapitalAssetItems()) {
594             Integer lineNumber = capitalAssetItem.getPurchasingItem().getItemLineNumber();
595             PurApItem newItem = newDocument.getItemByLineNumber(lineNumber.intValue());
596             capitalAssetItem.setItemIdentifier(newItem.getItemIdentifier());
597             capitalAssetItem.setPurchasingDocument(newDocument);
598             capitalAssetItem.setCapitalAssetSystemIdentifier(null);
599             CapitalAssetSystem oldSystem = capitalAssetItem.getPurchasingCapitalAssetSystem();
600             capitalAssetItem.setPurchasingCapitalAssetSystem(new PurchaseOrderCapitalAssetSystem(oldSystem));
601 
602         }
603     }
604 
605     /**
606      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#createAndSavePotentialChangeDocument(java.lang.String,
607      *      java.lang.String, java.lang.String)
608      */
609     @Override
610     public PurchaseOrderDocument createAndSavePotentialChangeDocument(String documentNumber, String docType, String currentDocumentStatusCode) {
611         PurchaseOrderDocument currentDocument = getPurchaseOrderByDocumentNumber(documentNumber);
612 
613         try {
614             PurchaseOrderDocument newDocument = createPurchaseOrderDocumentFromSourceDocument(currentDocument, docType);
615 
616             if (ObjectUtils.isNotNull(newDocument)) {
617                 newDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS);
618 
619                 // set status if needed
620                 if (StringUtils.isNotBlank(currentDocumentStatusCode)) {
621                     currentDocument.updateAndSaveAppDocStatus(currentDocumentStatusCode);
622                 }
623                 try {
624                     documentService.saveDocument(newDocument, DocumentSystemSaveEvent.class);
625                 }
626                 // if we catch a ValidationException it means the new PO doc found errors
627                 catch (ValidationException ve) {
628                     throw ve;
629                 }
630                 // if no validation exception was thrown then rules have passed and we are ok to edit the current PO
631                 currentDocument.setPendingActionIndicator(true);
632                 Note note = new Note();
633                 List<Note> noteList = new ArrayList<Note>();
634                 currentDocument.setNotes(noteList);
635                 saveDocumentNoValidationUsingClearMessageMap(currentDocument);
636                 // savePurchaseOrderData(currentDocument);
637 
638                 return newDocument;
639             } else {
640                 String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc id "
641                         + documentNumber + " returned null for new document";
642                 LOG.error(errorMsg);
643                 throw new RuntimeException(errorMsg);
644             }
645         } catch (WorkflowException we) {
646             String errorMsg = "Workflow Exception caught trying to create and save PO document of type '" + docType
647                     + "' using source document with doc id '" + documentNumber + "'";
648             LOG.error(errorMsg, we);
649             throw new RuntimeException(errorMsg, we);
650         }
651     }
652 
653     /**
654      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#createAndRoutePotentialChangeDocument(java.lang.String,
655      *      java.lang.String, java.lang.String, java.util.List, java.lang.String)
656      */
657     @Override
658     public PurchaseOrderDocument createAndRoutePotentialChangeDocument(String documentNumber, String docType, String annotation, List adhocRoutingRecipients, String currentDocumentStatusCode) {
659         PurchaseOrderDocument currentDocument = getPurchaseOrderByDocumentNumber(documentNumber);
660 
661         try {
662             currentDocument.updateAndSaveAppDocStatus(currentDocumentStatusCode);
663         } catch (WorkflowException e) {
664             throw new RuntimeException("Error saving routing data while saving document with id " + currentDocument.getDocumentNumber(), e);
665         }
666 
667         try {
668             PurchaseOrderDocument newDocument = createPurchaseOrderDocumentFromSourceDocument(currentDocument, docType);
669             // newDocument.setStatusCode(PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS);
670             newDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS);
671             if (ObjectUtils.isNotNull(newDocument)) {
672                 try {
673                     // set the pending indictor before routing, so that when routing is done in synch mode, the pending indicator
674                     // won't be set again after route finishes and cause inconsistency
675                     currentDocument.setPendingActionIndicator(true);
676                     documentService.routeDocument(newDocument, annotation, adhocRoutingRecipients);
677                 }
678                 // if we catch a ValidationException it means the new PO doc found errors
679                 catch (ValidationException ve) {
680                     // clear the pending indictor if an exception occurs, to leave the existing PO intact
681                     currentDocument.setPendingActionIndicator(false);
682                     //savePurchaseOrderData(currentDocument);
683                     saveDocumentNoValidationUsingClearMessageMap(currentDocument);
684                     throw ve;
685                 }
686                 return newDocument;
687             } else {
688                 String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc id " + documentNumber + " returned null for new document";
689                 LOG.error(errorMsg);
690                 throw new RuntimeException(errorMsg);
691             }
692         } catch (WorkflowException we) {
693             String errorMsg = "Workflow Exception caught trying to create and route PO document of type '" + docType + "' using source document with doc id '" + documentNumber + "'";
694             LOG.error(errorMsg, we);
695             throw new RuntimeException(errorMsg, we);
696         }
697     }
698 
699     /**
700      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#createAndSavePurchaseOrderSplitDocument(java.util.List,
701      *      java.lang.String, boolean)
702      */
703     @Override
704     public PurchaseOrderSplitDocument createAndSavePurchaseOrderSplitDocument(List<PurchaseOrderItem> newPOItems, PurchaseOrderDocument currentDocument, boolean copyNotes, String splitNoteText) {
705 
706         if (ObjectUtils.isNull(currentDocument)) {
707             String errorMsg = "Attempting to create new PO of type PurchaseOrderSplitDocument from source PO doc that is null";
708             LOG.error(errorMsg);
709             throw new RuntimeException(errorMsg);
710         }
711         // Following code added to add notes to the current PO Document
712         try {
713             Note splitNote = SpringContext.getBean(DocumentService.class).createNoteFromDocument(currentDocument, splitNoteText);
714             currentDocument.addNote(splitNote);
715             noteService.save(splitNote);
716         } catch (Exception e) {
717             throw new RuntimeException(e);
718         }
719         String documentNumber = currentDocument.getDocumentNumber();
720 
721         try {
722             // Create the new Split PO document (throws WorkflowException)
723             PurchaseOrderSplitDocument newDocument = (PurchaseOrderSplitDocument) documentService.getNewDocument(PurchaseOrderDocTypes.PURCHASE_ORDER_SPLIT_DOCUMENT);
724 
725             if (ObjectUtils.isNotNull(newDocument)) {
726 
727                 // Prepare for copying fields over from the current document.
728                 Set<Class> classesToExclude = getClassesToExcludeFromCopy();
729                 Map<String, Class> uncopyableFields = PurapConstants.UNCOPYABLE_FIELDS_FOR_PO;
730                 uncopyableFields.putAll(PurapConstants.uncopyableFieldsForSplitPurchaseOrder());
731 
732                 // Copy all fields over from the current document except the items and the above-specified fields.
733                 PurApObjectUtils.populateFromBaseWithSuper(currentDocument, newDocument, uncopyableFields, classesToExclude);
734                 newDocument.getDocumentHeader().setDocumentDescription(currentDocument.getDocumentHeader().getDocumentDescription());
735                 newDocument.getDocumentHeader().setOrganizationDocumentNumber(currentDocument.getDocumentHeader().getOrganizationDocumentNumber());
736                 newDocument.setPurchaseOrderCurrentIndicator(true);
737                 newDocument.setPendingActionIndicator(false);
738 
739                 newDocument.setAccountDistributionMethod(currentDocument.getAccountDistributionMethod());
740                 // Add in and renumber the items that the new document should have.
741                 newDocument.setItems(newPOItems);
742                 purapService.addBelowLineItems(newDocument);
743                 newDocument.renumberItems(0);
744 
745                 newDocument.setPostingYear(currentDocument.getPostingYear());
746 
747                 if (copyNotes) {
748                     // Copy the old notes, except for the one that contains the split note text.
749                     List<Note> notes = currentDocument.getNotes();
750                     int noteLength = notes.size();
751                     if (noteLength > 0) {
752                         notes.subList(noteLength - 1, noteLength).clear();
753                         for (Note note : notes) {
754                             try {
755                                 Note copyingNote = documentService.createNoteFromDocument(newDocument, note.getNoteText());
756                                 newDocument.addNote(copyingNote);
757                                 noteService.save(copyingNote);
758                             } catch (Exception e) {
759                                 throw new RuntimeException(e);
760                             }
761                         }
762                     }
763                 }
764 
765                 newDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_IN_PROCESS);
766 
767                 // fix references before saving
768                 fixItemReferences(newDocument);
769 
770                 // need to save the document first before creating the note
771                 purapService.saveDocumentNoValidation(newDocument);
772 
773                 // Modify the split note text and add the note.
774                 splitNoteText = splitNoteText.substring(splitNoteText.indexOf(":") + 1);
775                 splitNoteText = PurapConstants.PODocumentsStrings.SPLIT_NOTE_PREFIX_NEW_DOC + currentDocument.getPurapDocumentIdentifier() + " : " + splitNoteText;
776                 try {
777                     Note splitNote = documentService.createNoteFromDocument(newDocument, splitNoteText);
778                     newDocument.addNote(splitNote);
779                     noteService.save(splitNote);
780                 } catch (Exception e) {
781                     throw new RuntimeException(e);
782                 }
783 
784                 return newDocument;
785             } else {
786                 String errorMsg = "Attempting to create new PO of type 'PurchaseOrderSplitDocument' from source PO doc id " + documentNumber + " returned null for new document";
787                 LOG.error(errorMsg);
788                 throw new RuntimeException(errorMsg);
789             }
790         } catch (WorkflowException we) {
791             String errorMsg = "Workflow Exception caught trying to create and save PO document of type PurchaseOrderSplitDocument using source document with doc id '" + documentNumber + "'";
792             LOG.error(errorMsg, we);
793             throw new RuntimeException(errorMsg, we);
794         }
795     }
796 
797     /**
798      * Gets a set of classes to exclude from those whose fields will be copied during a copy operation from one Document to another.
799      *
800      * @return A Set<Class>
801      */
802     protected Set<Class> getClassesToExcludeFromCopy() {
803         Set<Class> classesToExclude = new HashSet<Class>();
804         Class sourceObjectClass = DocumentBase.class;
805         classesToExclude.add(sourceObjectClass);
806         while (sourceObjectClass.getSuperclass() != null) {
807             sourceObjectClass = sourceObjectClass.getSuperclass();
808             classesToExclude.add(sourceObjectClass);
809         }
810         return classesToExclude;
811     }
812 
813     /**
814      * Returns the current route node name.
815      *
816      * @param wd The KualiWorkflowDocument object whose current route node we're trying to get.
817      * @return The current route node name.
818      * @throws WorkflowException
819      */
820     protected String getCurrentRouteNodeName(WorkflowDocument wd) throws WorkflowException {
821         String[] nodeNames = (String[]) wd.getNodeNames().toArray();
822         if ((nodeNames == null) || (nodeNames.length == 0)) {
823             return null;
824         } else {
825             return nodeNames[0];
826         }
827     }
828 
829     /**
830      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#completePurchaseOrder(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
831      */
832     @Override
833     public void completePurchaseOrder(PurchaseOrderDocument po) {
834         LOG.debug("completePurchaseOrder() started");
835         setCurrentAndPendingIndicatorsForApprovedPODocuments(po);
836         setupDocumentForPendingFirstTransmission(po);
837 
838         // check thresholds to see if receiving is required for purchase order
839         if (!po.isReceivingDocumentRequiredIndicator()) {
840             setReceivingRequiredIndicatorForPurchaseOrder(po);
841         }
842 
843         // update the vendor record if the commodity code used on the PO is not already associated with the vendor.
844         updateVendorCommodityCode(po);
845 
846         // PERFORM ANY LOGIC THAT COULD POTENTIALLY CAUSE THE DOCUMENT TO FAIL BEFORE THIS LINE
847         // FOLLOWING LINES COULD INVOLVE TRANSMITTING THE PO TO THE VENDOR WHICH WILL NOT BE REVERSED IN A TRANSACTION ROLLBACK
848 
849         // if the document is set in a Pending Transmission status then don't OPEN the PO just leave it as is
850         if (!po.getRequisitionSourceCode().equalsIgnoreCase(PurapConstants.RequisitionSources.MANUAL_INGEST)) {
851             if (!(po.getOrderType().getPurchaseOrderType()).equals(OLEConstants.APPROVAL)) {
852                 List<PurApItem> items = po.getItems();
853                 fileNameList = new ArrayList();
854                 for (PurApItem item : items) {
855                     initiateTransmission(po, item);
856                 }
857                 sendEmail();
858             }
859         }
860         //initiateTransmission(po);
861 
862 
863         if (!PurchaseOrderStatuses.STATUSES_BY_TRANSMISSION_TYPE.values().contains(po.getApplicationDocumentStatus())) {
864             attemptSetupOfInitialOpenOfDocument(po);
865         } else if (PurchaseOrderStatuses.APPDOC_PENDING_CXML.equals(po.getApplicationDocumentStatus())) {
866             completeB2BPurchaseOrder(po);
867         } else if (PurchaseOrderStatuses.APPDOC_PENDING_PRINT.equals(po.getApplicationDocumentStatus())) {
868             // default to using user that routed PO
869             String userToRouteFyi = po.getDocumentHeader().getWorkflowDocument().getRoutedByPrincipalId();
870             if (po.getPurchaseOrderAutomaticIndicator()) {
871                 // if APO, use the user that initiated the requisition
872                 RequisitionDocument req = requisitionService.getRequisitionById(po.getRequisitionIdentifier());
873                 userToRouteFyi = req.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
874             }
875 
876             po.getDocumentHeader().getWorkflowDocument().adHocToPrincipal(ActionRequestType.FYI, po.getDocumentHeader().getWorkflowDocument().getCurrentNodeNames().iterator().next(), "This PO is ready for printing and distribution.", userToRouteFyi, "", true, "PRINT");
877         }
878 
879     }
880 
881 
882     protected boolean completeB2BPurchaseOrder(PurchaseOrderDocument po) {
883         String errors = b2bPurchaseOrderService.sendPurchaseOrder(po);
884         if (StringUtils.isEmpty(errors)) {
885             // PO sent successfully; change status to OPEN
886             attemptSetupOfInitialOpenOfDocument(po);
887             po.setPurchaseOrderLastTransmitTimestamp(dateTimeService.getCurrentTimestamp());
888             return true;
889         } else {
890             // PO transmission failed; record errors and change status to "cxml failed"
891             try {
892                 String noteText = "Unable to transmit the PO for the following reasons:\n" + errors;
893                 int noteMaxSize = dataDictionaryService.getAttributeMaxLength("Note", "noteText");
894 
895                 // Break up the note into multiple pieces if the note is too large to fit in the database field.
896                 while (noteText.length() > noteMaxSize) {
897                     int fromIndex = 0;
898                     String noteText1 = noteText.substring(0, noteMaxSize);
899                     Note note1 = documentService.createNoteFromDocument(po, noteText1);
900                     po.addNote(note1);
901                     documentService.saveDocumentNotes(po);
902                     noteText = noteText.substring(noteMaxSize);
903                 }
904 
905                 Note note = documentService.createNoteFromDocument(po, noteText);
906                 po.addNote(note);
907                 documentService.saveDocumentNotes(po);
908             } catch (Exception e) {
909                 throw new RuntimeException(e);
910             }
911 
912             try {
913                 po.updateAndSaveAppDocStatus(PurchaseOrderStatuses.APPDOC_CXML_ERROR);
914             } catch (WorkflowException e) {
915                 throw new RuntimeException("Error saving routing data while saving document with id " + po.getDocumentNumber(), e);
916             }
917 
918             return false;
919         }
920     }
921 
922     @Override
923     public void retransmitB2BPurchaseOrder(PurchaseOrderDocument po) {
924         if (completeB2BPurchaseOrder(po)) {
925             KNSGlobalVariables.getMessageList().add(PurapKeyConstants.B2B_PO_RETRANSMIT_SUCCESS);
926         } else {
927             GlobalVariables.getMessageMap().putError(OLEConstants.GLOBAL_ERRORS, PurapKeyConstants.B2B_PO_RETRANSMIT_FAILED);
928         }
929         purapService.saveDocumentNoValidation(po);
930     }
931 
932     @Override
933     public void completePurchaseOrderAmendment(PurchaseOrderDocument poa) {
934         LOG.debug("completePurchaseOrderAmendment() started");
935 
936         setCurrentAndPendingIndicatorsForApprovedPODocuments(poa);
937 
938         if (SpringContext.getBean(PaymentRequestService.class).hasActivePaymentRequestsForPurchaseOrder(poa.getPurapDocumentIdentifier())) {
939             poa.setPaymentRequestPositiveApprovalIndicator(true);
940             poa.setReceivingDocumentRequiredIndicator(false);
941         }
942         // check thresholds to see if receiving is required for purchase order amendment
943         if (!poa.isReceivingDocumentRequiredIndicator() &&
944                 !SpringContext.getBean(PaymentRequestService.class).hasActivePaymentRequestsForPurchaseOrder(poa.getPurapDocumentIdentifier())) {
945             setReceivingRequiredIndicatorForPurchaseOrder(poa);
946         }
947 
948         // if unordered items have been added to the PO then send an FYI to all fiscal officers
949         if (hasNewUnorderedItem(poa)) {
950             sendFyiForNewUnorderedItems(poa);
951         }
952         DocumentRouteHeaderValue routeHeader = ((RouteHeaderService) KEWServiceLocator.getService(KEWServiceLocator.DOC_ROUTE_HEADER_SRV)).getRouteHeader(poa.getDocumentNumber());
953         String status = routeHeader.getDocRouteStatus();
954         if (status.equals(KewApiConstants.ROUTE_HEADER_PROCESSED_CD)) {
955             List<PurApItem> items = poa.getItems();
956             for (PurApItem item : items) {
957                 initiateTransmission(poa, item);
958             }
959             //initiateTransmission(poa);
960         }
961 
962     }
963 
964     /**
965      * If there are commodity codes on the items on the PurchaseOrderDocument that haven't existed yet on the vendor that the
966      * PurchaseOrderDocument is using, then we will spawn a new VendorDetailMaintenanceDocument automatically to update the vendor
967      * with the commodity codes that aren't already existing on the vendor.
968      *
969      * @param po The PurchaseOrderDocument containing the vendor that we want to update.
970      */
971     @Override
972     public void updateVendorCommodityCode(PurchaseOrderDocument po) {
973         String noteText = "";
974         VendorDetail oldVendorDetail = po.getVendorDetail();
975         VendorDetail newVendorDetail = updateVendorWithMissingCommodityCodesIfNecessary(po);
976         if (newVendorDetail != null) {
977             try {
978                 // spawn a new vendor maintenance document to add the note
979                 MaintenanceDocument vendorMaintDoc = null;
980                 try {
981                     vendorMaintDoc = (MaintenanceDocument) documentService.getNewDocument("PVEN");
982                     vendorMaintDoc.getDocumentHeader().setDocumentDescription("Automatically spawned from PO");
983                     vendorMaintDoc.getOldMaintainableObject().setBusinessObject(oldVendorDetail);
984                     vendorMaintDoc.getNewMaintainableObject().setBusinessObject(newVendorDetail);
985                     vendorMaintDoc.getNewMaintainableObject().setMaintenanceAction(OLEConstants.MAINTENANCE_EDIT_ACTION);
986                     vendorMaintDoc.getNewMaintainableObject().setDocumentNumber(vendorMaintDoc.getDocumentNumber());
987                     boolean isVendorLocked = checkForLockingDocument(vendorMaintDoc);
988                     if (!isVendorLocked) {
989                         // validating vendor doc to capture exception before trying to route which if exception happens in
990                         // docService, then PO will fail too
991                         vendorMaintDoc.validateBusinessRules(new RouteDocumentEvent(vendorMaintDoc));
992                         addNoteForCommodityCodeToVendor(vendorMaintDoc.getNewMaintainableObject(), vendorMaintDoc.getDocumentNumber(), po.getPurapDocumentIdentifier());
993                         documentService.routeDocument(vendorMaintDoc, null, null);
994                     } else {
995                         // Add a note to the PO to tell the users that we can't automatically update the vendor because it's locked.
996                         noteText = "Unable to automatically update vendor because it is locked";
997                     }
998                 } catch (Exception e) {
999                     if (ObjectUtils.isNull(vendorMaintDoc)) {
1000                         noteText = "Unable to create a new VendorDetailMaintenanceDocument to update the vendor with new commodity codes";
1001                     } else {
1002                         noteText = "Unable to route a new VendorDetailMaintenanceDocument to update the vendor with new commodity codes";
1003                     }
1004                 } finally {
1005                     if (StringUtils.isNotBlank(noteText)) {
1006                         // update on purchase order notes
1007                         Note note = documentService.createNoteFromDocument(po, noteText);
1008                         po.addNote(note);
1009                         noteService.save(note);
1010                     }
1011                 }
1012             } catch (Exception e) {
1013                 LOG.error("updateVendorCommodityCode() unable to add a note(" + noteText + ") to PO document " + po.getDocumentNumber());
1014                 throw new RuntimeException(e);
1015             }
1016         }
1017     }
1018 
1019     /**
1020      * Creates a note to be added to the Vendor Maintenance Document which is spawned from the PurchaseOrderDocument.
1021      *
1022      * @param maintainable
1023      * @param documentNumber
1024      * @param poID
1025      */
1026     protected void addNoteForCommodityCodeToVendor(Maintainable maintainable, String documentNumber, Integer poID) {
1027         Note newBONote = new Note();
1028         newBONote.setNoteText("Change vendor document ID <" + documentNumber + ">. Document was automatically created from PO <" + poID + "> to add commodity codes used on this PO that were not yet assigned to this vendor.");
1029         try {
1030 
1031             newBONote = noteService.createNote(newBONote, maintainable.getBusinessObject(), GlobalVariables.getUserSession().getPrincipalId());
1032             newBONote.setNotePostedTimestampToCurrent();
1033         } catch (Exception e) {
1034             throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e);
1035         }
1036         List<Note> noteList = noteService.getByRemoteObjectId(maintainable.getBusinessObject().getObjectId());
1037         noteList.add(newBONote);
1038         noteService.saveNoteList(noteList);
1039     }
1040 
1041     /**
1042      * Checks whether the vendor is currently locked.
1043      *
1044      * @param document The MaintenanceDocument containing the vendor.
1045      * @return boolean true if the vendor is currently locked and false otherwise.
1046      */
1047     protected boolean checkForLockingDocument(MaintenanceDocument document) {
1048         String blockingDocId = maintenanceDocumentService.getLockingDocumentId(document);
1049         if (StringUtils.isBlank(blockingDocId)) {
1050             return false;
1051         } else {
1052             return true;
1053         }
1054     }
1055 
1056     /**
1057      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#updateVendorWithMissingCommodityCodesIfNecessary(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1058      */
1059     @Override
1060     public VendorDetail updateVendorWithMissingCommodityCodesIfNecessary(PurchaseOrderDocument po) {
1061         List<CommodityCode> result = new ArrayList<CommodityCode>();
1062         boolean foundDefault = false;
1063         VendorDetail vendor = (VendorDetail) ObjectUtils.deepCopy(po.getVendorDetail());
1064         for (PurchaseOrderItem item : (List<PurchaseOrderItem>) po.getItems()) {
1065             // Only check on commodity codes if the item is active and is above the line item type.
1066             if (item.getItemType().isLineItemIndicator() && item.isItemActiveIndicator()) {
1067                 CommodityCode cc = item.getCommodityCode();
1068                 if (cc != null && !result.contains(cc)) {
1069                     List<VendorCommodityCode> vendorCommodityCodes = po.getVendorDetail().getVendorCommodities();
1070                     boolean foundMatching = false;
1071                     for (VendorCommodityCode vcc : vendorCommodityCodes) {
1072                         if (vcc.getCommodityCode().getPurchasingCommodityCode().equals(cc.getPurchasingCommodityCode())) {
1073                             foundMatching = true;
1074                         }
1075                         if (!foundDefault && vcc.isCommodityDefaultIndicator()) {
1076                             foundDefault = true;
1077                         }
1078                     }
1079                     if (!foundMatching) {
1080                         result.add(cc);
1081                         VendorCommodityCode vcc = new VendorCommodityCode(vendor.getVendorHeaderGeneratedIdentifier(), vendor.getVendorDetailAssignedIdentifier(), cc, true);
1082                         vcc.setActive(true);
1083                         if (!foundDefault) {
1084                             vcc.setCommodityDefaultIndicator(true);
1085                             foundDefault = true;
1086                         }
1087                         vendor.getVendorCommodities().add(vcc);
1088                     }
1089                 }
1090             }
1091         }
1092         if (result.size() > 0) {
1093             // We also have to add to the old vendor detail's vendorCommodities if we're adding to the new
1094             // vendor detail's vendorCommodities.
1095             for (int i = 0; i < result.size(); i++) {
1096                 po.getVendorDetail().getVendorCommodities().add(new VendorCommodityCode());
1097             }
1098             return vendor;
1099         } else {
1100             return null;
1101         }
1102     }
1103 
1104     /**
1105      * Update the purchase order document with the appropriate status for pending first transmission based on the transmission type.
1106      *
1107      * @param po The purchase order document whose status to be updated.
1108      */
1109     protected void setupDocumentForPendingFirstTransmission(PurchaseOrderDocument po) {
1110         if (POTransmissionMethods.PRINT.equals(po.getPurchaseOrderTransmissionMethodCode()) || POTransmissionMethods.FAX.equals(po.getPurchaseOrderTransmissionMethodCode()) || POTransmissionMethods.ELECTRONIC.equals(po.getPurchaseOrderTransmissionMethodCode())) {
1111             String newStatusCode = PurchaseOrderStatuses.STATUSES_BY_TRANSMISSION_TYPE.get(po.getPurchaseOrderTransmissionMethodCode());
1112             if (LOG.isDebugEnabled()) {
1113                 LOG.debug("setupDocumentForPendingFirstTransmission() Purchase Order Transmission Type is '" + po.getPurchaseOrderTransmissionMethodCode() + "' setting status to '" + newStatusCode + "'");
1114             }
1115             try {
1116                 po.updateAndSaveAppDocStatus(newStatusCode);
1117             } catch (WorkflowException e) {
1118                 throw new RuntimeException("Error saving routing data while saving document with id " + po.getDocumentNumber(), e);
1119             }
1120         }
1121     }
1122 
1123     /**
1124      * If the status of the purchase order is not OPEN and the initial open date is null, sets the initial open date to current date
1125      * and update the status to OPEN, then save the purchase order.
1126      *
1127      * @param po The purchase order document whose initial open date and status we want to update.
1128      */
1129     protected void attemptSetupOfInitialOpenOfDocument(PurchaseOrderDocument po) {
1130         if (LOG.isInfoEnabled()) {
1131             LOG.info("attemptSetupOfInitialOpenOfDocument() started using document with doc id " + po.getDocumentNumber());
1132         }
1133 
1134         if (!PurchaseOrderStatuses.APPDOC_OPEN.equals(po.getApplicationDocumentStatus())) {
1135             if (ObjectUtils.isNull(po.getPurchaseOrderInitialOpenTimestamp())) {
1136                 LOG.debug("attemptSetupOfInitialOpenOfDocument() setting initial open date on document");
1137                 po.setPurchaseOrderInitialOpenTimestamp(dateTimeService.getCurrentTimestamp());
1138             } else {
1139                 throw new RuntimeException("Document does not have status code '" + PurchaseOrderStatuses.APPDOC_OPEN + "' on it but value of initial open date is " + po.getPurchaseOrderInitialOpenTimestamp());
1140             }
1141             LOG.info("attemptSetupOfInitialOpenOfDocument() Setting po document id " + po.getDocumentNumber() + " status from '" + po.getApplicationDocumentStatus() + "' to '" + PurchaseOrderStatuses.APPDOC_OPEN + "'");
1142             try {
1143                 po.updateAndSaveAppDocStatus(PurchaseOrderStatuses.APPDOC_OPEN);
1144             } catch (WorkflowException we) {
1145                 throw new RuntimeException("Unable to load a WorkflowDocument object for " + po.getDocumentNumber(), we);
1146             }
1147             po.setApplicationDocumentStatus(PurchaseOrderStatuses.APPDOC_OPEN);
1148             // no need to save here because calling class should handle the save if needed
1149         }
1150 
1151         // Modified for jira OLE-2478
1152         /*else {
1153             LOG.error("attemptSetupOfInitialOpenOfDocument() Found document already in '" + PurchaseOrderStatuses.OPEN + "' status for PO#" + po.getPurapDocumentIdentifier() + "; will not change or update");
1154         }*/
1155     }
1156 
1157     /**
1158      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#getCurrentPurchaseOrder(java.lang.Integer)
1159      */
1160     @Override
1161     public PurchaseOrderDocument getCurrentPurchaseOrder(Integer id) {
1162         return getPurchaseOrderByDocumentNumber(purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(id));
1163         // TODO hjs: code review (why is this DB call so complicated? wouldn't this method be cleaner and less db calls?)
1164         // return purchaseOrderDao.getCurrentPurchaseOrder(id);
1165     }
1166 
1167     /**
1168      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#getPurchaseOrderByDocumentNumber(java.lang.String)
1169      */
1170     @Override
1171     public PurchaseOrderDocument getPurchaseOrderByDocumentNumber(String documentNumber) {
1172         if (ObjectUtils.isNotNull(documentNumber)) {
1173             try {
1174                 PurchaseOrderDocument doc = (PurchaseOrderDocument) documentService.getByDocumentHeaderId(documentNumber);
1175                 if (ObjectUtils.isNotNull(doc)) {
1176                     WorkflowDocument workflowDocument = doc.getDocumentHeader().getWorkflowDocument();
1177                     doc.refreshReferenceObject(OLEPropertyConstants.DOCUMENT_HEADER);
1178                     doc.getDocumentHeader().setWorkflowDocument(workflowDocument);
1179                 }
1180                 return doc;
1181             } catch (WorkflowException e) {
1182                 String errorMessage = "Error getting purchase order document from document service";
1183                 LOG.error("getPurchaseOrderByDocumentNumber() " + errorMessage, e);
1184                 throw new RuntimeException(errorMessage, e);
1185             }
1186         }
1187         return null;
1188     }
1189 
1190     /**
1191      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#getOldestPurchaseOrder(org.kuali.ole.module.purap.document.PurchaseOrderDocument,
1192      *      org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1193      */
1194     @Override
1195     public PurchaseOrderDocument getOldestPurchaseOrder(PurchaseOrderDocument po, PurchaseOrderDocument documentBusinessObject) {
1196         LOG.debug("entering getOldestPO(PurchaseOrderDocument)");
1197         if (ObjectUtils.isNotNull(po)) {
1198             String oldestDocumentNumber = purchaseOrderDao.getOldestPurchaseOrderDocumentNumber(po.getPurapDocumentIdentifier());
1199             // OLEMI-9746 -- See Harsha's comments...
1200             if (StringUtils.isBlank(oldestDocumentNumber)) {
1201                 return null;
1202             }
1203             if (StringUtils.equals(oldestDocumentNumber, po.getDocumentNumber())) {
1204                 // manually set bo notes - this is mainly done for performance reasons (preferably we could call
1205                 // retrieve doc notes in PersistableBusinessObjectBase but that is protected)
1206                 updateNotes(po, documentBusinessObject);
1207                 LOG.debug("exiting getOldestPO(PurchaseOrderDocument)");
1208                 return po;
1209             } else {
1210                 PurchaseOrderDocument oldestPurchaseOrder = getPurchaseOrderByDocumentNumber(oldestDocumentNumber);
1211                 updateNotes(oldestPurchaseOrder, documentBusinessObject);
1212                 LOG.debug("exiting getOldestPO(PurchaseOrderDocument)");
1213                 return oldestPurchaseOrder;
1214             }
1215         }
1216         return null;
1217     }
1218 
1219     /**
1220      * If the purchase order's object id is not null (I think this means if it's an existing purchase order that had already been
1221      * saved to the db previously), get the notes of the purchase order from the database, fix the notes' fields by calling the
1222      * fixDbNoteFields, then set the notes to the purchase order. Otherwise (I think this means if it's a new purchase order), set
1223      * the notes of this purchase order to be the notes of the documentBusinessObject.
1224      *
1225      * @param po                     The current purchase order.
1226      * @param documentBusinessObject The oldest purchase order whose purapDocumentIdentifier is the same as the po's
1227      *                               purapDocumentIdentifier.
1228      */
1229     protected void updateNotes(PurchaseOrderDocument po, PurchaseOrderDocument documentBusinessObject) {
1230         if (ObjectUtils.isNotNull(documentBusinessObject)) {
1231             if (ObjectUtils.isNotNull(po.getObjectId())) {
1232                 List<Note> dbNotes = noteService.getByRemoteObjectId(po.getObjectId());
1233                 // need to set fields that are not ojb managed (i.e. the notes on the documentBusinessObject may have been modified
1234                 // independently of the ones in the db)
1235                 fixDbNoteFields(documentBusinessObject, dbNotes);
1236                 po.setNotes(dbNotes);
1237             } else {
1238                 po.setNotes(documentBusinessObject.getNotes());
1239             }
1240         }
1241     }
1242 
1243     /**
1244      * This method fixes non ojb managed missing fields from the db
1245      *
1246      * @param documentBusinessObject The oldest purchase order whose purapDocumentIdentifier is the same as the po's
1247      *                               purapDocumentIdentifier.
1248      * @param dbNotes                The notes of the purchase order obtained from the database.
1249      */
1250     protected void fixDbNoteFields(PurchaseOrderDocument documentBusinessObject, List<Note> dbNotes) {
1251         for (int i = 0; i < dbNotes.size(); i++) {
1252             Note dbNote = dbNotes.get(i);
1253             List<Note> currentNotes = documentBusinessObject.getNotes();
1254             if (i < currentNotes.size()) {
1255                 Note currentNote = (currentNotes).get(i);
1256                 // set the fyi from the current note if not empty
1257                 AdHocRouteRecipient fyiNoteRecipient = currentNote.getAdHocRouteRecipient();
1258                 if (ObjectUtils.isNotNull(fyiNoteRecipient)) {
1259                     dbNote.setAdHocRouteRecipient(fyiNoteRecipient);
1260                 }
1261             }
1262         }
1263     }
1264 
1265     /**
1266      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#getPurchaseOrderNotes(java.lang.Integer)
1267      */
1268     @Override
1269     public List<Note> getPurchaseOrderNotes(Integer id) {
1270         List<Note> notes = new ArrayList<Note>();
1271 
1272         PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(purchaseOrderDao.getOldestPurchaseOrderDocumentNumber(id));
1273         if (ObjectUtils.isNotNull(po)) {
1274             notes = noteService.getByRemoteObjectId(po.getObjectId());
1275         }
1276         return notes;
1277     }
1278 
1279     /**
1280      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForApprovedPODocuments(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1281      */
1282     @Override
1283     public void setCurrentAndPendingIndicatorsForApprovedPODocuments(PurchaseOrderDocument newPO) {
1284 
1285         // if(newPO.getPurapDocumentIdentifier() == null)
1286         // return;
1287 
1288         // Get the "current PO" that's in the database, i.e. the PO row that contains current indicator = Y
1289         PurchaseOrderDocument oldPO = getCurrentPurchaseOrder(newPO.getPurapDocumentIdentifier());
1290 
1291         if (oldPO == null) {
1292             return;
1293         }
1294 
1295         // If the document numbers between the oldPO and the newPO are different, then this is a PO change document.
1296         if (!oldPO.getDocumentNumber().equals(newPO.getDocumentNumber())) {
1297             // First, we set the indicators for the oldPO to : Current = N and Pending = N
1298             oldPO.setPurchaseOrderCurrentIndicator(false);
1299             oldPO.setPendingActionIndicator(false);
1300 
1301             // set the status and status history of the oldPO to retired version
1302             try {
1303                 oldPO.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_RETIRED_VERSION);
1304             } catch (WorkflowException e) {
1305                 throw new RuntimeException("Error saving routing data while saving document with id " + oldPO.getDocumentNumber(), e);
1306             }
1307 
1308             saveDocumentNoValidationUsingClearMessageMap(oldPO);
1309         }
1310         if (!oldPO.getVendorDetail().getVendorHeaderGeneratedIdentifier().equals(newPO.getVendorDetail().getVendorHeaderGeneratedIdentifier())) {
1311             List<PurApItem> items = oldPO.getItems();
1312             for (PurApItem item : items) {
1313                 initiateTransmission(oldPO, item);
1314             }
1315             // initiateTransmission(oldPO);
1316         }
1317         // Now, we set the "new PO" indicators so that Current = Y and Pending = N
1318         newPO.setPurchaseOrderCurrentIndicator(true);
1319         newPO.setPendingActionIndicator(false);
1320 
1321         // this was never being saved. Should call the method to save the newPO.
1322         // saveDocumentNoValidationUsingClearMessageMap(newPO);
1323     }
1324 
1325     /**
1326      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedChangePODocuments(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1327      */
1328     @Override
1329     public void setCurrentAndPendingIndicatorsForDisapprovedChangePODocuments(PurchaseOrderDocument newPO) {
1330         updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN);
1331     }
1332 
1333     /**
1334      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledChangePODocuments(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1335      */
1336     @Override
1337     public void setCurrentAndPendingIndicatorsForCancelledChangePODocuments(PurchaseOrderDocument newPO) {
1338         updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN);
1339     }
1340 
1341     /**
1342      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledReopenPODocuments(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1343      */
1344     @Override
1345     public void setCurrentAndPendingIndicatorsForCancelledReopenPODocuments(PurchaseOrderDocument newPO) {
1346         updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED);
1347     }
1348 
1349     /**
1350      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedReopenPODocuments(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1351      */
1352     @Override
1353     public void setCurrentAndPendingIndicatorsForDisapprovedReopenPODocuments(PurchaseOrderDocument newPO) {
1354         updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED);
1355     }
1356 
1357     /**
1358      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledRemoveHoldPODocuments(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1359      */
1360     @Override
1361     public void setCurrentAndPendingIndicatorsForCancelledRemoveHoldPODocuments(PurchaseOrderDocument newPO) {
1362         updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_PAYMENT_HOLD);
1363     }
1364 
1365     /**
1366      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedRemoveHoldPODocuments(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1367      */
1368     @Override
1369     public void setCurrentAndPendingIndicatorsForDisapprovedRemoveHoldPODocuments(PurchaseOrderDocument newPO) {
1370         updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_PAYMENT_HOLD);
1371     }
1372 
1373     /**
1374      * Update the statuses of both the old purchase order and the new purchase orders, then save the old and the new purchase
1375      * orders.
1376      *
1377      * @param newPO       The new change purchase order document (e.g. the PurchaseOrderAmendmentDocument that was resulted from the user
1378      *                    clicking on the amend button).
1379      * @param newPOStatus The status to be set on the new change purchase order document.
1380      * @param oldPOStatus The status to be set on the existing (old) purchase order document.
1381      */
1382     protected void updateCurrentDocumentForNoPendingAction(PurchaseOrderDocument newPO, String newPOStatus, String oldPOStatus) {
1383         // Get the "current PO" that's in the database, i.e. the PO row that contains current indicator = Y
1384         PurchaseOrderDocument oldPO = getCurrentPurchaseOrder(newPO.getPurapDocumentIdentifier());
1385         // Set the Pending indicator for the oldPO to N
1386         oldPO.setPendingActionIndicator(false);
1387         try {
1388             oldPO.updateAndSaveAppDocStatus(oldPOStatus);
1389             newPO.updateAndSaveAppDocStatus(newPOStatus);
1390         } catch (WorkflowException e) {
1391             throw new RuntimeException("Error saving routing data while saving document", e);
1392         }
1393 
1394         // savePurchaseOrderData(oldPO);
1395         saveDocumentNoValidationUsingClearMessageMap(oldPO);
1396         saveDocumentNoValidationUsingClearMessageMap(newPO);
1397     }
1398 
1399     @Override
1400     public List<PurchaseOrderQuoteStatus> getPurchaseOrderQuoteStatusCodes() {
1401         List<PurchaseOrderQuoteStatus> poQuoteStatuses = new ArrayList<PurchaseOrderQuoteStatus>();
1402         poQuoteStatuses = (List<PurchaseOrderQuoteStatus>) businessObjectService.findAll(PurchaseOrderQuoteStatus.class);
1403         return poQuoteStatuses;
1404     }
1405 
1406     @Override
1407     public void setReceivingRequiredIndicatorForPurchaseOrder(PurchaseOrderDocument po) {
1408         ThresholdHelper thresholdHelper = new ThresholdHelper(po);
1409         boolean result = thresholdHelper.isReceivingDocumentRequired();
1410         if (result) {
1411             ThresholdSummary thresholdSummary = thresholdHelper.getThresholdSummary();
1412             ReceivingThreshold receivingThreshold = thresholdHelper.getReceivingThreshold();
1413             po.setReceivingDocumentRequiredIndicator(true);
1414 
1415             String notetxt = "Receiving is set to be required because the threshold summary with a total amount of " + thresholdSummary.getTotalAmount();
1416             notetxt += " exceeds the receiving threshold of " + receivingThreshold.getThresholdAmount();
1417             notetxt += " with respect to the threshold criteria ";
1418 
1419             if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART) {
1420                 notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1421             } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_ACCOUNTTYPE) {
1422                 notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1423                 notetxt += " - Account Type " + receivingThreshold.getAccountTypeCode();
1424             } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_SUBFUND) {
1425                 notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1426                 notetxt += " - Sub-Fund " + receivingThreshold.getSubFundGroupCode();
1427             } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_COMMODITYCODE) {
1428                 notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1429                 notetxt += " - Commodity Code " + receivingThreshold.getPurchasingCommodityCode();
1430             } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_OBJECTCODE) {
1431                 notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1432                 notetxt += " - Object code " + receivingThreshold.getFinancialObjectCode();
1433             } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_ORGANIZATIONCODE) {
1434                 notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1435                 notetxt += " - Organization " + receivingThreshold.getOrganizationCode();
1436             } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_VENDOR) {
1437                 notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1438                 notetxt += " - Vendor " + receivingThreshold.getVendorNumber();
1439             }
1440 
1441             try {
1442                 Note note = documentService.createNoteFromDocument(po, notetxt);
1443 //                documentService.addNoteToDocument(po, note);
1444                 noteService.save(note);
1445             } catch (Exception e) {
1446                 throw new RuntimeException(e);
1447             }
1448         }
1449     }
1450 
1451     /**
1452      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#hasNewUnorderedItem(org.kuali.ole.module.purap.document.PurchaseOrderDocument)
1453      */
1454     @Override
1455     public boolean hasNewUnorderedItem(PurchaseOrderDocument po) {
1456 
1457         boolean itemAdded = false;
1458 
1459         for (PurchaseOrderItem poItem : (List<PurchaseOrderItem>) po.getItems()) {
1460             // only check, active, above the line, unordered items
1461             if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode())) {
1462 
1463                 // if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1464                 if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(po.getPurapDocumentIdentifier()))) {
1465                     itemAdded = true;
1466                     break;
1467                 }
1468             }
1469         }
1470 
1471         return itemAdded;
1472     }
1473 
1474     @Override
1475     public boolean isNewUnorderedItem(PurchaseOrderItem poItem) {
1476 
1477         boolean itemAdded = false;
1478 
1479         // only check, active, above the line, unordered items
1480         if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode())) {
1481 
1482             // if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1483             if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(poItem.getPurchaseOrder().getPurapDocumentIdentifier()))) {
1484                 itemAdded = true;
1485             }
1486         }
1487 
1488         return itemAdded;
1489     }
1490 
1491     @Override
1492     public boolean isNewItemForAmendment(PurchaseOrderItem poItem) {
1493 
1494         boolean itemAdded = false;
1495 
1496         // only check, active, above the line, unordered items
1497         if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator()) {
1498 
1499             // if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1500             if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(poItem.getPurchaseOrder().getPurapDocumentIdentifier()))) {
1501                 itemAdded = true;
1502             }
1503         }
1504 
1505         return itemAdded;
1506     }
1507 
1508     /**
1509      * Sends an FYI to fiscal officers for new unordered items.
1510      *
1511      * @param po
1512      */
1513     protected void sendFyiForNewUnorderedItems(PurchaseOrderDocument po) {
1514 
1515         List<AdHocRoutePerson> fyiList = createFyiFiscalOfficerListForNewUnorderedItems(po);
1516         String annotation = "Notification of New Unordered Items for Purchase Order" + po.getPurapDocumentIdentifier() + "(document id " + po.getDocumentNumber() + ")";
1517         String responsibilityNote = "Purchase Order Amendment Routed By User";
1518 
1519         for (AdHocRoutePerson adHocPerson : fyiList) {
1520             try {
1521                 po.appSpecificRouteDocumentToUser(
1522                         po.getDocumentHeader().getWorkflowDocument(),
1523                         adHocPerson.getPerson().getPrincipalId(),
1524                         annotation,
1525                         responsibilityNote);
1526             } catch (WorkflowException e) {
1527                 throw new RuntimeException("Error routing fyi for document with id " + po.getDocumentNumber(), e);
1528             }
1529 
1530         }
1531     }
1532 
1533     /**
1534      * Creates a list of fiscal officers for new unordered items added to a purchase order.
1535      *
1536      * @param po
1537      * @return
1538      */
1539     protected List<AdHocRoutePerson> createFyiFiscalOfficerListForNewUnorderedItems(PurchaseOrderDocument po) {
1540 
1541         List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
1542         Map fiscalOfficers = new HashMap();
1543         AdHocRoutePerson adHocRoutePerson = null;
1544 
1545         for (PurchaseOrderItem poItem : (List<PurchaseOrderItem>) po.getItems()) {
1546             // only check, active, above the line, unordered items
1547             if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode())) {
1548 
1549                 // if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1550                 if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(po.getPurapDocumentIdentifier()))) {
1551 
1552                     // loop through accounts and pull off fiscal officer
1553                     for (PurApAccountingLine account : poItem.getSourceAccountingLines()) {
1554 
1555                         // check for dupes of fiscal officer
1556                         if (fiscalOfficers.containsKey(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName()) == false) {
1557 
1558                             // add fiscal officer to list
1559                             fiscalOfficers.put(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName(), account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
1560 
1561                             // create AdHocRoutePerson object and add to list
1562                             adHocRoutePerson = new AdHocRoutePerson();
1563                             adHocRoutePerson.setId(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
1564                             adHocRoutePerson.setActionRequested(OLEConstants.WORKFLOW_FYI_REQUEST);
1565                             adHocRoutePersons.add(adHocRoutePerson);
1566                         }
1567                     }
1568                 }
1569             }
1570         }
1571 
1572         return adHocRoutePersons;
1573     }
1574 
1575     /**
1576      * Sends an FYI to fiscal officers for general ledger entries created for amend purchase order
1577      *
1578      * @param po
1579      */
1580     @Override
1581     public void sendFyiForGLEntries(PurchaseOrderDocument po) {
1582 
1583         List<AdHocRoutePerson> fyiList = createFyiFiscalOfficerListForAmendGlEntries(po);
1584         String annotation = "Amendment to Purchase Order " + po.getPurapDocumentIdentifier() + "( Document id " + po.getDocumentNumber() + ")" +
1585                 " resulted in the generation of Pending General Ledger Entries.";
1586         String responsibilityNote = "Purchase Order Amendment Routed By User";
1587 
1588         for (AdHocRoutePerson adHocPerson : fyiList) {
1589             try {
1590                 po.appSpecificRouteDocumentToUser(
1591                         po.getDocumentHeader().getWorkflowDocument(),
1592                         adHocPerson.getPerson().getPrincipalId(),
1593                         annotation,
1594                         responsibilityNote);
1595             } catch (WorkflowException e) {
1596                 throw new RuntimeException("Error routing fyi for document with id " + po.getDocumentNumber(), e);
1597             }
1598 
1599         }
1600     }
1601 
1602     /**
1603      * Creates a list of fiscal officers for amend genera
1604      *
1605      * @param po
1606      * @return
1607      */
1608     protected List<AdHocRoutePerson> createFyiFiscalOfficerListForAmendGlEntries(PurchaseOrderDocument po) {
1609 
1610         List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
1611         Map fiscalOfficers = new HashMap();
1612         AdHocRoutePerson adHocRoutePerson = null;
1613 
1614         for (SourceAccountingLine account : po.getGlOnlySourceAccountingLines()) {
1615             // loop through accounts and pull off fiscal officer
1616             // for(PurApAccountingLine account : poItem.getSourceAccountingLines()){
1617             // check for dupes of fiscal officer
1618             Account acct = SpringContext.getBean(AccountService.class).getByPrimaryId(account.getChartOfAccountsCode(),
1619                     account.getAccountNumber());
1620             String principalName = acct.getAccountFiscalOfficerUser().getPrincipalName();
1621             // String principalName = account.getAccount().getAccountFiscalOfficerUser().getPrincipalName();
1622             if (fiscalOfficers.containsKey(principalName) == false) {
1623                 // add fiscal officer to list
1624                 fiscalOfficers.put(principalName, principalName);
1625                 // create AdHocRoutePerson object and add to list
1626                 adHocRoutePerson = new AdHocRoutePerson();
1627                 adHocRoutePerson.setId(principalName);
1628                 adHocRoutePerson.setActionRequested(KewApiConstants.ACTION_REQUEST_FYI_REQ);
1629                 adHocRoutePersons.add(adHocRoutePerson);
1630             }
1631             // }
1632         }
1633 
1634         return adHocRoutePersons;
1635     }
1636 
1637     /**
1638      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#categorizeItemsForSplit(java.util.List)
1639      */
1640     @Override
1641     public HashMap<String, List<PurchaseOrderItem>> categorizeItemsForSplit(List<PurchaseOrderItem> items) {
1642         HashMap<String, List<PurchaseOrderItem>> movingOrNot = new HashMap<String, List<PurchaseOrderItem>>(3);
1643         List<PurchaseOrderItem> movingPOItems = new ArrayList<PurchaseOrderItem>();
1644         List<PurchaseOrderItem> remainingPOItems = new ArrayList<PurchaseOrderItem>();
1645         List<PurchaseOrderItem> remainingPOLineItems = new ArrayList<PurchaseOrderItem>();
1646         for (PurchaseOrderItem item : items) {
1647             if (item.isMovingToSplit()) {
1648                 movingPOItems.add(item);
1649             } else {
1650                 remainingPOItems.add(item);
1651                 if (item.getItemType().isLineItemIndicator()) {
1652                     remainingPOLineItems.add(item);
1653                 }
1654             }
1655         }
1656         movingOrNot.put(PODocumentsStrings.ITEMS_MOVING_TO_SPLIT, movingPOItems);
1657         movingOrNot.put(PODocumentsStrings.ITEMS_REMAINING, remainingPOItems);
1658         movingOrNot.put(PODocumentsStrings.LINE_ITEMS_REMAINING, remainingPOLineItems);
1659         return movingOrNot;
1660     }
1661 
1662     /**
1663      * @see org.kuali.module.purap.service.PurchaseOrderService#populateQuoteWithVendor(java.lang.Integer, java.lang.Integer,
1664      *      java.lang.String)
1665      */
1666     @Override
1667     public PurchaseOrderVendorQuote populateQuoteWithVendor(Integer headerId, Integer detailId, String documentNumber) {
1668         VendorDetail vendor = vendorService.getVendorDetail(headerId, detailId);
1669         updateDefaultVendorAddress(vendor);
1670         PurchaseOrderVendorQuote newPOVendorQuote = populateAddressForPOVendorQuote(vendor, documentNumber);
1671 
1672         // Set the vendorPhoneNumber on the quote to be the first "phone number" type phone
1673         // found on the list. If there's no "phone number" type found, the quote's
1674         // vendorPhoneNumber will be blank regardless of any other types of phone found on the list.
1675         for (VendorPhoneNumber phone : vendor.getVendorPhoneNumbers()) {
1676             if (VendorConstants.PhoneTypes.PHONE.equals(phone.getVendorPhoneTypeCode())) {
1677                 newPOVendorQuote.setVendorPhoneNumber(phone.getVendorPhoneNumber());
1678                 break;
1679             }
1680         }
1681 
1682         return newPOVendorQuote;
1683     }
1684 
1685     /**
1686      * Creates the new PurchaseOrderVendorQuote and populate the address fields for it.
1687      *
1688      * @param newVendor      The VendorDetail object from which we obtain the values for the address fields.
1689      * @param documentNumber The documentNumber of the PurchaseOrderDocument containing the PurchaseOrderVendorQuote.
1690      * @return
1691      */
1692     protected PurchaseOrderVendorQuote populateAddressForPOVendorQuote(VendorDetail newVendor, String documentNumber) {
1693         PurchaseOrderVendorQuote newPOVendorQuote = new PurchaseOrderVendorQuote();
1694         newPOVendorQuote.setVendorName(newVendor.getVendorName());
1695         newPOVendorQuote.setVendorHeaderGeneratedIdentifier(newVendor.getVendorHeaderGeneratedIdentifier());
1696         newPOVendorQuote.setVendorDetailAssignedIdentifier(newVendor.getVendorDetailAssignedIdentifier());
1697         newPOVendorQuote.setDocumentNumber(documentNumber);
1698         boolean foundAddress = false;
1699         for (VendorAddress address : newVendor.getVendorAddresses()) {
1700             if (AddressTypes.QUOTE.equals(address.getVendorAddressTypeCode())) {
1701                 newPOVendorQuote.setVendorCityName(address.getVendorCityName());
1702                 newPOVendorQuote.setVendorCountryCode(address.getVendorCountryCode());
1703                 newPOVendorQuote.setVendorLine1Address(address.getVendorLine1Address());
1704                 newPOVendorQuote.setVendorLine2Address(address.getVendorLine2Address());
1705                 newPOVendorQuote.setVendorPostalCode(address.getVendorZipCode());
1706                 newPOVendorQuote.setVendorStateCode(address.getVendorStateCode());
1707                 newPOVendorQuote.setVendorFaxNumber(address.getVendorFaxNumber());
1708                 foundAddress = true;
1709                 break;
1710             }
1711         }
1712         if (!foundAddress) {
1713             newPOVendorQuote.setVendorCityName(newVendor.getDefaultAddressCity());
1714             newPOVendorQuote.setVendorCountryCode(newVendor.getDefaultAddressCountryCode());
1715             newPOVendorQuote.setVendorLine1Address(newVendor.getDefaultAddressLine1());
1716             newPOVendorQuote.setVendorLine2Address(newVendor.getDefaultAddressLine2());
1717             newPOVendorQuote.setVendorPostalCode(newVendor.getDefaultAddressPostalCode());
1718             newPOVendorQuote.setVendorStateCode(newVendor.getDefaultAddressStateCode());
1719             newPOVendorQuote.setVendorFaxNumber(newVendor.getDefaultFaxNumber());
1720         }
1721         return newPOVendorQuote;
1722     }
1723 
1724     /**
1725      * Obtains the defaultAddress of the vendor and setting the default address fields on the vendor.
1726      *
1727      * @param vendor The VendorDetail object whose default address we'll obtain and set the fields.
1728      */
1729     protected void updateDefaultVendorAddress(VendorDetail vendor) {
1730         VendorAddress defaultAddress = vendorService.getVendorDefaultAddress(vendor.getVendorAddresses(), vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode(), "");
1731         if (defaultAddress != null) {
1732             if (defaultAddress.getVendorState() != null) {
1733                 vendor.setVendorStateForLookup(defaultAddress.getVendorState().getName());
1734             }
1735             vendor.setDefaultAddressLine1(defaultAddress.getVendorLine1Address());
1736             vendor.setDefaultAddressLine2(defaultAddress.getVendorLine2Address());
1737             vendor.setDefaultAddressCity(defaultAddress.getVendorCityName());
1738             vendor.setDefaultAddressPostalCode(defaultAddress.getVendorZipCode());
1739             vendor.setDefaultAddressStateCode(defaultAddress.getVendorStateCode());
1740             vendor.setDefaultAddressInternationalProvince(defaultAddress.getVendorAddressInternationalProvinceName());
1741             vendor.setDefaultAddressCountryCode(defaultAddress.getVendorCountryCode());
1742             vendor.setDefaultFaxNumber(defaultAddress.getVendorFaxNumber());
1743         }
1744     }
1745 
1746     /**
1747      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#processACMReq(org.kuali.ole.module.purap.document.ContractManagerAssignmentDocument)
1748      */
1749     @Override
1750     public void processACMReq(ContractManagerAssignmentDocument acmDoc) {
1751         List<ContractManagerAssignmentDetail> acmDetails = acmDoc.getContractManagerAssignmentDetails();
1752         for (Object element : acmDetails) {
1753             ContractManagerAssignmentDetail detail = (ContractManagerAssignmentDetail) element;
1754 
1755             if (ObjectUtils.isNotNull(detail.getContractManagerCode())) {
1756                 // Get the requisition for this ContractManagerAssignmentDetail.
1757                 RequisitionDocument req = requisitionService.getRequisitionById(detail.getRequisitionIdentifier());
1758 
1759                 if (PurapConstants.RequisitionStatuses.APPDOC_AWAIT_CONTRACT_MANAGER_ASSGN.equals(req.getApplicationDocumentStatus())) {
1760                     // only update REQ if code is empty and status is correct
1761                     try {
1762                         req.updateAndSaveAppDocStatus(PurapConstants.RequisitionStatuses.APPDOC_CLOSED);
1763                     } catch (WorkflowException e) {
1764                         throw new RuntimeException("Error saving routing data while saving document with id " + req.getDocumentNumber(), e);
1765                     }
1766 
1767                     purapService.saveDocumentNoValidation(req);
1768                     createPurchaseOrderDocument(req, OLEConstants.SYSTEM_USER, detail.getContractManagerCode());
1769                 }
1770             }
1771 
1772         }// endfor
1773     }
1774 
1775     /**
1776      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#autoCloseFullyDisencumberedOrders()
1777      */
1778     @Override
1779     public boolean autoCloseFullyDisencumberedOrders() {
1780         LOG.debug("autoCloseFullyDisencumberedOrders() started");
1781 
1782         List<AutoClosePurchaseOrderView> autoCloseList = purchaseOrderDao.getAllOpenPurchaseOrders(getExcludedVendorChoiceCodes());
1783 
1784         //we need to eliminate the AutoClosePurchaseOrderView whose workflowdocument status is not OPEN..
1785         //KFSMI-7533
1786         List<AutoClosePurchaseOrderView> purchaseOrderAutoCloseList = filterDocumentsForAppDocStatusOpen(autoCloseList);
1787 
1788         for (AutoClosePurchaseOrderView poAutoClose : purchaseOrderAutoCloseList) {
1789             if ((poAutoClose.getTotalAmount() != null) && ((KualiDecimal.ZERO.compareTo(poAutoClose.getTotalAmount())) != 0)) {
1790                 if (LOG.isDebugEnabled()) {
1791                     LOG.debug("autoCloseFullyDisencumberedOrders() PO ID " + poAutoClose.getPurapDocumentIdentifier() + " with total " + poAutoClose.getTotalAmount().doubleValue() + " will be closed");
1792                 }
1793                 String newStatus = PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_CLOSE;
1794                 String annotation = "This PO was automatically closed in batch.";
1795                 String documentType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT;
1796                 PurchaseOrderDocument document = getPurchaseOrderByDocumentNumber(poAutoClose.getDocumentNumber());
1797                 createNoteForAutoCloseOrders(document, annotation);
1798                 createAndRoutePotentialChangeDocument(poAutoClose.getDocumentNumber(), documentType, annotation, null, newStatus);
1799 
1800             }
1801         }
1802         LOG.debug("autoCloseFullyDisencumberedOrders() ended");
1803 
1804         return true;
1805     }
1806 
1807     /**
1808      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#autoCloseRecurringOrders()
1809      */
1810     @Override
1811     public boolean autoCloseRecurringOrders() {
1812         LOG.debug("autoCloseRecurringOrders() started");
1813         boolean shouldSendEmail = true;
1814         MailMessage message = new MailMessage();
1815         String parameterEmail = parameterService.getParameterValueAsString(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_TO_EMAIL_ADDRESSES);
1816 
1817         if (StringUtils.isEmpty(parameterEmail)) {
1818             // Don't stop the show if the email address is wrong, log it and continue.
1819             LOG.warn("autoCloseRecurringOrders(): parameterEmail is missing, we'll not send out any emails for this job.");
1820             shouldSendEmail = false;
1821         }
1822         if (shouldSendEmail) {
1823             message = setMessageAddressesAndSubject(message, parameterEmail);
1824         }
1825         StringBuffer emailBody = new StringBuffer();
1826         // There should always be a "AUTO_CLOSE_RECURRING_ORDER_DT"
1827         // row in the table, this method sets it to "mm/dd/yyyy" after processing.
1828         String recurringOrderDateString = parameterService.getParameterValueAsString(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_DATE);
1829         boolean validDate = true;
1830         java.util.Date recurringOrderDate = null;
1831         try {
1832             recurringOrderDate = dateTimeService.convertToDate(recurringOrderDateString);
1833         } catch (ParseException pe) {
1834             validDate = false;
1835         }
1836         if (StringUtils.isEmpty(recurringOrderDateString) || recurringOrderDateString.equalsIgnoreCase("mm/dd/yyyy") || (!validDate)) {
1837             if (recurringOrderDateString.equalsIgnoreCase("mm/dd/yyyy")) {
1838                 LOG.debug("autoCloseRecurringOrders(): mm/dd/yyyy " + "was found in the Application Settings table. No orders will be closed, method will end.");
1839                 if (shouldSendEmail) {
1840                     emailBody.append("The AUTO_CLOSE_RECURRING_ORDER_DT found in the Application Settings table " + "was mm/dd/yyyy. No recurring PO's were closed.");
1841                 }
1842             } else {
1843                 if (LOG.isDebugEnabled()) {
1844                     LOG.debug("autoCloseRecurringOrders(): An invalid autoCloseRecurringOrdersDate " + "was found in the Application Settings table: " + recurringOrderDateString + ". Method will end.");
1845                 }
1846                 if (shouldSendEmail) {
1847                     emailBody.append("An invalid AUTO_CLOSE_RECURRING_ORDER_DT was found in the Application Settings table: " + recurringOrderDateString + ". No recurring PO's were closed.");
1848                 }
1849             }
1850             if (shouldSendEmail) {
1851                 sendMessage(message, emailBody.toString());
1852             }
1853             LOG.debug("autoCloseRecurringOrders() ended");
1854 
1855             return false;
1856         }
1857         if (LOG.isDebugEnabled()) {
1858             LOG.debug("autoCloseRecurringOrders() The autoCloseRecurringOrdersDate found in the Application Settings table was " + recurringOrderDateString);
1859         }
1860         if (shouldSendEmail) {
1861             emailBody.append("The autoCloseRecurringOrdersDate found in the Application Settings table was " + recurringOrderDateString + ".");
1862         }
1863         Calendar appSettingsDate = dateTimeService.getCalendar(recurringOrderDate);
1864         Timestamp appSettingsDay = new Timestamp(appSettingsDate.getTime().getTime());
1865 
1866         Calendar todayMinusThreeMonths = getTodayMinusThreeMonths();
1867         Timestamp threeMonthsAgo = new Timestamp(todayMinusThreeMonths.getTime().getTime());
1868 
1869         if (appSettingsDate.after(todayMinusThreeMonths)) {
1870             if (LOG.isDebugEnabled()) {
1871                 LOG.debug("autoCloseRecurringOrders() The appSettingsDate: " + appSettingsDay + " is after todayMinusThreeMonths: " + threeMonthsAgo + ". The program will end.");
1872             }
1873             if (shouldSendEmail) {
1874                 emailBody.append("\n\nThe autoCloseRecurringOrdersDate: " + appSettingsDay + " is after todayMinusThreeMonths: " + threeMonthsAgo + ". The program will end.");
1875                 sendMessage(message, emailBody.toString());
1876             }
1877             LOG.debug("autoCloseRecurringOrders() ended");
1878 
1879             return false;
1880         }
1881 
1882         List<AutoClosePurchaseOrderView> closeList = purchaseOrderDao.getAutoCloseRecurringPurchaseOrders(getExcludedVendorChoiceCodes());
1883 
1884         //we need to eliminate the AutoClosePurchaseOrderView whose workflowdocument status is not OPEN..
1885         //KFSMI-7533
1886         List<AutoClosePurchaseOrderView> purchaseOrderAutoCloseList = filterDocumentsForAppDocStatusOpen(closeList);
1887 
1888         LOG.info("autoCloseRecurringOrders(): " + purchaseOrderAutoCloseList.size() + " PO's were returned for processing.");
1889         int counter = 0;
1890         for (AutoClosePurchaseOrderView poAutoClose : purchaseOrderAutoCloseList) {
1891             if (LOG.isDebugEnabled()) {
1892                 LOG.debug("autoCloseRecurringOrders(): Testing PO ID " + poAutoClose.getPurapDocumentIdentifier() + ". recurringPaymentEndDate: " + poAutoClose.getRecurringPaymentEndDate());
1893             }
1894             if (poAutoClose.getRecurringPaymentEndDate().before(threeMonthsAgo)) {
1895                 String newStatus = PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_CLOSE;
1896                 String annotation = "This recurring PO was automatically closed in batch.";
1897                 String documentType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT;
1898                 PurchaseOrderDocument document = getPurchaseOrderByDocumentNumber(poAutoClose.getDocumentNumber());
1899                 boolean rulePassed = kualiRuleService.applyRules(new AttributedRouteDocumentEvent("", document));
1900 
1901                 boolean success = true;
1902                 if (success) {
1903                     ++counter;
1904                     if (counter == 1) {
1905                         emailBody.append("\n\nThe following recurring Purchase Orders will be closed by auto close recurring batch job \n");
1906                     }
1907                     if (LOG.isDebugEnabled()) {
1908                         LOG.debug("autoCloseRecurringOrders() PO ID " + poAutoClose.getPurapDocumentIdentifier() + " will be closed.");
1909                     }
1910                     createNoteForAutoCloseOrders(document, annotation);
1911                     createAndRoutePotentialChangeDocument(poAutoClose.getDocumentNumber(), documentType, annotation, null, newStatus);
1912                     if (shouldSendEmail) {
1913                         emailBody.append("\n\n" + counter + " PO ID: " + poAutoClose.getPurapDocumentIdentifier() + ", End Date: " + poAutoClose.getRecurringPaymentEndDate() + ", Status: " + poAutoClose.getApplicationDocumentStatus() + ", VendorChoice: " + poAutoClose.getVendorChoiceCode() + ", RecurringPaymentType: " + poAutoClose.getRecurringPaymentTypeCode());
1914                     }
1915                 } else {
1916                     // If it was unsuccessful, we have to clear the error map in the GlobalVariables so that the previous
1917                     // error would not still be lingering around and the next PO in the list can be validated.
1918                     GlobalVariables.getMessageMap().clearErrorMessages();
1919                 }
1920             }
1921         }
1922         if (counter == 0) {
1923             LOG.debug("\n\nNo recurring PO's fit the conditions for closing.");
1924             if (shouldSendEmail) {
1925                 emailBody.append("\n\nNo recurring PO's fit the conditions for closing.");
1926             }
1927         }
1928         if (shouldSendEmail) {
1929             sendMessage(message, emailBody.toString());
1930         }
1931         resetAutoCloseRecurringOrderDateParameter();
1932         LOG.debug("autoCloseRecurringOrders() ended");
1933 
1934         return true;
1935     }
1936 
1937     /**
1938      * Filter out the auto close purchase order view documents for the appDocStatus with status open
1939      * For each document in the list, check if there is workflowdocument whose appdocstatus is open
1940      * add add to the return list.
1941      *
1942      * @param List<AutoClosePurchaseOrderView>
1943      *
1944      * @param appDocStatus
1945      * @return filteredAutoClosePOView filtered auto close po view documents where appdocstatus is open
1946      */
1947     protected List<AutoClosePurchaseOrderView> filterDocumentsForAppDocStatusOpen(List<AutoClosePurchaseOrderView> autoClosePurchaseOrderViews) {
1948         List<AutoClosePurchaseOrderView> filteredAutoClosePOView = new ArrayList<AutoClosePurchaseOrderView>();
1949 
1950         for (AutoClosePurchaseOrderView autoClosePurchaseOrderView : autoClosePurchaseOrderViews) {
1951             Document document = findDocument(autoClosePurchaseOrderView.getDocumentNumber());
1952 
1953             if (document != null) {
1954                 if (PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN.equalsIgnoreCase(
1955                         document.getDocumentHeader().getWorkflowDocument().getApplicationDocumentStatus())) {
1956                     //found the matched Awaiting Contract Manager Assignment status, retrieve the routeHeaderId and add to the list
1957                     filteredAutoClosePOView.add(autoClosePurchaseOrderView);
1958                 }
1959             }
1960         }
1961 
1962         return filteredAutoClosePOView;
1963     }
1964 
1965     /**
1966      * This method finds the document for the given document header id
1967      *
1968      * @param documentHeaderId
1969      * @return document The document in the workflow that matches the document header id.
1970      */
1971     protected Document findDocument(String documentHeaderId) {
1972         Document document = null;
1973 
1974         try {
1975             document = documentService.getByDocumentHeaderId(documentHeaderId);
1976         } catch (WorkflowException ex) {
1977             LOG.error("Exception encountered on finding the document: " + documentHeaderId, ex);
1978         } catch (UnknownDocumentTypeException ex) {
1979             // don't blow up just because a document type is not installed (but don't return it either)
1980             LOG.error("Exception encountered on finding the document: " + documentHeaderId, ex);
1981         }
1982 
1983         return document;
1984     }
1985 
1986 
1987     /**
1988      * Creates and returns a Calendar object of today minus three months.
1989      *
1990      * @return Calendar object of today minus three months.
1991      */
1992     protected Calendar getTodayMinusThreeMonths() {
1993         Calendar todayMinusThreeMonths = Calendar.getInstance(); // Set to today.
1994         todayMinusThreeMonths.add(Calendar.MONTH, -3); // Back up 3 months.
1995         todayMinusThreeMonths.set(Calendar.HOUR, 12);
1996         todayMinusThreeMonths.set(Calendar.MINUTE, 0);
1997         todayMinusThreeMonths.set(Calendar.SECOND, 0);
1998         todayMinusThreeMonths.set(Calendar.MILLISECOND, 0);
1999         todayMinusThreeMonths.set(Calendar.AM_PM, Calendar.AM);
2000         return todayMinusThreeMonths;
2001     }
2002 
2003     /**
2004      * Sets the to addresses, from address and the subject of the email.
2005      *
2006      * @param message        The MailMessage object of the email to be sent.
2007      * @param parameterEmail The String of email addresses with delimiters of ";" obtained from the system parameter.
2008      * @return The MailMessage object after the to addresses, from address and the subject have been set.
2009      */
2010     protected MailMessage setMessageAddressesAndSubject(MailMessage message, String parameterEmail) {
2011         String toAddressList[] = parameterEmail.split(";");
2012 
2013         if (toAddressList.length > 0) {
2014             for (String element : toAddressList) {
2015                 if (element != null) {
2016                     message.addToAddress(element.trim());
2017                 }
2018             }
2019         }
2020 
2021         message.setFromAddress(toAddressList[0]);
2022         message.setSubject("Auto Close Recurring Purchase Orders");
2023         return message;
2024     }
2025 
2026     /**
2027      * Sends the email by calling the sendMessage method in mailService and log error if exception occurs during the attempt to send
2028      * the message.
2029      *
2030      * @param message   The MailMessage object containing information to be sent.
2031      * @param emailBody The String containing the body of the email to be sent.
2032      */
2033     protected void sendMessage(MailMessage message, String emailBody) {
2034         message.setMessage(emailBody);
2035         try {
2036             mailService.sendMessage(message);
2037         } catch (Exception e) {
2038             // Don't stop the show if the email has problem, log it and continue.
2039             LOG.error("autoCloseRecurringOrders(): email problem. Message not sent.", e);
2040             throw new RuntimeException(e);
2041         }
2042     }
2043 
2044     /**
2045      * Resets the AUTO_CLOSE_RECURRING_ORDER_DT system parameter to "mm/dd/yyyy".
2046      */
2047     protected void resetAutoCloseRecurringOrderDateParameter() {
2048         Parameter autoCloseRecurringPODate = parameterService.getParameter(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_DATE);
2049         if (autoCloseRecurringPODate != null) {
2050             Parameter.Builder updatedParameter = Parameter.Builder.create(autoCloseRecurringPODate);
2051             updatedParameter.setValue("mm/dd/yyyy");
2052             parameterService.updateParameter(updatedParameter.build());
2053         }
2054     }
2055 
2056     /**
2057      * Gets a List of excluded vendor choice codes from PurapConstants.
2058      *
2059      * @return a List of excluded vendor choice codes
2060      */
2061     protected List<String> getExcludedVendorChoiceCodes() {
2062         List<String> excludedVendorChoiceCodes = new ArrayList<String>();
2063         for (String excludedCode : PurapConstants.AUTO_CLOSE_EXCLUSION_VNDR_CHOICE_CODES) {
2064             excludedVendorChoiceCodes.add(excludedCode);
2065         }
2066         return excludedVendorChoiceCodes;
2067     }
2068 
2069     /**
2070      * Creates and add a note to the purchase order document using the annotation String in the input parameter. This method is used
2071      * by the autoCloseRecurringOrders() and autoCloseFullyDisencumberedOrders to add a note to the purchase order to indicate that
2072      * the purchase order was closed by the batch job.
2073      *
2074      * @param purchaseOrderDocument The purchase order document that is being closed by the batch job.
2075      * @param annotation            The string to appear on the note to be attached to the purchase order.
2076      */
2077     protected void createNoteForAutoCloseOrders(PurchaseOrderDocument purchaseOrderDocument, String annotation) {
2078         try {
2079             Note noteObj = documentService.createNoteFromDocument(purchaseOrderDocument, annotation);
2080 //            documentService.addNoteToDocument(purchaseOrderDocument, noteObj);
2081             noteService.save(noteObj);
2082         } catch (Exception e) {
2083             String errorMessage = "Error creating and saving close note for purchase order with document service";
2084             LOG.error("createNoteForAutoCloseRecurringOrders " + errorMessage, e);
2085             throw new RuntimeException(errorMessage, e);
2086         }
2087     }
2088 
2089     /**
2090      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetItemsForIndividual(java.lang.Integer)
2091      */
2092     @Override
2093     public List<PurchasingCapitalAssetItem> retrieveCapitalAssetItemsForIndividual(Integer poId) {
2094         PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
2095         if (ObjectUtils.isNotNull(po)) {
2096             return po.getPurchasingCapitalAssetItems();
2097         }
2098         return null;
2099     }
2100 
2101     /**
2102      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetSystemForOneSystem(java.lang.Integer)
2103      */
2104     @Override
2105     public CapitalAssetSystem retrieveCapitalAssetSystemForOneSystem(Integer poId) {
2106         PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
2107         if (ObjectUtils.isNotNull(po)) {
2108             List<CapitalAssetSystem> systems = po.getPurchasingCapitalAssetSystems();
2109             if (ObjectUtils.isNotNull(systems)) {
2110                 // for one system, there should only ever be one system
2111                 return systems.get(0);
2112             }
2113         }
2114         return null;
2115     }
2116 
2117     /**
2118      * @see org.kuali.ole.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetSystemsForMultipleSystem(java.lang.Integer)
2119      */
2120     @Override
2121     public List<CapitalAssetSystem> retrieveCapitalAssetSystemsForMultipleSystem(Integer poId) {
2122         PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
2123         if (ObjectUtils.isNotNull(po)) {
2124             return po.getPurchasingCapitalAssetSystems();
2125         }
2126         return null;
2127     }
2128 
2129     /**
2130      * This method fixes the item references in this document
2131      */
2132     protected void fixItemReferences(PurchaseOrderDocument po) {
2133         // fix item and account references in case this is a new doc (since they will be lost)
2134         for (PurApItem item : (List<PurApItem>) po.getItems()) {
2135             item.setPurapDocument(po);
2136             item.fixAccountReferences();
2137         }
2138     }
2139 
2140     @Override
2141     public List getPendingPurchaseOrderFaxes() {
2142         List<PurchaseOrderDocument> purchaseOrderList = purchaseOrderDao.getPendingPurchaseOrdersForFaxing();
2143         return filterPurchaseOrderDocumentByAppDocStatus(purchaseOrderList,
2144                 PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_FAX);
2145     }
2146 
2147     /**
2148      * Wrapper class to the filterPaymentRequestByAppDocStatus
2149      * <p/>
2150      * This class first extract the payment request document numbers from the Payment Request Collections,
2151      * then perform the filterPaymentRequestByAppDocStatus function.  Base on the filtered payment request
2152      * doc number, reconstruct the filtered Payment Request Collection
2153      *
2154      * @param paymentRequestDocuments
2155      * @param appDocStatus
2156      * @return
2157      */
2158     protected List<PurchaseOrderDocument> filterPurchaseOrderDocumentByAppDocStatus(Collection<PurchaseOrderDocument> purchaseOrderDocuments, String... appDocStatus) {
2159         List<String> purchaseOrderDocNumbers = new ArrayList<String>();
2160         for (PurchaseOrderDocument purchaseOrder : purchaseOrderDocuments) {
2161             purchaseOrderDocNumbers.add(purchaseOrder.getDocumentNumber());
2162         }
2163 
2164         List<String> filteredPurchaseOrderDocNumbers = filterPurchaseOrderDocumentNumbersByAppDocStatus(purchaseOrderDocNumbers, appDocStatus);
2165 
2166         List<PurchaseOrderDocument> filteredPaymentRequestDocuments = new ArrayList<PurchaseOrderDocument>();
2167         //add to filtered collection if it is in the filtered payment request doc number list
2168         for (PurchaseOrderDocument po : purchaseOrderDocuments) {
2169             if (filteredPurchaseOrderDocNumbers.contains(po.getDocumentNumber())) {
2170                 filteredPaymentRequestDocuments.add(po);
2171             }
2172         }
2173         return filteredPaymentRequestDocuments;
2174     }
2175 
2176     /**
2177      * Since PaymentRequest does not have the app doc status, perform an additional lookup
2178      * through doc search by using list of PaymentRequest Doc numbers.  Query appDocStatus
2179      * from workflow document and filter against the provided status
2180      * <p/>
2181      * DocumentSearch allows for multiple docNumber lookup by docId|docId|docId conversion
2182      *
2183      * @param lookupDocNumbers
2184      * @param appDocStatus
2185      * @return List<String> purchaseOrderDocumentNumbers
2186      */
2187     protected List<String> filterPurchaseOrderDocumentNumbersByAppDocStatus(List<String> lookupDocNumbers, String... appDocStatus) {
2188         boolean valid = false;
2189 
2190         final String DOC_NUM_DELIM = "|";
2191         StrBuilder routerHeaderIdBuilder = new StrBuilder().appendWithSeparators(lookupDocNumbers, DOC_NUM_DELIM);
2192 
2193         List<String> purchaseOrderDocNumbers = new ArrayList<String>();
2194 
2195         DocumentSearchCriteria.Builder documentSearchCriteriaDTO = DocumentSearchCriteria.Builder.create();
2196         documentSearchCriteriaDTO.setDocumentId(routerHeaderIdBuilder.toString());
2197         documentSearchCriteriaDTO.setDocumentTypeName(PurapConstants.PurapDocTypeCodes.PO_DOCUMENT);
2198 
2199         DocumentSearchResults poDocumentsList = KewApiServiceLocator.getWorkflowDocumentService().documentSearch(
2200                 GlobalVariables.getUserSession().getPrincipalId(), documentSearchCriteriaDTO.build());
2201 
2202         for (DocumentSearchResult poDocument : poDocumentsList.getSearchResults()) {
2203             ///use the appDocStatus from the KeyValueDTO result to look up custom status
2204             if (Arrays.asList(appDocStatus).contains(poDocument.getDocument().getApplicationDocumentStatus())) {
2205                 //found the matching status, retrieve the routeHeaderId and add to the list
2206                 purchaseOrderDocNumbers.add(poDocument.getDocument().getDocumentId());
2207             }
2208         }
2209 
2210         return purchaseOrderDocNumbers;
2211     }
2212 
2213     @Override
2214     public String getPurchaseOrderAppDocStatus(Integer poId) {
2215         //TODO: This could be kind of expensive for one field
2216         PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
2217         if (ObjectUtils.isNotNull(po)) {
2218             return po.getApplicationDocumentStatus();
2219         }
2220 
2221         return null;
2222     }
2223 
2224     /**
2225      * helper method to take the po and save it using businessObjectService so that only the po related data is saved since most
2226      * often we are only updating the flags on the document. It will then reindex the document.
2227      *
2228      * @param po
2229      */
2230     protected void savePurchaseOrderData(PurchaseOrderDocument po) {
2231         // saving old PO using the business object service because the documentService saveDocument
2232         // will try to save the notes again and will cause ojb lock exception.
2233         // since only values that is changed on PO is pendingActionIndicator, save on businessObjectService is used
2234         // OLEMI-9741
2235 
2236         businessObjectService.save(po);
2237 
2238         // reindex the document so that the app doc status gets updated in the results for the PO lookups.
2239         final DocumentAttributeIndexingQueue documentAttributeIndexingQueue = KewApiServiceLocator
2240                 .getDocumentAttributeIndexingQueue();
2241         documentAttributeIndexingQueue.indexDocument(po.getDocumentNumber());
2242 
2243     }
2244 
2245     /**
2246      * @return Returns the personService.
2247      */
2248     protected PersonService getPersonService() {
2249         if (personService == null) {
2250             personService = SpringContext.getBean(PersonService.class);
2251         }
2252         return personService;
2253     }
2254 
2255     public void setB2bPurchaseOrderService(B2BPurchaseOrderService purchaseOrderService) {
2256         this.b2bPurchaseOrderService = purchaseOrderService;
2257     }
2258 
2259     public void setBusinessObjectService(BusinessObjectService boService) {
2260         this.businessObjectService = boService;
2261     }
2262 
2263     public void setDateTimeService(DateTimeService dateTimeService) {
2264         this.dateTimeService = dateTimeService;
2265     }
2266 
2267     public void setDocumentService(DocumentService documentService) {
2268         this.documentService = documentService;
2269     }
2270 
2271     public void setNoteService(NoteService noteService) {
2272         this.noteService = noteService;
2273     }
2274 
2275     public void setPurapService(PurapService purapService) {
2276         this.purapService = purapService;
2277     }
2278 
2279     public void setPrintService(PrintService printService) {
2280         this.printService = printService;
2281     }
2282 
2283     public void setPurchaseOrderDao(PurchaseOrderDao purchaseOrderDao) {
2284         this.purchaseOrderDao = purchaseOrderDao;
2285     }
2286 
2287     public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
2288         this.workflowDocumentService = workflowDocumentService;
2289     }
2290 
2291     public void setConfigurationService(ConfigurationService kualiConfigurationService) {
2292         this.kualiConfigurationService = kualiConfigurationService;
2293     }
2294 
2295     public void setKualiRuleService(KualiRuleService kualiRuleService) {
2296         this.kualiRuleService = kualiRuleService;
2297     }
2298 
2299     public void setVendorService(VendorService vendorService) {
2300         this.vendorService = vendorService;
2301     }
2302 
2303     public void setRequisitionService(RequisitionService requisitionService) {
2304         this.requisitionService = requisitionService;
2305     }
2306 
2307     public void setPurapWorkflowIntegrationService(PurApWorkflowIntegrationService purapWorkflowIntegrationService) {
2308         this.purapWorkflowIntegrationService = purapWorkflowIntegrationService;
2309     }
2310 
2311     public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
2312         this.maintenanceDocumentService = maintenanceDocumentService;
2313     }
2314 
2315     public void setParameterService(ParameterService parameterService) {
2316         this.parameterService = parameterService;
2317     }
2318 
2319     public void setMailService(MailService mailService) {
2320         this.mailService = mailService;
2321     }
2322 
2323     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
2324         this.dataDictionaryService = dataDictionaryService;
2325     }
2326 
2327 
2328     /**
2329      * This method is the starting point of EDI File creation and transmission for a PO
2330      *
2331      * @param po
2332      */
2333     @Override
2334     public void initiateTransmission(PurchaseOrderDocument po, PurApItem item) {
2335         if(po.getRequestorPersonEmailAddress() != null){
2336             fromEmailAddress = po.getRequestorPersonEmailAddress();
2337         }
2338 
2339         if (po != null && po.getVendorDetail() != null) {
2340             List<VendorTransmissionFormatDetail> vendorTxFormat = po.getVendorDetail().getVendorTransmissionFormat();
2341             boolean isSuccess = false;
2342             if (vendorTxFormat.size() > 0) {
2343                 for (int i = 0; i < vendorTxFormat.size(); i++) {
2344                     VendorTransmissionFormatDetail vendorTransmissionFormatDetail = vendorTxFormat.get(i);
2345                     boolean isPrefferedTransmissionFormat = vendorTransmissionFormatDetail.isVendorPreferredTransmissionFormat();
2346                     if (isPrefferedTransmissionFormat && item.getItemLineNumber() != null) {
2347                         String documentType = po.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
2348                         String ediFileName = documentType + "_" + po.getPurapDocumentIdentifier().toString() + "_" + PurapConstants.ItemTypeCodes.ITEM_TYPE_ITEM_CODE + item.getItemLineNumber() + "_" + System.currentTimeMillis() + ".edi";
2349                         String pdfFileName = documentType + "_" + po.getPurapDocumentIdentifier().toString() + "_" + System.currentTimeMillis() + ".pdf";
2350                         String directory = kualiConfigurationService.getPropertyValueAsString(OLEConstants.STAGING_DIRECTORY_KEY);
2351                         String fileLocation = directory + kualiConfigurationService.getPropertyValueAsString("kualietl.vendortransmissionfile");
2352                         File fileLocationDir = new File(fileLocation);
2353                         if (!(fileLocationDir.exists())) {
2354                             fileLocationDir.mkdir();
2355                         }
2356                         String file = fileLocation.trim() + ediFileName.trim();
2357 
2358                         if (vendorTransmissionFormatDetail.getVendorTransmissionFormat() != null &&
2359                                 vendorTransmissionFormatDetail.getVendorTransmissionFormat().getVendorTransmissionFormat() != null &&
2360                                 vendorTransmissionFormatDetail.getVendorTransmissionFormat().getVendorTransmissionFormat().equalsIgnoreCase("EDI")) {
2361                             if (LOG.isDebugEnabled()) {
2362                                 LOG.debug("EDI File======================>" + file);
2363                             }
2364                             isSuccess = savePurchaseOrderEdi(po, file, item);
2365                         }
2366                         if (vendorTransmissionFormatDetail.getVendorTransmissionFormat() != null &&
2367                                 vendorTransmissionFormatDetail.getVendorTransmissionFormat().getVendorTransmissionFormat() != null &&
2368                                 vendorTransmissionFormatDetail.getVendorTransmissionFormat().getVendorTransmissionFormat().equalsIgnoreCase(OLEConstants.OLE_VENDOR_PDF_OPTION)) {
2369                             if(!(po.getDocumentNumber().equals(documentNumber))) {
2370                                 savePurchaseOrderPdf(po, pdfFileName, item);
2371                                 processFTPTransmission(vendorTransmissionFormatDetail, file, pdfFileName.trim());
2372                                 documentNumber = po.getDocumentNumber();
2373                             }
2374                         }
2375                         if (isSuccess && vendorTransmissionFormatDetail.getVendorTransmissionTypes().getVendorTransmissionType() != null) {
2376                             processFTPTransmission(vendorTransmissionFormatDetail, file, ediFileName.trim());
2377                         }
2378                     }
2379                 }
2380             }
2381 
2382         }
2383     }
2384 
2385     public String getEmailAddress(VendorTransmissionFormatDetail vendorTransmissionFormatDetail){
2386         String toAddress = "";
2387         Integer vendorId = vendorTransmissionFormatDetail.getVendorHeaderGeneratedIdentifier();
2388         if(vendorId != null){
2389             Map searchMap = new HashMap();
2390             searchMap.put("vendorHeaderGeneratedIdentifier",vendorId);
2391             List<VendorAddress> address = (List<VendorAddress>)SpringContext.getBean(BusinessObjectService.class).findMatching(VendorAddress.class,searchMap);
2392             if(address.size() > 0){
2393                 for(VendorAddress vendorAddress : address){
2394                     if(toAddress == null || (toAddress.isEmpty())){
2395                         toAddress = vendorAddress.getVendorAddressEmailAddress();
2396                     }
2397                 }
2398 
2399             }
2400             if(toAddress == null || toAddress.isEmpty()){
2401                 List<VendorContact> vendorContactList = (List<VendorContact>)SpringContext.getBean(BusinessObjectService.class).findMatching(VendorContact.class,searchMap);
2402                 if(vendorContactList.size() > 0){
2403                     for(VendorContact vendorContact : vendorContactList){
2404                         if(toAddress == null || (toAddress.isEmpty())){
2405                             toAddress = vendorContact.getVendorContactEmailAddress();
2406                         }
2407                     }
2408                 }
2409             }
2410         }
2411 
2412         return toAddress;
2413     }
2414 
2415 
2416     /**
2417      * This method is to create and save the EDI file into the staging directory.
2418      *
2419      * @param po
2420      * @param file
2421      * @param isRetransmit
2422      * @param environment
2423      */
2424     public boolean savePurchaseOrderEdi(PurchaseOrderDocument po, String file, PurApItem item) {
2425         LOG.debug("****************savePurchaseOrderEdi() started*******************");
2426         boolean isSuccess = false;
2427         PurchaseOrderEdi purchaseOrderEdi = SpringContext.getBean(PurchaseOrderEdi.class, "purchaseOrderEdi");
2428         try {
2429             isSuccess = purchaseOrderEdi.saveEdi(po, item, file);
2430             LOG.debug("savePurchaseOrderEdi() completed");
2431         } catch (Exception e) {
2432             LOG.error("Caught exception ", e);
2433             throw new RuntimeException(e);
2434         }
2435         return isSuccess;
2436     }
2437 
2438     public boolean savePurchaseOrderPdf(PurchaseOrderDocument po, String file, PurApItem item) {
2439         LOG.debug("****************savePurchaseOrderPDF() started*******************");
2440         boolean isSuccess = false;
2441         PurchaseOrderParameters purchaseOrderParameters = getPurchaseOrderParameters();
2442         purchaseOrderParameters.setPurchaseOrderPdfAndFaxParameters(po);
2443         PurchaseOrderTransmitParameters purchaseOrderTransmitParameter = (PurchaseOrderTransmitParameters) purchaseOrderParameters;
2444         String pdfFileLocation = ConfigContext.getCurrentContextConfig().getProperty(org.kuali.ole.OLEConstants.VENDOR_TRANSMISSION_FILE_PATH);
2445         if (pdfFileLocation != null) {
2446             purchaseOrderTransmitParameter.setPdfFileLocation(pdfFileLocation);
2447             purchaseOrderTransmitParameter.setPdfFileName(file);
2448         }
2449         String environment = kualiConfigurationService.getPropertyValueAsString(OLEConstants.ENVIRONMENT_KEY);
2450         PurchaseOrderPdf purchaseOrderPdf = SpringContext.getBean(PurchaseOrderPdf.class, "purchaseOrderPdf");
2451         try {
2452             purchaseOrderPdf.savePdf(po, purchaseOrderParameters, isSuccess, environment);
2453             isSuccess = true;
2454             LOG.debug("savePurchaseOrderPdf() completed");
2455         } catch (Exception e) {
2456             LOG.error("Caught exception ", e);
2457             throw new RuntimeException(e);
2458         }
2459         return isSuccess;
2460     }
2461 
2462     public PurchaseOrderParameters getPurchaseOrderParameters() {
2463         return SpringContext.getBean(PurchaseOrderParameters.class);
2464     }
2465 
2466 
2467     /**
2468      * This method is for transmitting EDI file into FTP Server
2469      *
2470      * @param vendorTransmissionFormatDetail
2471      * @param file
2472      * @param fileName
2473      */
2474     @Override
2475     public boolean processFTPTransmission(VendorTransmissionFormatDetail vendorTransmissionFormatDetail, String file, String fileName) {
2476 
2477         if (vendorTransmissionFormatDetail != null &&
2478                 vendorTransmissionFormatDetail.getVendorEDIConnectionAddress() != null &&
2479                 vendorTransmissionFormatDetail.getVendorEDIConnectionUserName() != null &&
2480                 vendorTransmissionFormatDetail.getVendorEDIConnectionPassword() != null) {
2481             if (LOG.isDebugEnabled()) {
2482                 LOG.debug("Transmission Format==================>" + vendorTransmissionFormatDetail.getVendorTransmissionFormat().getVendorTransmissionFormat());
2483                 LOG.debug("Connection Type======================>" + vendorTransmissionFormatDetail.getVendorTransmissionTypes().getVendorTransmissionType());
2484                 LOG.debug("Connection Address===================>" + vendorTransmissionFormatDetail.getVendorEDIConnectionAddress());
2485                 LOG.debug("Connection User Name=================>" + vendorTransmissionFormatDetail.getVendorEDIConnectionUserName());
2486                 //            LOG.info("Connection Password==================>"+vendorTransmissionFormatDetail.getVendorEDIConnectionPassword());
2487                 LOG.debug("file=================================>" + file);
2488                 LOG.debug("ediFileName==========================>" + fileName);
2489             }
2490 
2491             OleTransmissionService transmissionService = SpringContext.getBean(OleTransmissionService.class);
2492             if (vendorTransmissionFormatDetail.getVendorTransmissionTypes().getVendorTransmissionType().equalsIgnoreCase("SFTP")) {
2493                 transmissionService.doSFTPUpload(vendorTransmissionFormatDetail, file, fileName);
2494             } else if (vendorTransmissionFormatDetail.getVendorTransmissionTypes().getVendorTransmissionType().equalsIgnoreCase("FTP")) {
2495                 transmissionService.doFTPUpload(vendorTransmissionFormatDetail, file, fileName);
2496             } else if (vendorTransmissionFormatDetail.getVendorTransmissionTypes().getVendorTransmissionType().equalsIgnoreCase(OLEConstants.OLE_VENDOR_EMAIL_OPTION)) {
2497                 String fileLocation = ConfigContext.getCurrentContextConfig().getProperty(org.kuali.ole.OLEConstants.VENDOR_TRANSMISSION_FILE_PATH) + fileName;
2498                 fileNameList.add(fileLocation);
2499                 toEmailAddress = getEmailAddress(vendorTransmissionFormatDetail);
2500             }
2501         } else if (vendorTransmissionFormatDetail != null &&
2502                 getVendorFormatType(vendorTransmissionFormatDetail.getVendorTransmissionFormatId()).equalsIgnoreCase(OLEConstants.OLE_VENDOR_PDF_OPTION) &&
2503                 vendorTransmissionFormatDetail.isVendorPreferredTransmissionFormat() &&
2504                 getVendorTransmissionType(vendorTransmissionFormatDetail.getVendorTransmissionTypeId()).equalsIgnoreCase(OLEConstants.OLE_VENDOR_EMAIL_OPTION)) {
2505             if (vendorTransmissionFormatDetail.getVendorTransmissionTypes().getVendorTransmissionType().equalsIgnoreCase(OLEConstants.OLE_VENDOR_EMAIL_OPTION) &&
2506                     vendorTransmissionFormatDetail.getVendorTransmissionFormat().getVendorTransmissionFormat().equalsIgnoreCase(OLEConstants.OLE_VENDOR_PDF_OPTION)) {
2507                 String fileLocation = ConfigContext.getCurrentContextConfig().getProperty(org.kuali.ole.OLEConstants.VENDOR_TRANSMISSION_FILE_PATH) + fileName;
2508                 fileNameList.add(fileLocation);
2509                 toEmailAddress = getEmailAddress(vendorTransmissionFormatDetail);
2510             }
2511         } else if (vendorTransmissionFormatDetail != null &&
2512                 getVendorFormatType(vendorTransmissionFormatDetail.getVendorTransmissionFormatId()).equalsIgnoreCase(OLEConstants.OLE_VENDOR_EDI_OPTION) &&
2513                 vendorTransmissionFormatDetail.isVendorPreferredTransmissionFormat() &&
2514                 getVendorTransmissionType(vendorTransmissionFormatDetail.getVendorTransmissionTypeId()).equalsIgnoreCase(OLEConstants.OLE_VENDOR_EMAIL_OPTION)) {
2515                 String fileLocation = ConfigContext.getCurrentContextConfig().getProperty(org.kuali.ole.OLEConstants.VENDOR_TRANSMISSION_FILE_PATH) + fileName;
2516                 fileNameList.add(fileLocation);
2517                 toEmailAddress = getEmailAddress(vendorTransmissionFormatDetail);
2518         }
2519         return true;
2520     }
2521 
2522     public String getVendorFormatType(Long id){
2523         String vendorFormatType = "";
2524         Map searchMap = new HashMap();
2525         searchMap.put(OLEConstants.VENDOR_TRANS_FORMAT_ID,id);
2526         OleVendorTransmissionFormat vendorTransmissionFormat = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(OleVendorTransmissionFormat.class,searchMap);
2527         if(vendorTransmissionFormat != null){
2528             vendorFormatType = vendorTransmissionFormat.getVendorTransmissionFormat();
2529         }
2530         return vendorFormatType;
2531     }
2532 
2533     public String getVendorTransmissionType(Integer id){
2534         String vendorTransmissionType = "";
2535         Map searchMap = new HashMap();
2536         searchMap.put(OLEConstants.VENDOR_TRANS_TYPE_ID,id);
2537         OleVendorTransmissionType transmissionType = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(OleVendorTransmissionType.class,searchMap);
2538         if(transmissionType != null){
2539             vendorTransmissionType = transmissionType.getVendorTransmissionType();
2540         }
2541         return vendorTransmissionType;
2542     }
2543 
2544     public void sendEmail() {
2545         try {
2546             if (toEmailAddress != null && fromEmailAddress != null && fileNameList.size() > 0) {
2547                 String subject = OLEConstants.MAIL_SUBJECT;
2548                 String messageBody = OLEConstants.MAIL_MESSAGE_BODY;
2549                 OleMailer oleMail = GlobalResourceLoader.getService(OLEConstants.OLE_MAILER);
2550                 oleMail.SendEMail(toEmailAddress, fromEmailAddress, fileNameList, subject, messageBody);
2551             }
2552         } catch (Exception e) {
2553             LOG.info("Email Sending Failed for fromAddress ["+fromEmailAddress+"] and toAddress [ "+toEmailAddress+" ]");
2554             e.printStackTrace();
2555         }
2556     }
2557 }