001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.purap.document.service.impl;
017    
018    import java.io.ByteArrayOutputStream;
019    import java.sql.Timestamp;
020    import java.text.ParseException;
021    import java.util.ArrayList;
022    import java.util.Calendar;
023    import java.util.Collection;
024    import java.util.Date;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    
032    import org.apache.commons.lang.StringUtils;
033    import org.kuali.kfs.integration.purap.CapitalAssetSystem;
034    import org.kuali.kfs.module.purap.PurapConstants;
035    import org.kuali.kfs.module.purap.PurapConstants.CreditMemoStatuses;
036    import org.kuali.kfs.module.purap.PurapConstants.PODocumentsStrings;
037    import org.kuali.kfs.module.purap.PurapConstants.POTransmissionMethods;
038    import org.kuali.kfs.module.purap.PurapConstants.PaymentRequestStatuses;
039    import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderDocTypes;
040    import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
041    import org.kuali.kfs.module.purap.PurapConstants.RequisitionSources;
042    import org.kuali.kfs.module.purap.PurapKeyConstants;
043    import org.kuali.kfs.module.purap.PurapParameterConstants;
044    import org.kuali.kfs.module.purap.PurapPropertyConstants;
045    import org.kuali.kfs.module.purap.PurapWorkflowConstants.PurchaseOrderDocument.NodeDetailEnum;
046    import org.kuali.kfs.module.purap.batch.AutoCloseRecurringOrdersStep;
047    import org.kuali.kfs.module.purap.businessobject.AutoClosePurchaseOrderView;
048    import org.kuali.kfs.module.purap.businessobject.ContractManagerAssignmentDetail;
049    import org.kuali.kfs.module.purap.businessobject.CreditMemoView;
050    import org.kuali.kfs.module.purap.businessobject.PaymentRequestView;
051    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
052    import org.kuali.kfs.module.purap.businessobject.PurApItem;
053    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderCapitalAssetItem;
054    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderCapitalAssetSystem;
055    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
056    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderQuoteStatus;
057    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderVendorQuote;
058    import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
059    import org.kuali.kfs.module.purap.businessobject.ReceivingThreshold;
060    import org.kuali.kfs.module.purap.document.ContractManagerAssignmentDocument;
061    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
062    import org.kuali.kfs.module.purap.document.PurchaseOrderSplitDocument;
063    import org.kuali.kfs.module.purap.document.PurchasingDocument;
064    import org.kuali.kfs.module.purap.document.RequisitionDocument;
065    import org.kuali.kfs.module.purap.document.dataaccess.PurchaseOrderDao;
066    import org.kuali.kfs.module.purap.document.service.B2BPurchaseOrderService;
067    import org.kuali.kfs.module.purap.document.service.LogicContainer;
068    import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
069    import org.kuali.kfs.module.purap.document.service.PrintService;
070    import org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService;
071    import org.kuali.kfs.module.purap.document.service.PurapService;
072    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
073    import org.kuali.kfs.module.purap.document.service.RequisitionService;
074    import org.kuali.kfs.module.purap.util.PurApObjectUtils;
075    import org.kuali.kfs.module.purap.util.ThresholdHelper;
076    import org.kuali.kfs.module.purap.util.ThresholdHelper.ThresholdSummary;
077    import org.kuali.kfs.sys.KFSConstants;
078    import org.kuali.kfs.sys.KFSPropertyConstants;
079    import org.kuali.kfs.sys.context.SpringContext;
080    import org.kuali.kfs.sys.document.FinancialSystemTransactionalDocumentBase;
081    import org.kuali.kfs.sys.document.validation.event.AttributedRouteDocumentEvent;
082    import org.kuali.kfs.sys.document.validation.event.DocumentSystemSaveEvent;
083    import org.kuali.kfs.vnd.VendorConstants;
084    import org.kuali.kfs.vnd.VendorConstants.AddressTypes;
085    import org.kuali.kfs.vnd.businessobject.CommodityCode;
086    import org.kuali.kfs.vnd.businessobject.VendorAddress;
087    import org.kuali.kfs.vnd.businessobject.VendorCommodityCode;
088    import org.kuali.kfs.vnd.businessobject.VendorDetail;
089    import org.kuali.kfs.vnd.businessobject.VendorPhoneNumber;
090    import org.kuali.kfs.vnd.document.service.VendorService;
091    import org.kuali.rice.kew.exception.WorkflowException;
092    import org.kuali.rice.kew.service.WorkflowDocumentActions;
093    import org.kuali.rice.kew.util.KEWConstants;
094    import org.kuali.rice.kim.bo.Person;
095    import org.kuali.rice.kim.service.PersonService;
096    import org.kuali.rice.kns.bo.AdHocRoutePerson;
097    import org.kuali.rice.kns.bo.AdHocRouteRecipient;
098    import org.kuali.rice.kns.bo.Note;
099    import org.kuali.rice.kns.bo.Parameter;
100    import org.kuali.rice.kns.document.DocumentBase;
101    import org.kuali.rice.kns.document.MaintenanceDocument;
102    import org.kuali.rice.kns.exception.ValidationException;
103    import org.kuali.rice.kns.mail.MailMessage;
104    import org.kuali.rice.kns.maintenance.Maintainable;
105    import org.kuali.rice.kns.rule.event.RouteDocumentEvent;
106    import org.kuali.rice.kns.service.BusinessObjectService;
107    import org.kuali.rice.kns.service.DataDictionaryService;
108    import org.kuali.rice.kns.service.DateTimeService;
109    import org.kuali.rice.kns.service.DocumentService;
110    import org.kuali.rice.kns.service.KualiConfigurationService;
111    import org.kuali.rice.kns.service.KualiRuleService;
112    import org.kuali.rice.kns.service.MailService;
113    import org.kuali.rice.kns.service.MaintenanceDocumentService;
114    import org.kuali.rice.kns.service.NoteService;
115    import org.kuali.rice.kns.service.ParameterService;
116    import org.kuali.rice.kns.service.SequenceAccessorService;
117    import org.kuali.rice.kns.util.GlobalVariables;
118    import org.kuali.rice.kns.util.KualiDecimal;
119    import org.kuali.rice.kns.util.MessageMap;
120    import org.kuali.rice.kns.util.ObjectUtils;
121    import org.kuali.rice.kns.util.TypedArrayList;
122    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
123    import org.kuali.rice.kns.workflow.service.KualiWorkflowInfo;
124    import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
125    import org.springframework.transaction.annotation.Transactional;
126    
127    
128    @Transactional
129    public class PurchaseOrderServiceImpl implements PurchaseOrderService {
130        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurchaseOrderServiceImpl.class);
131    
132        private BusinessObjectService businessObjectService;
133        protected DateTimeService dateTimeService;
134        private DocumentService documentService;
135        private NoteService noteService;
136        protected PurapService purapService;
137        private PrintService printService;
138        private PurchaseOrderDao purchaseOrderDao;
139        private WorkflowDocumentService workflowDocumentService;
140        private KualiConfigurationService kualiConfigurationService;
141        private KualiRuleService kualiRuleService;
142        private VendorService vendorService;
143        private RequisitionService requisitionService;
144        private PurApWorkflowIntegrationService purapWorkflowIntegrationService;
145        private KualiWorkflowInfo workflowInfoService;
146        private MaintenanceDocumentService maintenanceDocumentService;
147        private ParameterService parameterService;
148        private PersonService<Person> personService;
149        private MailService mailService;
150        private B2BPurchaseOrderService b2bPurchaseOrderService;
151        private DataDictionaryService dataDictionaryService;
152        
153        public void setB2bPurchaseOrderService(B2BPurchaseOrderService purchaseOrderService) {
154            b2bPurchaseOrderService = purchaseOrderService;
155        }
156    
157        public void setBusinessObjectService(BusinessObjectService boService) {
158            this.businessObjectService = boService;
159        }
160    
161        public void setDateTimeService(DateTimeService dateTimeService) {
162            this.dateTimeService = dateTimeService;
163        }
164    
165        public void setDocumentService(DocumentService documentService) {
166            this.documentService = documentService;
167        }
168    
169        public void setNoteService(NoteService noteService) {
170            this.noteService = noteService;
171        }
172    
173        public void setPurapService(PurapService purapService) {
174            this.purapService = purapService;
175        }
176    
177        public void setPrintService(PrintService printService) {
178            this.printService = printService;
179        }
180    
181        public void setPurchaseOrderDao(PurchaseOrderDao purchaseOrderDao) {
182            this.purchaseOrderDao = purchaseOrderDao;
183        }
184    
185        public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
186            this.workflowDocumentService = workflowDocumentService;
187        }
188    
189        public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
190            this.kualiConfigurationService = kualiConfigurationService;
191        }
192    
193        public void setKualiRuleService(KualiRuleService kualiRuleService) {
194            this.kualiRuleService = kualiRuleService;
195        }
196    
197        public void setVendorService(VendorService vendorService) {
198            this.vendorService = vendorService;
199        }
200    
201        public void setRequisitionService(RequisitionService requisitionService) {
202            this.requisitionService = requisitionService;
203        }
204    
205        public void setPurapWorkflowIntegrationService(PurApWorkflowIntegrationService purapWorkflowIntegrationService) {
206            this.purapWorkflowIntegrationService = purapWorkflowIntegrationService;
207        }
208        
209        public void setWorkflowInfoService(KualiWorkflowInfo workflowInfoService) {
210            this.workflowInfoService = workflowInfoService;
211        }
212    
213        public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
214            this.maintenanceDocumentService = maintenanceDocumentService;
215        }
216        
217        public void setParameterService(ParameterService parameterService) {
218            this.parameterService = parameterService;
219        }
220    
221        public void setMailService(MailService mailService) {
222            this.mailService = mailService;
223        }
224        
225        
226        public boolean isPurchaseOrderOpenForProcessing(Integer poId) {
227            return isPurchaseOrderOpenForProcessing(getCurrentPurchaseOrder(poId));
228        }
229        
230        public boolean isPurchaseOrderOpenForProcessing(PurchaseOrderDocument purchaseOrderDocument) {
231            boolean can = PurchaseOrderStatuses.OPEN.equals(purchaseOrderDocument.getStatusCode());
232            can = can && purchaseOrderDocument.isPurchaseOrderCurrentIndicator() && !purchaseOrderDocument.isPendingActionIndicator();
233            
234            // in addition, check conditions about No In Process PREQ and CM) 
235            if (can) {
236                List<PaymentRequestView> preqViews = purchaseOrderDocument.getRelatedViews().getRelatedPaymentRequestViews();
237                if ( preqViews != null ) {
238                    for (PaymentRequestView preqView : preqViews) {
239                        if (StringUtils.equalsIgnoreCase(preqView.getStatusCode(), PaymentRequestStatuses.IN_PROCESS)) {
240                            return false;
241                        }
242                    }
243                }            
244                List<CreditMemoView> cmViews = purchaseOrderDocument.getRelatedViews().getRelatedCreditMemoViews();
245                if ( cmViews != null ) {
246                    for (CreditMemoView cmView : cmViews) {
247                        if (StringUtils.equalsIgnoreCase(cmView.getCreditMemoStatusCode(), CreditMemoStatuses.IN_PROCESS)) {
248                            return false;
249                        }
250                    }
251                }
252            }
253    
254            return can;
255        }
256    
257        /**
258         * Sets the error map to a new, empty error map before calling saveDocumentNoValidation to save the document.
259         * 
260         * @param document The purchase order document to be saved.
261         */
262        protected void saveDocumentNoValidationUsingClearErrorMap(PurchaseOrderDocument document) {
263            MessageMap errorHolder = GlobalVariables.getMessageMap();
264            GlobalVariables.setMessageMap(new MessageMap());
265            try {
266                purapService.saveDocumentNoValidation(document);
267            }
268            finally {
269                GlobalVariables.setMessageMap(errorHolder);
270            }
271        }
272    
273        /**
274         * Calls the saveDocument method of documentService to save the document.
275         * 
276         * @param document The document to be saved.
277         */
278        protected void saveDocumentStandardSave(PurchaseOrderDocument document) {
279            try {
280                documentService.saveDocument(document);
281            }
282            catch (WorkflowException we) {
283                String errorMsg = "Workflow Error saving document # " + document.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
284                LOG.error(errorMsg, we);
285                throw new RuntimeException(errorMsg, we);
286            }
287        }
288    
289        public PurchasingCapitalAssetItem createCamsItem(PurchasingDocument purDoc, PurApItem purapItem) {
290            PurchasingCapitalAssetItem camsItem = new PurchaseOrderCapitalAssetItem();
291            camsItem.setItemIdentifier(purapItem.getItemIdentifier());
292            // If the system type is INDIVIDUAL then for each of the capital asset items, we need a system attached to it.
293            if (purDoc.getCapitalAssetSystemTypeCode().equals(PurapConstants.CapitalAssetTabStrings.INDIVIDUAL_ASSETS)) {
294                CapitalAssetSystem resultSystem = new PurchaseOrderCapitalAssetSystem();
295                camsItem.setPurchasingCapitalAssetSystem(resultSystem);
296            }
297            camsItem.setPurchasingDocument(purDoc);
298    
299            return camsItem;
300        }
301        
302        public CapitalAssetSystem createCapitalAssetSystem() {
303            CapitalAssetSystem resultSystem = new PurchaseOrderCapitalAssetSystem();
304            return resultSystem;
305        }
306        
307        /**
308         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAutomaticPurchaseOrderDocument(org.kuali.kfs.module.purap.document.RequisitionDocument)
309         */
310        public void createAutomaticPurchaseOrderDocument(RequisitionDocument reqDocument) {
311            String newSessionUserId = KFSConstants.SYSTEM_USER;
312            try {
313                LogicContainer logicToRun = new LogicContainer() {
314                    public Object runLogic(Object[] objects) throws Exception {
315                        RequisitionDocument doc = (RequisitionDocument) objects[0];
316                        // update REQ data
317                        doc.setPurchaseOrderAutomaticIndicator(Boolean.TRUE);
318                        // create PO and populate with default data
319                        PurchaseOrderDocument po = generatePurchaseOrderFromRequisition(doc);
320                        po.setDefaultValuesForAPO();
321                        po.setContractManagerCode(PurapConstants.APO_CONTRACT_MANAGER);
322                        documentService.routeDocument(po, null, null);
323                        
324                        final WorkflowDocumentActions workflowDocumentActions = SpringContext.getBean(WorkflowDocumentActions.class);
325                        workflowDocumentActions.indexDocument(new Long(po.getDocumentNumber()));
326                        return null;
327                    }
328                };
329                purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[] { reqDocument });
330            }
331            catch (WorkflowException e) {
332                String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
333                LOG.error(errorMsg, e);
334                throw new RuntimeException(errorMsg, e);
335            }
336            catch (Exception e) {
337                throw new RuntimeException(e);
338            }
339        }
340    
341        /**
342         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createPurchaseOrderDocument(org.kuali.kfs.module.purap.document.RequisitionDocument,
343         *      java.lang.String, java.lang.Integer)
344         */
345        public PurchaseOrderDocument createPurchaseOrderDocument(RequisitionDocument reqDocument, String newSessionUserId, Integer contractManagerCode) {
346            try {
347                LogicContainer logicToRun = new LogicContainer() {
348                    public Object runLogic(Object[] objects) throws Exception {
349                        RequisitionDocument doc = (RequisitionDocument) objects[0];
350                        PurchaseOrderDocument po = generatePurchaseOrderFromRequisition(doc);
351                        Integer cmCode = (Integer) objects[1];
352                        po.setContractManagerCode(cmCode);
353                        purapService.saveDocumentNoValidation(po);
354                        return po;
355                    }
356                };
357                return (PurchaseOrderDocument) purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[] { reqDocument, contractManagerCode });
358            }
359            catch (WorkflowException e) {
360                String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
361                LOG.error(errorMsg, e);
362                throw new RuntimeException(errorMsg, e);
363            }
364            catch (Exception e) {
365                throw new RuntimeException(e);
366            }
367        }
368    
369        /**
370         * Create Purchase Order and populate with data from Requisition and other default data
371         * 
372         * @param reqDocument The requisition document from which we create the purchase order document.
373         * @return The purchase order document created by this method.
374         * @throws WorkflowException
375         */
376        protected PurchaseOrderDocument generatePurchaseOrderFromRequisition(RequisitionDocument reqDocument) throws WorkflowException {
377            PurchaseOrderDocument poDocument = null;
378            poDocument = (PurchaseOrderDocument) documentService.getNewDocument(PurchaseOrderDocTypes.PURCHASE_ORDER_DOCUMENT);
379            poDocument.populatePurchaseOrderFromRequisition(reqDocument);
380            poDocument.setStatusCode(PurchaseOrderStatuses.IN_PROCESS);
381            poDocument.setPurchaseOrderCurrentIndicator(true);
382            poDocument.setPendingActionIndicator(false);
383    
384            if (RequisitionSources.B2B.equals(poDocument.getRequisitionSourceCode())) {
385                String paramName = PurapParameterConstants.DEFAULT_B2B_VENDOR_CHOICE;
386                String paramValue = parameterService.getParameterValue(PurchaseOrderDocument.class, paramName);
387                poDocument.setPurchaseOrderVendorChoiceCode(paramValue);
388            }
389    
390            if (ObjectUtils.isNotNull(poDocument.getVendorContract())) {
391                poDocument.setVendorPaymentTermsCode(poDocument.getVendorContract().getVendorPaymentTermsCode());
392                poDocument.setVendorShippingPaymentTermsCode(poDocument.getVendorContract().getVendorShippingPaymentTermsCode());
393                poDocument.setVendorShippingTitleCode(poDocument.getVendorContract().getVendorShippingTitleCode());
394            }
395            else {
396                VendorDetail vendor = vendorService.getVendorDetail(poDocument.getVendorHeaderGeneratedIdentifier(), poDocument.getVendorDetailAssignedIdentifier());
397                if (ObjectUtils.isNotNull(vendor)) {
398                    poDocument.setVendorPaymentTermsCode(vendor.getVendorPaymentTermsCode());
399                    poDocument.setVendorShippingPaymentTermsCode(vendor.getVendorShippingPaymentTermsCode());
400                    poDocument.setVendorShippingTitleCode(vendor.getVendorShippingTitleCode());
401                }
402            }
403    
404            if (!PurapConstants.RequisitionSources.B2B.equals(poDocument.getRequisitionSourceCode())) {
405                purapService.addBelowLineItems(poDocument);
406            }
407            poDocument.fixItemReferences();
408    
409            return poDocument;
410        }
411    
412        /**
413         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getInternalPurchasingDollarLimit(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
414         */
415        public KualiDecimal getInternalPurchasingDollarLimit(PurchaseOrderDocument document) {
416            if ((document.getVendorContract() != null) && (document.getContractManager() != null)) {
417                KualiDecimal contractDollarLimit = vendorService.getApoLimitFromContract(document.getVendorContract().getVendorContractGeneratedIdentifier(), document.getChartOfAccountsCode(), document.getOrganizationCode());
418                //FIXME somehow data fields such as contractManagerDelegationDollarLimit in reference object contractManager didn't get retrieved 
419                // (are null) as supposed to be (this happens whether or not proxy is set to true), even though contractManager is not null;
420                // so here we have to manually refresh the contractManager to retrieve the fields 
421                if (document.getContractManager().getContractManagerDelegationDollarLimit() == null) {
422                    document.refreshReferenceObject(PurapPropertyConstants.CONTRACT_MANAGER);
423                }
424                KualiDecimal contractManagerLimit = document.getContractManager().getContractManagerDelegationDollarLimit();
425                if ((contractDollarLimit != null) && (contractManagerLimit != null)) {
426                    if (contractDollarLimit.compareTo(contractManagerLimit) > 0) {
427                        return contractDollarLimit;
428                    }
429                    else {
430                        return contractManagerLimit;
431                    }
432                }
433                else if (contractDollarLimit != null) {
434                    return contractDollarLimit;
435                }
436                else {
437                    return contractManagerLimit;
438                }
439            }
440            else if ((document.getVendorContract() == null) && (document.getContractManager() != null)) {
441                //FIXME As above, here we have to manually refresh the contractManager to retrieve its field
442                if (document.getContractManager().getContractManagerDelegationDollarLimit() == null) {
443                    document.refreshReferenceObject(PurapPropertyConstants.CONTRACT_MANAGER);
444                }
445                return document.getContractManager().getContractManagerDelegationDollarLimit();
446            }
447            else if ((document.getVendorContract() != null) && (document.getContractManager() == null)) {
448                return purapService.getApoLimit(document.getVendorContract().getVendorContractGeneratedIdentifier(), document.getChartOfAccountsCode(), document.getOrganizationCode());
449            }
450            else {
451                String errorMsg = "No internal purchase order dollar limit found for purchase order '" + document.getPurapDocumentIdentifier() + "'.";
452                LOG.warn(errorMsg);
453                return null;
454            }
455        }
456    
457        /**
458         * Loops through the collection of error messages and adding each of them to the error map.
459         * 
460         * @param errorKey The resource key used to retrieve the error text from the error message resource bundle.
461         * @param errors The collection of error messages.
462         */
463        protected void addStringErrorMessagesToErrorMap(String errorKey, Collection<String> errors) {
464            if (ObjectUtils.isNotNull(errors)) {
465                for (String error : errors) {
466                    LOG.error("Adding error message using error key '" + errorKey + "' with text '" + error + "'");
467                    GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, errorKey, error);
468                }
469            }
470        }
471    
472    
473        /**
474         * TODO RELEASE 3 - QUOTE
475         * 
476         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#printPurchaseOrderQuoteRequestsListPDF(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
477         *      java.io.ByteArrayOutputStream)
478         */
479        public boolean printPurchaseOrderQuoteRequestsListPDF(String documentNumber, ByteArrayOutputStream baosPDF) {
480            PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
481            String environment = kualiConfigurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY);
482            Collection<String> generatePDFErrors = printService.generatePurchaseOrderQuoteRequestsListPdf(po, baosPDF);
483    
484            if (generatePDFErrors.size() > 0) {
485                addStringErrorMessagesToErrorMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
486                return false;
487            } else {
488                return true;
489            }
490        }
491    
492        /**
493         * TODO RELEASE 3 - QUOTE
494         * 
495         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#printPurchaseOrderQuotePDF(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
496         *      org.kuali.kfs.module.purap.businessobject.PurchaseOrderVendorQuote, java.io.ByteArrayOutputStream)
497         */
498        public boolean printPurchaseOrderQuotePDF(PurchaseOrderDocument po, PurchaseOrderVendorQuote povq, ByteArrayOutputStream baosPDF) {
499            String environment = kualiConfigurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY);
500            Collection<String> generatePDFErrors = printService.generatePurchaseOrderQuotePdf(po, povq, baosPDF, environment);
501    
502            if (generatePDFErrors.size() > 0) {
503                addStringErrorMessagesToErrorMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
504                return false;
505            }
506            else {
507                return true;
508            }
509        }
510    
511        /**
512         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#performPurchaseOrderFirstTransmitViaPrinting(java.lang.String,
513         *      java.io.ByteArrayOutputStream)
514         */
515        public void performPurchaseOrderFirstTransmitViaPrinting(String documentNumber, ByteArrayOutputStream baosPDF) {
516            PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
517            String environment = kualiConfigurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY);
518            Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdf(po, baosPDF, environment, null);
519            if (!generatePDFErrors.isEmpty()) {
520                addStringErrorMessagesToErrorMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
521                throw new ValidationException("printing purchase order for first transmission failed");
522            }
523            if (ObjectUtils.isNotNull(po.getPurchaseOrderFirstTransmissionTimestamp())) {
524                // should not call this method for first transmission if document has already been transmitted
525                String errorMsg = "Method to perform first transmit was called on document (doc id " + documentNumber + ") with already filled in 'first transmit date'";
526                LOG.error(errorMsg);
527                throw new RuntimeException(errorMsg);
528            }
529            Timestamp currentDate = dateTimeService.getCurrentTimestamp();
530            po.setPurchaseOrderFirstTransmissionTimestamp(currentDate);
531            po.setPurchaseOrderLastTransmitTimestamp(currentDate);
532            po.setOverrideWorkflowButtons(Boolean.FALSE);
533            boolean performedAction = purapWorkflowIntegrationService.takeAllActionsForGivenCriteria(po, "Action taken automatically as part of document initial print transmission", NodeDetailEnum.DOCUMENT_TRANSMISSION.getName(), GlobalVariables.getUserSession().getPerson(), null);
534            if (!performedAction) {
535                Person systemUserPerson = getPersonService().getPersonByPrincipalName(KFSConstants.SYSTEM_USER);
536                purapWorkflowIntegrationService.takeAllActionsForGivenCriteria(po, "Action taken automatically as part of document initial print transmission by user " + GlobalVariables.getUserSession().getPerson().getName(), NodeDetailEnum.DOCUMENT_TRANSMISSION.getName(), systemUserPerson, KFSConstants.SYSTEM_USER);
537            }
538            po.setOverrideWorkflowButtons(Boolean.TRUE);
539            if (!po.getStatusCode().equals(PurapConstants.PurchaseOrderStatuses.OPEN)) {
540                attemptSetupOfInitialOpenOfDocument(po);
541            }
542            purapService.saveDocumentNoValidation(po);
543        }
544    
545        /**
546         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#performPurchaseOrderPreviewPrinting(java.lang.String,
547         *      java.io.ByteArrayOutputStream)
548         */
549        public void performPurchaseOrderPreviewPrinting(String documentNumber, ByteArrayOutputStream baosPDF) {
550            performPrintPurchaseOrderPDFOnly(documentNumber, baosPDF);
551        }
552    
553        /**
554         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#performPrintPurchaseOrderPDFOnly(java.lang.String,
555         *      java.io.ByteArrayOutputStream)
556         */
557        public void performPrintPurchaseOrderPDFOnly(String documentNumber, ByteArrayOutputStream baosPDF) {
558            PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
559            String environment = kualiConfigurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY);
560            Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdf(po, baosPDF, environment, null);
561            if (!generatePDFErrors.isEmpty()) {
562                addStringErrorMessagesToErrorMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
563                throw new ValidationException("printing purchase order for first transmission failed");
564            }
565        }
566    
567        /**
568         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retransmitPurchaseOrderPDF(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
569         *      java.io.ByteArrayOutputStream)
570         */
571        public void retransmitPurchaseOrderPDF(PurchaseOrderDocument po, ByteArrayOutputStream baosPDF) {
572    
573            String environment = kualiConfigurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY);
574            List<PurchaseOrderItem> items = po.getItems();
575            List<PurchaseOrderItem> retransmitItems = new ArrayList<PurchaseOrderItem>();
576            for (PurchaseOrderItem item : items) {
577                if (item.isItemSelectedForRetransmitIndicator()) {
578                    retransmitItems.add(item);
579                }
580            }
581            Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdfForRetransmission(po, baosPDF, environment, retransmitItems);
582    
583            if (generatePDFErrors.size() > 0) {
584                addStringErrorMessagesToErrorMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
585                throw new ValidationException("found errors while trying to print po with doc id " + po.getDocumentNumber());
586            }
587            po.setPurchaseOrderLastTransmitTimestamp(dateTimeService.getCurrentTimestamp());
588            purapService.saveDocumentNoValidation(po);
589        }
590    
591        /**
592         * This method creates a new Purchase Order Document using the given document type based off the given source document. This
593         * method will return null if the source document given is null.<br>
594         * <br> ** THIS METHOD DOES NOT SAVE EITHER THE GIVEN SOURCE DOCUMENT OR THE NEW DOCUMENT CREATED
595         * 
596         * @param sourceDocument - document the new Purchase Order Document should be based off of in terms of data
597         * @param docType - document type of the potential new Purchase Order Document
598         * @return the new Purchase Order Document of the given document type or null if the given source document is null
599         * @throws WorkflowException if a new document cannot be created using the given type
600         */
601        protected PurchaseOrderDocument createPurchaseOrderDocumentFromSourceDocument(PurchaseOrderDocument sourceDocument, String docType) throws WorkflowException {
602            if (ObjectUtils.isNull(sourceDocument)) {
603                String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc that is null";
604                LOG.error(errorMsg);
605                throw new RuntimeException(errorMsg);
606            }
607    
608            PurchaseOrderDocument newPurchaseOrderChangeDocument = (PurchaseOrderDocument) documentService.getNewDocument(docType);
609    
610            Set classesToExclude = new HashSet();
611            Class sourceObjectClass = FinancialSystemTransactionalDocumentBase.class;
612            classesToExclude.add(sourceObjectClass);
613            while (sourceObjectClass.getSuperclass() != null) {
614                sourceObjectClass = sourceObjectClass.getSuperclass();
615                classesToExclude.add(sourceObjectClass);
616            }
617            PurApObjectUtils.populateFromBaseWithSuper(sourceDocument, newPurchaseOrderChangeDocument, PurapConstants.uncopyableFieldsForPurchaseOrder(), classesToExclude);
618            newPurchaseOrderChangeDocument.getDocumentHeader().setDocumentDescription(sourceDocument.getDocumentHeader().getDocumentDescription());
619            newPurchaseOrderChangeDocument.getDocumentHeader().setOrganizationDocumentNumber(sourceDocument.getDocumentHeader().getOrganizationDocumentNumber());
620            newPurchaseOrderChangeDocument.getDocumentHeader().setExplanation(sourceDocument.getDocumentHeader().getExplanation());
621            newPurchaseOrderChangeDocument.setPurchaseOrderCurrentIndicator(false);
622            newPurchaseOrderChangeDocument.setPendingActionIndicator(false);
623    
624            // TODO f2f: what is this doing?
625            // Need to find a way to make the ManageableArrayList to expand and populating the items and
626            // accounts, otherwise it will complain about the account on item 1 is missing.
627            for (PurApItem item : (List<PurApItem>) newPurchaseOrderChangeDocument.getItems()) {
628                item.getSourceAccountingLines().iterator();
629                // we only need to do this once to apply to all items, so we can break out of the loop now
630                SequenceAccessorService sas = SpringContext.getBean(SequenceAccessorService.class);
631                Integer itemIdentifier = sas.getNextAvailableSequenceNumber("PO_ITM_ID", PurApItem.class).intValue(); 
632                item.setItemIdentifier(itemIdentifier); 
633            }
634    
635            updateCapitalAssetRelatedCollections(newPurchaseOrderChangeDocument);
636            newPurchaseOrderChangeDocument.refreshNonUpdateableReferences();
637            
638            return newPurchaseOrderChangeDocument;
639        }
640    
641        protected void updateCapitalAssetRelatedCollections(PurchaseOrderDocument newDocument) {
642     
643            for (PurchasingCapitalAssetItem capitalAssetItem : newDocument.getPurchasingCapitalAssetItems()) {
644                Integer lineNumber = capitalAssetItem.getPurchasingItem().getItemLineNumber();
645                PurApItem newItem = newDocument.getItemByLineNumber(lineNumber.intValue());
646                capitalAssetItem.setItemIdentifier(newItem.getItemIdentifier());
647                capitalAssetItem.setPurchasingDocument(newDocument);
648                capitalAssetItem.setCapitalAssetSystemIdentifier(null);
649                CapitalAssetSystem oldSystem = capitalAssetItem.getPurchasingCapitalAssetSystem();
650                capitalAssetItem.setPurchasingCapitalAssetSystem(new PurchaseOrderCapitalAssetSystem(oldSystem));
651                
652            }
653        }
654        
655        /**
656         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAndSavePotentialChangeDocument(java.lang.String,
657         *      java.lang.String, java.lang.String)
658         */
659        public PurchaseOrderDocument createAndSavePotentialChangeDocument(String documentNumber, String docType, String currentDocumentStatusCode) {
660            PurchaseOrderDocument currentDocument = getPurchaseOrderByDocumentNumber(documentNumber);
661            
662            try {
663                PurchaseOrderDocument newDocument = createPurchaseOrderDocumentFromSourceDocument(currentDocument, docType);
664                
665                if (ObjectUtils.isNotNull(newDocument)) {
666                    newDocument.setStatusCode(PurchaseOrderStatuses.CHANGE_IN_PROCESS);
667                    // set status if needed
668                    if (StringUtils.isNotBlank(currentDocumentStatusCode)) {
669                        purapService.updateStatus(currentDocument, currentDocumentStatusCode);
670                    }
671                    try {
672                        documentService.saveDocument(newDocument, DocumentSystemSaveEvent.class);
673                    }
674                    // if we catch a ValidationException it means the new PO doc found errors
675                    catch (ValidationException ve) {
676                        throw ve;
677                    }
678                    // if no validation exception was thrown then rules have passed and we are ok to edit the current PO
679                    currentDocument.setPendingActionIndicator(true);
680                    
681                    saveDocumentNoValidationUsingClearErrorMap(currentDocument);
682                    
683                    return newDocument;
684                }
685                else {
686                    String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc id " + documentNumber + " returned null for new document";
687                    LOG.error(errorMsg);
688                    throw new RuntimeException(errorMsg);
689                }
690            }
691            catch (WorkflowException we) {
692                String errorMsg = "Workflow Exception caught trying to create and save PO document of type '" + docType + "' using source document with doc id '" + documentNumber + "'";
693                LOG.error(errorMsg, we);
694                throw new RuntimeException(errorMsg, we);
695            }
696        }
697    
698        /**
699         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAndRoutePotentialChangeDocument(java.lang.String,
700         *      java.lang.String, java.lang.String, java.util.List, java.lang.String)
701         */
702        public PurchaseOrderDocument createAndRoutePotentialChangeDocument(String documentNumber, String docType, String annotation, List adhocRoutingRecipients, String currentDocumentStatusCode) {
703            PurchaseOrderDocument currentDocument = getPurchaseOrderByDocumentNumber(documentNumber);
704            purapService.updateStatus(currentDocument, currentDocumentStatusCode);
705            
706            try {
707                PurchaseOrderDocument newDocument = createPurchaseOrderDocumentFromSourceDocument(currentDocument, docType);
708                newDocument.setStatusCode(PurchaseOrderStatuses.CHANGE_IN_PROCESS);
709                if (ObjectUtils.isNotNull(newDocument)) {
710                    try {
711                        // set the pending indictor before routing, so that when routing is done in synch mode, the pending indicator won't be set again after route finishes and cause inconsistency 
712                        currentDocument.setPendingActionIndicator(true);
713                        documentService.routeDocument(newDocument, annotation, adhocRoutingRecipients);
714                    }
715                    // if we catch a ValidationException it means the new PO doc found errors
716                    catch (ValidationException ve) {
717                        //clear the pending indictor if an exception occurs, to leave the existing PO intact
718                        currentDocument.setPendingActionIndicator(false);
719                        saveDocumentNoValidationUsingClearErrorMap(currentDocument);
720                        throw ve;
721                    }
722                    return newDocument;
723                }
724                else {
725                    String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc id " + documentNumber + " returned null for new document";
726                    LOG.error(errorMsg);
727                    throw new RuntimeException(errorMsg);
728                }
729            }
730            catch (WorkflowException we) {
731                String errorMsg = "Workflow Exception caught trying to create and route PO document of type '" + docType + "' using source document with doc id '" + documentNumber + "'";
732                LOG.error(errorMsg, we);
733                throw new RuntimeException(errorMsg, we);
734            }
735        }
736        
737        /**
738         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAndSavePurchaseOrderSplitDocument(java.util.List, java.lang.String, boolean)
739         */
740        public PurchaseOrderSplitDocument createAndSavePurchaseOrderSplitDocument(List<PurchaseOrderItem> newPOItems, PurchaseOrderDocument currentDocument, boolean copyNotes, String splitNoteText) {
741            
742            if (ObjectUtils.isNull(currentDocument)) {
743                String errorMsg = "Attempting to create new PO of type PurchaseOrderSplitDocument from source PO doc that is null";
744                LOG.error(errorMsg);
745                throw new RuntimeException(errorMsg);
746            }
747            String documentNumber = currentDocument.getDocumentNumber();
748            
749            try {
750                // Create the new Split PO document (throws WorkflowException)
751                PurchaseOrderSplitDocument newDocument = (PurchaseOrderSplitDocument)documentService.getNewDocument(PurchaseOrderDocTypes.PURCHASE_ORDER_SPLIT_DOCUMENT);
752    
753                if (ObjectUtils.isNotNull(newDocument)) {
754                    
755                    // Prepare for copying fields over from the current document.
756                    Set<Class> classesToExclude = getClassesToExcludeFromCopy();
757                    Map<String, Class> uncopyableFields = PurapConstants.UNCOPYABLE_FIELDS_FOR_PO;
758                    uncopyableFields.putAll(PurapConstants.uncopyableFieldsForSplitPurchaseOrder());
759                    
760                    // Copy all fields over from the current document except the items and the above-specified fields.
761                    PurApObjectUtils.populateFromBaseWithSuper(currentDocument, newDocument, uncopyableFields, classesToExclude);
762                    newDocument.getDocumentHeader().setDocumentDescription(currentDocument.getDocumentHeader().getDocumentDescription());
763                    newDocument.getDocumentHeader().setOrganizationDocumentNumber(currentDocument.getDocumentHeader().getOrganizationDocumentNumber());   
764                    newDocument.setPurchaseOrderCurrentIndicator(true);
765                    newDocument.setPendingActionIndicator(false);
766                    
767                    // Add in and renumber the items that the new document should have.
768                    newDocument.setItems(newPOItems);
769                    SpringContext.getBean(PurapService.class).addBelowLineItems(newDocument);
770                    newDocument.renumberItems(0);
771                    
772                    newDocument.setPostingYear(currentDocument.getPostingYear());
773                    
774                    if (copyNotes) {
775                        // Copy the old notes, except for the one that contains the split note text.
776                        List<Note> notes = (List<Note>)currentDocument.getBoNotes();
777                        int noteLength = notes.size();
778                        if (noteLength > 0) {
779                            notes.subList(noteLength - 1, noteLength).clear();
780                            for(Note note : notes) {
781                                try {
782                                    Note copyingNote = documentService.createNoteFromDocument(newDocument, note.getNoteText());
783                                    newDocument.addNote(copyingNote);
784                                }
785                                catch (Exception e) {
786                                    throw new RuntimeException(e);
787                                }                           
788                            }
789                        }
790                    }
791                    // Modify the split note text and add the note.
792                    splitNoteText = splitNoteText.substring(splitNoteText.indexOf(":") + 1);
793                    splitNoteText = PurapConstants.PODocumentsStrings.SPLIT_NOTE_PREFIX_NEW_DOC + currentDocument.getPurapDocumentIdentifier() + " : " + splitNoteText;
794                    try {
795                        Note splitNote = documentService.createNoteFromDocument(newDocument, splitNoteText);
796                        newDocument.addNote(splitNote);
797                    }
798                    catch (Exception e) {
799                        throw new RuntimeException(e);
800                    }
801                    newDocument.setStatusCode(PurchaseOrderStatuses.IN_PROCESS);
802                    
803                    //fix references before saving
804                    fixItemReferences(newDocument);
805                    
806                    purapService.saveDocumentNoValidation(newDocument);
807                                       
808                    return newDocument;
809                }
810                else {
811                    String errorMsg = "Attempting to create new PO of type 'PurchaseOrderSplitDocument' from source PO doc id " + documentNumber + " returned null for new document";
812                    LOG.error(errorMsg);
813                    throw new RuntimeException(errorMsg);
814                }            
815            }
816            catch (WorkflowException we) {
817                String errorMsg = "Workflow Exception caught trying to create and save PO document of type PurchaseOrderSplitDocument using source document with doc id '" + documentNumber + "'";
818                LOG.error(errorMsg, we);
819                throw new RuntimeException(errorMsg, we);
820            }
821        }
822        
823        /**
824         * Gets a set of classes to exclude from those whose fields will be copied during a copy operation from one Document to
825         * another.
826         * 
827         * @return A Set<Class> 
828         */
829        protected Set<Class> getClassesToExcludeFromCopy() {
830            Set<Class> classesToExclude = new HashSet<Class>();
831            Class sourceObjectClass = DocumentBase.class;
832            classesToExclude.add(sourceObjectClass);
833            while (sourceObjectClass.getSuperclass() != null) {
834                sourceObjectClass = sourceObjectClass.getSuperclass();
835                classesToExclude.add(sourceObjectClass);
836            }
837            return classesToExclude;
838        }
839    
840        /**
841         * Returns the current route node name.
842         * 
843         * @param wd The KualiWorkflowDocument object whose current route node we're trying to get.
844         * @return The current route node name.
845         * @throws WorkflowException
846         */
847        protected String getCurrentRouteNodeName(KualiWorkflowDocument wd) throws WorkflowException {
848            String[] nodeNames = wd.getNodeNames();
849            if ((nodeNames == null) || (nodeNames.length == 0)) {
850                return null;
851            }
852            else {
853                return nodeNames[0];
854            }
855        }
856    
857        /**
858         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#completePurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
859         */
860        public void completePurchaseOrder(PurchaseOrderDocument po) {
861            LOG.debug("completePurchaseOrder() started");
862            setCurrentAndPendingIndicatorsForApprovedPODocuments(po);
863            setupDocumentForPendingFirstTransmission(po);
864            
865            // check thresholds to see if receiving is required for purchase order
866            if (!po.isReceivingDocumentRequiredIndicator()) {
867                setReceivingRequiredIndicatorForPurchaseOrder(po);
868            }
869    
870            // update the vendor record if the commodity code used on the PO is not already associated with the vendor.
871            updateVendorCommodityCode(po);
872    
873            // PERFORM ANY LOGIC THAT COULD POTENTIALLY CAUSE THE DOCUMENT TO FAIL BEFORE THIS LINE
874            // FOLLOWING LINES COULD INVOLVE TRANSMITTING THE PO TO THE VENDOR WHICH WILL NOT BE REVERSED IN A TRANSACTION ROLLBACK
875            
876            // if the document is set in a Pending Transmission status then don't OPEN the PO just leave it as is
877            if (!PurchaseOrderStatuses.STATUSES_BY_TRANSMISSION_TYPE.values().contains(po.getStatusCode())) {
878                attemptSetupOfInitialOpenOfDocument(po);
879            }
880            else if (PurchaseOrderStatuses.PENDING_CXML.equals(po.getStatusCode())) {
881                completeB2BPurchaseOrder(po);
882            }
883            else if (PurchaseOrderStatuses.PENDING_PRINT.equals(po.getStatusCode())) {
884                //default to using user that routed PO
885                String userToRouteFyi = po.getDocumentHeader().getWorkflowDocument().getRoutedByPrincipalId();
886                if (po.getPurchaseOrderAutomaticIndicator()) {
887                    //if APO, use the user that initiated the requisition
888                    RequisitionDocument req = SpringContext.getBean(RequisitionService.class).getRequisitionById(po.getRequisitionIdentifier());
889                    userToRouteFyi = req.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
890                }
891                
892                try {
893                    //send FYI to user for printing
894                    po.getDocumentHeader().getWorkflowDocument().adHocRouteDocumentToPrincipal(KEWConstants.ACTION_REQUEST_FYI_REQ, po.getDocumentHeader().getWorkflowDocument().getCurrentRouteNodeNames(), "This PO is ready for printing and distribution.", userToRouteFyi, "", true, "PRINT");
895                }
896                catch (WorkflowException e) {
897                    LOG.error("Error sending FYI to user to print PO.", e);
898                    throw new RuntimeException("Error sending FYI to user to print PO.", e);
899                }
900            }
901    
902        }
903    
904        protected boolean completeB2BPurchaseOrder(PurchaseOrderDocument po) {
905            String errors = b2bPurchaseOrderService.sendPurchaseOrder(po);
906            if (StringUtils.isEmpty(errors)) {
907                //PO sent successfully; change status to OPEN
908                attemptSetupOfInitialOpenOfDocument(po);
909                po.setPurchaseOrderLastTransmitTimestamp(dateTimeService.getCurrentTimestamp());
910                return true;
911            }
912            else {
913                //PO transmission failed; record errors and change status to "cxml failed"
914                try {
915                    String noteText = "Unable to transmit the PO for the following reasons:\n" + errors;                
916                    int noteMaxSize = dataDictionaryService.getAttributeMaxLength("Note", "noteText");
917    
918                    // Break up the note into multiple pieces if the note is too large to fit in the database field.
919                    while (noteText.length() > noteMaxSize) {
920                        int fromIndex = 0;
921                        String noteText1 = noteText.substring(0, noteMaxSize);
922                        Note note1 = documentService.createNoteFromDocument(po, noteText1);
923                        documentService.addNoteToDocument(po, note1);
924                        noteText = noteText.substring(noteMaxSize);
925                    }
926    
927                    Note note = documentService.createNoteFromDocument(po, noteText);
928                    documentService.addNoteToDocument(po, note);
929                }
930                catch (Exception e) {
931                    throw new RuntimeException(e);
932                }
933                
934                purapService.updateStatus(po, PurchaseOrderStatuses.CXML_ERROR);
935                return false;
936            }
937        }
938    
939        public void retransmitB2BPurchaseOrder(PurchaseOrderDocument po) {
940            if (completeB2BPurchaseOrder(po)) {
941                GlobalVariables.getMessageList().add(PurapKeyConstants.B2B_PO_RETRANSMIT_SUCCESS);            
942            }
943            else {
944                GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, PurapKeyConstants.B2B_PO_RETRANSMIT_FAILED);
945            }
946            purapService.saveDocumentNoValidation(po);
947        }
948        
949        public boolean canHoldPayment(PurchaseOrderDocument purchaseOrder){
950            
951            if (purchaseOrder.getStatusCode().equals(PurapConstants.PurchaseOrderStatuses.OPEN) && 
952                purchaseOrder.isPurchaseOrderCurrentIndicator() && 
953                !purchaseOrder.isPendingActionIndicator()) {
954                return true;
955            }
956            
957            return false;
958        }
959        
960        public boolean canAmendPurchaseOrder(PurchaseOrderDocument purchaseOrder){
961            boolean canAmend = false;
962            
963            //The other conditions for displaying amend button (apart from the condition about No In Process PREQ and CM) 
964            //are the same as the conditions for displaying the Payment Hold button, so we're reusing that method here.
965            if (canHoldPayment(purchaseOrder)) {
966                
967               canAmend = true;
968    
969               if (purchaseOrder.getRelatedViews().getRelatedPaymentRequestViews() != null && 
970                   purchaseOrder.getRelatedViews().getRelatedPaymentRequestViews().size() > 0) {
971                   
972                   for (PaymentRequestView preq : purchaseOrder.getRelatedViews().getRelatedPaymentRequestViews()) {
973                       if (StringUtils.equalsIgnoreCase(preq.getStatusCode(), PaymentRequestStatuses.IN_PROCESS)) {
974                           return false;
975                       }
976                   }
977                   
978               }
979                
980                if (purchaseOrder.getRelatedViews().getRelatedCreditMemoViews() != null && 
981                    purchaseOrder.getRelatedViews().getRelatedCreditMemoViews().size() > 0) {
982                    
983                    for (CreditMemoView cm : purchaseOrder.getRelatedViews().getRelatedCreditMemoViews()) {
984                        if (StringUtils.equalsIgnoreCase(cm.getCreditMemoStatusCode(), CreditMemoStatuses.IN_PROCESS)) {
985                            return false;
986                        }
987                    }
988                }
989            }
990            
991            return canAmend;
992        }
993        
994        public void completePurchaseOrderAmendment(PurchaseOrderDocument poa) {
995            LOG.debug("completePurchaseOrderAmendment() started");
996            
997            setCurrentAndPendingIndicatorsForApprovedPODocuments(poa);
998    
999            if (SpringContext.getBean(PaymentRequestService.class).hasActivePaymentRequestsForPurchaseOrder(poa.getPurapDocumentIdentifier())) {
1000                poa.setPaymentRequestPositiveApprovalIndicator(true);
1001                poa.setReceivingDocumentRequiredIndicator(false);
1002            }
1003            // check thresholds to see if receiving is required for purchase order amendment
1004            else if (!poa.isReceivingDocumentRequiredIndicator()) {
1005                setReceivingRequiredIndicatorForPurchaseOrder(poa);                       
1006            }
1007    
1008            // if unordered items have been added to the PO then send an FYI to all fiscal officers
1009            if (hasNewUnorderedItem(poa)) {
1010                sendFyiForNewUnorderedItems(poa);
1011            }
1012    
1013        }
1014    
1015        /**
1016         * If there are commodity codes on the items on the PurchaseOrderDocument that
1017         * haven't existed yet on the vendor that the PurchaseOrderDocument is using,
1018         * then we will spawn a new VendorDetailMaintenanceDocument automatically to
1019         * update the vendor with the commodity codes that aren't already existing on
1020         * the vendor.
1021         *
1022         * @param po The PurchaseOrderDocument containing the vendor that we want to update.
1023         */
1024        public void updateVendorCommodityCode(PurchaseOrderDocument po) {
1025            String noteText = "";
1026            VendorDetail oldVendorDetail = po.getVendorDetail();
1027            VendorDetail newVendorDetail = updateVendorWithMissingCommodityCodesIfNecessary(po);
1028            if (newVendorDetail != null) {
1029                try {
1030                    // spawn a new vendor maintenance document to add the note
1031                    MaintenanceDocument vendorMaintDoc = null;
1032                    try {
1033                        vendorMaintDoc = (MaintenanceDocument) documentService.getNewDocument("PVEN");
1034                        vendorMaintDoc.getDocumentHeader().setDocumentDescription("Automatically spawned from PO");
1035                        vendorMaintDoc.getOldMaintainableObject().setBusinessObject(oldVendorDetail);
1036                        vendorMaintDoc.getNewMaintainableObject().setBusinessObject(newVendorDetail);
1037                        vendorMaintDoc.getNewMaintainableObject().setMaintenanceAction(KFSConstants.MAINTENANCE_EDIT_ACTION);
1038                        vendorMaintDoc.getNewMaintainableObject().setDocumentNumber(vendorMaintDoc.getDocumentNumber());
1039                        boolean isVendorLocked = checkForLockingDocument(vendorMaintDoc);
1040                        if (!isVendorLocked) {
1041                            //validating vendor doc to capture exception before trying to route which if exception happens in docService, then PO will fail too
1042                            vendorMaintDoc.validateBusinessRules(new RouteDocumentEvent(vendorMaintDoc));
1043                            addNoteForCommodityCodeToVendor(vendorMaintDoc.getNewMaintainableObject(), vendorMaintDoc.getDocumentNumber(), po.getPurapDocumentIdentifier());
1044                            documentService.routeDocument(vendorMaintDoc, null, null);
1045                        }
1046                        else {
1047                            // Add a note to the PO to tell the users that we can't automatically update the vendor because it's locked.
1048                            noteText = "Unable to automatically update vendor because it is locked";
1049                        }
1050                    }
1051                    catch (Exception e) {
1052                        if (ObjectUtils.isNull(vendorMaintDoc)) {
1053                            noteText = "Unable to create a new VendorDetailMaintenanceDocument to update the vendor with new commodity codes";
1054                        }
1055                        else {
1056                            noteText = "Unable to route a new VendorDetailMaintenanceDocument to update the vendor with new commodity codes";
1057                        }
1058                    }
1059                    finally {
1060                        if (StringUtils.isNotBlank(noteText)) {
1061                            // update on purchase order notes
1062                            Note note = documentService.createNoteFromDocument(po, noteText);
1063                            documentService.addNoteToDocument(po, note);
1064                            noteService.save(note);
1065                        }
1066                    }
1067                }
1068                catch (Exception e) {
1069                    LOG.error("updateVendorCommodityCode() unable to add a note(" + noteText + ") to PO document " + po.getDocumentNumber());
1070                }
1071            }
1072        }
1073    
1074        /**
1075         * Creates a note to be added to the Vendor Maintenance Document which is spawned
1076         * from the PurchaseOrderDocument.
1077         * 
1078         * @param maintainable
1079         * @param documentNumber
1080         * @param poID
1081         */
1082        protected void addNoteForCommodityCodeToVendor(Maintainable maintainable, String documentNumber, Integer poID) {
1083            Note newBONote = new Note();
1084            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.");
1085            try {
1086                newBONote = noteService.createNote(newBONote, maintainable.getBusinessObject());
1087            }
1088            catch (Exception e) {
1089                throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e);
1090            }
1091            maintainable.getBusinessObject().getBoNotes().add(newBONote);        
1092        }
1093        
1094        /**
1095         * Checks whether the vendor is currently locked.
1096         * 
1097         * @param document The MaintenanceDocument containing the vendor.
1098         * @return boolean true if the vendor is currently locked and false otherwise.
1099         */
1100        protected boolean checkForLockingDocument(MaintenanceDocument document) {
1101            String blockingDocId = maintenanceDocumentService.getLockingDocumentId(document);
1102            if (StringUtils.isBlank(blockingDocId)) {
1103                return false;
1104            }
1105            else {
1106                return true;
1107            }
1108        }
1109        
1110        /**
1111         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#updateVendorWithMissingCommodityCodesIfNecessary(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1112         */
1113        public VendorDetail updateVendorWithMissingCommodityCodesIfNecessary(PurchaseOrderDocument po) {
1114            List<CommodityCode> result = new ArrayList<CommodityCode>();
1115            boolean foundDefault = false;
1116            VendorDetail vendor = (VendorDetail)ObjectUtils.deepCopy(po.getVendorDetail());
1117            for (PurchaseOrderItem item : (List<PurchaseOrderItem>)po.getItems()) {
1118                //Only check on commodity codes if the item is active and is above the line item type.
1119                if (item.getItemType().isLineItemIndicator() && item.isItemActiveIndicator()) {
1120                    CommodityCode cc = item.getCommodityCode();
1121                    if (cc != null && !result.contains(cc)) {
1122                        List<VendorCommodityCode> vendorCommodityCodes = po.getVendorDetail().getVendorCommodities();
1123                        boolean foundMatching = false;
1124                        for (VendorCommodityCode vcc : vendorCommodityCodes) {
1125                            if (vcc.getCommodityCode().getPurchasingCommodityCode().equals(cc.getPurchasingCommodityCode())) {
1126                                foundMatching = true;
1127                            }
1128                            if (!foundDefault && vcc.isCommodityDefaultIndicator()) {
1129                                foundDefault = true;
1130                            }
1131                        }
1132                        if (!foundMatching) {
1133                            result.add(cc);
1134                            VendorCommodityCode vcc = new VendorCommodityCode(vendor.getVendorHeaderGeneratedIdentifier(), vendor.getVendorDetailAssignedIdentifier(), cc, true);
1135                            vcc.setActive(true);
1136                            if (!foundDefault) {
1137                                vcc.setCommodityDefaultIndicator(true);
1138                                foundDefault = true;
1139                            }
1140                            vendor.getVendorCommodities().add(vcc);
1141                        }
1142                    }
1143                }
1144            }
1145            if (result.size() > 0) {
1146                //We also have to add to the old vendor detail's vendorCommodities if we're adding to the new
1147                //vendor detail's vendorCommodities.
1148                for (int i=0; i<result.size(); i++) {
1149                    po.getVendorDetail().getVendorCommodities().add(new VendorCommodityCode());
1150                }
1151                return vendor;
1152            }
1153            else {
1154                return null;
1155            }
1156        }
1157        
1158        /**
1159         * Update the purchase order document with the appropriate status for pending first transmission based on the transmission type.
1160         * 
1161         * @param po The purchase order document whose status to be updated.
1162         */
1163        protected void setupDocumentForPendingFirstTransmission(PurchaseOrderDocument po) {
1164            if (POTransmissionMethods.PRINT.equals(po.getPurchaseOrderTransmissionMethodCode()) || POTransmissionMethods.FAX.equals(po.getPurchaseOrderTransmissionMethodCode()) || POTransmissionMethods.ELECTRONIC.equals(po.getPurchaseOrderTransmissionMethodCode())) {
1165                String newStatusCode = PurchaseOrderStatuses.STATUSES_BY_TRANSMISSION_TYPE.get(po.getPurchaseOrderTransmissionMethodCode());
1166                LOG.debug("setupDocumentForPendingFirstTransmission() Purchase Order Transmission Type is '" + po.getPurchaseOrderTransmissionMethodCode() + "' setting status to '" + newStatusCode + "'");
1167                purapService.updateStatus(po, newStatusCode);
1168            }
1169        }
1170    
1171        /**
1172         * 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
1173         * and update the status to OPEN, then save the purchase order.
1174         * 
1175         * @param po The purchase order document whose initial open date and status we want to update.
1176         */
1177        protected void attemptSetupOfInitialOpenOfDocument(PurchaseOrderDocument po) {
1178            LOG.debug("attemptSetupOfInitialOpenOfDocument() started using document with doc id " + po.getDocumentNumber());
1179    
1180            if (!PurchaseOrderStatuses.OPEN.equals(po.getStatusCode())) {
1181                if (ObjectUtils.isNull(po.getPurchaseOrderInitialOpenTimestamp())) {
1182                    LOG.debug("attemptSetupOfInitialOpenOfDocument() setting initial open date on document");
1183                    po.setPurchaseOrderInitialOpenTimestamp(dateTimeService.getCurrentTimestamp());
1184                }
1185                else {
1186                    throw new RuntimeException("Document does not have status code '" + PurchaseOrderStatuses.OPEN + "' on it but value of initial open date is " + po.getPurchaseOrderInitialOpenTimestamp());
1187                }
1188                LOG.info("attemptSetupOfInitialOpenOfDocument() Setting po document id " + po.getDocumentNumber() + " status from '" + po.getStatusCode() + "' to '" + PurchaseOrderStatuses.OPEN + "'");
1189                purapService.updateStatus(po, PurchaseOrderStatuses.OPEN);
1190                //no need to save here because calling class should handle the save if needed
1191            }
1192            else {
1193                LOG.error("attemptSetupOfInitialOpenOfDocument() Found document already in '" + PurchaseOrderStatuses.OPEN + "' status for PO#" + po.getPurapDocumentIdentifier() + "; will not change or update");
1194            }
1195        }
1196    
1197        /**
1198         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getCurrentPurchaseOrder(java.lang.Integer)
1199         */
1200        public PurchaseOrderDocument getCurrentPurchaseOrder(Integer id) {
1201            return getPurchaseOrderByDocumentNumber(purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(id));
1202            //TODO hjs: code review (why is this DB call so complicated?  wouldn't this method be cleaner and less db calls?)
1203    //        return purchaseOrderDao.getCurrentPurchaseOrder(id);
1204        }
1205    
1206        /**
1207         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getPurchaseOrderByDocumentNumber(java.lang.String)
1208         */
1209        public PurchaseOrderDocument getPurchaseOrderByDocumentNumber(String documentNumber) {
1210            if (ObjectUtils.isNotNull(documentNumber)) {
1211                try {
1212                    PurchaseOrderDocument doc = (PurchaseOrderDocument) documentService.getByDocumentHeaderId(documentNumber);
1213                    if (ObjectUtils.isNotNull(doc)) {
1214                        KualiWorkflowDocument workflowDocument = doc.getDocumentHeader().getWorkflowDocument();
1215                        doc.refreshReferenceObject(KFSPropertyConstants.DOCUMENT_HEADER);
1216                        doc.getDocumentHeader().setWorkflowDocument(workflowDocument);
1217                    }
1218                    return doc;
1219                }
1220                catch (WorkflowException e) {
1221                    String errorMessage = "Error getting purchase order document from document service";
1222                    LOG.error("getPurchaseOrderByDocumentNumber() " + errorMessage, e);
1223                    throw new RuntimeException(errorMessage, e);
1224                }
1225            }
1226            return null;
1227        }
1228    
1229        /**
1230         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getOldestPurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
1231         *      org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1232         */
1233        public PurchaseOrderDocument getOldestPurchaseOrder(PurchaseOrderDocument po, PurchaseOrderDocument documentBusinessObject) {
1234            LOG.debug("entering getOldestPO(PurchaseOrderDocument)");
1235            if (ObjectUtils.isNotNull(po)) {
1236                String oldestDocumentNumber = purchaseOrderDao.getOldestPurchaseOrderDocumentNumber(po.getPurapDocumentIdentifier());
1237                if (StringUtils.equals(oldestDocumentNumber, po.getDocumentNumber())) {
1238                    // manually set bo notes - this is mainly done for performance reasons (preferably we could call
1239                    // retrieve doc notes in PersistableBusinessObjectBase but that is private)
1240                    updateNotes(po, documentBusinessObject);
1241                    LOG.debug("exiting getOldestPO(PurchaseOrderDocument)");
1242                    return po;
1243                }
1244                else {
1245                    PurchaseOrderDocument oldestPurchaseOrder = getPurchaseOrderByDocumentNumber(oldestDocumentNumber);
1246                    updateNotes(oldestPurchaseOrder, documentBusinessObject);
1247                    LOG.debug("exiting getOldestPO(PurchaseOrderDocument)");
1248                    return oldestPurchaseOrder;
1249                }
1250            }
1251            return null;
1252        }
1253    
1254        /**
1255         * 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
1256         * saved to the db previously), get the notes of the purchase order from the database, fix the notes' fields by calling the
1257         * fixDbNoteFields, then set the notes to the purchase order. Otherwise (I think this means if it's a new purchase order), set
1258         * the notes of this purchase order to be the notes of the documentBusinessObject.
1259         * 
1260         * @param po The current purchase order.
1261         * @param documentBusinessObject The oldest purchase order whose purapDocumentIdentifier is the same as the po's
1262         *        purapDocumentIdentifier.
1263         */
1264        protected void updateNotes(PurchaseOrderDocument po, PurchaseOrderDocument documentBusinessObject) {
1265            if (ObjectUtils.isNotNull(documentBusinessObject)) {
1266                if (ObjectUtils.isNotNull(po.getObjectId())) {
1267                    List<Note> dbNotes = noteService.getByRemoteObjectId(po.getObjectId());
1268                    // need to set fields that are not ojb managed (i.e. the notes on the documentBusinessObject may have been modified
1269                    // independently of the ones in the db)
1270                    fixDbNoteFields(documentBusinessObject, dbNotes);
1271                    po.setBoNotes(dbNotes);
1272                }
1273                else {
1274                    po.setBoNotes(documentBusinessObject.getBoNotes());
1275                }
1276            }
1277        }
1278    
1279        /**
1280         * This method fixes non ojb managed missing fields from the db
1281         * 
1282         * @param documentBusinessObject The oldest purchase order whose purapDocumentIdentifier is the same as the po's
1283         *        purapDocumentIdentifier.
1284         * @param dbNotes The notes of the purchase order obtained from the database.
1285         */
1286        protected void fixDbNoteFields(PurchaseOrderDocument documentBusinessObject, List<Note> dbNotes) {
1287            for (int i = 0; i < dbNotes.size(); i++) {
1288                Note dbNote = dbNotes.get(i);
1289                List<Note> currentNotes = (List<Note>) documentBusinessObject.getBoNotes();
1290                if (i < currentNotes.size()) {
1291                    Note currentNote = (currentNotes).get(i);
1292                    // set the fyi from the current note if not empty
1293                    AdHocRouteRecipient fyiNoteRecipient = currentNote.getAdHocRouteRecipient();
1294                    if (ObjectUtils.isNotNull(fyiNoteRecipient)) {
1295                        dbNote.setAdHocRouteRecipient(fyiNoteRecipient);
1296                    }
1297                }
1298            }
1299        }
1300    
1301        /**
1302         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getPurchaseOrderNotes(java.lang.Integer)
1303         */
1304        public ArrayList<Note> getPurchaseOrderNotes(Integer id) {
1305            ArrayList notes = new TypedArrayList(Note.class);
1306            PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(purchaseOrderDao.getOldestPurchaseOrderDocumentNumber(id));
1307            if (ObjectUtils.isNotNull(po)) {
1308                notes = noteService.getByRemoteObjectId(po.getObjectId());
1309            }
1310            return notes;
1311        }
1312    
1313        /**
1314         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForApprovedPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1315         */
1316        public void setCurrentAndPendingIndicatorsForApprovedPODocuments(PurchaseOrderDocument newPO) {
1317            // Get the "current PO" that's in the database, i.e. the PO row that contains current indicator = Y
1318            PurchaseOrderDocument oldPO = getCurrentPurchaseOrder(newPO.getPurapDocumentIdentifier());
1319    
1320            // If the document numbers between the oldPO and the newPO are different, then this is a PO change document.
1321            if (!oldPO.getDocumentNumber().equals(newPO.getDocumentNumber())) {
1322                // First, we set the indicators for the oldPO to : Current = N and Pending = N
1323                oldPO.setPurchaseOrderCurrentIndicator(false);
1324                oldPO.setPendingActionIndicator(false);
1325    
1326                // set the status and status history of the oldPO to retired version
1327                purapService.updateStatus(oldPO, PurapConstants.PurchaseOrderStatuses.RETIRED_VERSION);
1328    
1329                saveDocumentNoValidationUsingClearErrorMap(oldPO);
1330            }
1331    
1332            // Now, we set the "new PO" indicators so that Current = Y and Pending = N
1333            newPO.setPurchaseOrderCurrentIndicator(true);
1334            newPO.setPendingActionIndicator(false);
1335        }
1336    
1337        /**
1338         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedChangePODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1339         */
1340        public void setCurrentAndPendingIndicatorsForDisapprovedChangePODocuments(PurchaseOrderDocument newPO) {
1341            updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.OPEN);
1342        }
1343    
1344        /**
1345         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledChangePODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1346         */
1347        public void setCurrentAndPendingIndicatorsForCancelledChangePODocuments(PurchaseOrderDocument newPO) {
1348            updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.OPEN);
1349        }
1350    
1351        /**
1352         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledReopenPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1353         */
1354        public void setCurrentAndPendingIndicatorsForCancelledReopenPODocuments(PurchaseOrderDocument newPO) {
1355            updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.CLOSED);
1356        }
1357    
1358        /**
1359         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedReopenPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1360         */
1361        public void setCurrentAndPendingIndicatorsForDisapprovedReopenPODocuments(PurchaseOrderDocument newPO) {
1362            updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.CLOSED);
1363        }
1364    
1365        /**
1366         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledRemoveHoldPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1367         */
1368        public void setCurrentAndPendingIndicatorsForCancelledRemoveHoldPODocuments(PurchaseOrderDocument newPO) {
1369            updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.PAYMENT_HOLD);
1370        }
1371    
1372        /**
1373         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedRemoveHoldPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1374         */
1375        public void setCurrentAndPendingIndicatorsForDisapprovedRemoveHoldPODocuments(PurchaseOrderDocument newPO) {
1376            updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.PAYMENT_HOLD);
1377        }
1378    
1379        /**
1380         * Update the statuses of both the old purchase order and the new purchase orders, then save the old and the new purchase
1381         * orders.
1382         * 
1383         * @param newPO The new change purchase order document (e.g. the PurchaseOrderAmendmentDocument that was resulted from the user
1384         *        clicking on the amend button).
1385         * @param newPOStatus The status to be set on the new change purchase order document.
1386         * @param oldPOStatus The status to be set on the existing (old) purchase order document.
1387         */
1388        protected void updateCurrentDocumentForNoPendingAction(PurchaseOrderDocument newPO, String newPOStatus, String oldPOStatus) {
1389            // Get the "current PO" that's in the database, i.e. the PO row that contains current indicator = Y
1390            PurchaseOrderDocument oldPO = getCurrentPurchaseOrder(newPO.getPurapDocumentIdentifier());
1391            // Set the Pending indicator for the oldPO to N
1392            oldPO.setPendingActionIndicator(false);
1393            purapService.updateStatus(oldPO, oldPOStatus);
1394            purapService.updateStatus(newPO, newPOStatus);
1395            saveDocumentNoValidationUsingClearErrorMap(oldPO);
1396            saveDocumentNoValidationUsingClearErrorMap(newPO);
1397        }
1398    
1399        public ArrayList<PurchaseOrderQuoteStatus> getPurchaseOrderQuoteStatusCodes() {
1400            ArrayList poQuoteStatuses = new TypedArrayList(PurchaseOrderQuoteStatus.class);
1401            poQuoteStatuses = (ArrayList) businessObjectService.findAll(PurchaseOrderQuoteStatus.class);
1402            return poQuoteStatuses;
1403        }
1404    
1405        public void setReceivingRequiredIndicatorForPurchaseOrder(PurchaseOrderDocument po) {
1406            ThresholdHelper thresholdHelper = new ThresholdHelper(po);
1407            boolean result = thresholdHelper.isReceivingDocumentRequired();
1408            if (result) {
1409                ThresholdSummary thresholdSummary = thresholdHelper.getThresholdSummary();
1410                ReceivingThreshold receivingThreshold = thresholdHelper.getReceivingThreshold();
1411                po.setReceivingDocumentRequiredIndicator(true);
1412                
1413                String notetxt = "Receiving is set to be required because the threshold summary with a total amount of " + thresholdSummary.getTotalAmount();
1414                notetxt += " exceeds the receiving threshold of " + receivingThreshold.getThresholdAmount();            
1415                notetxt += " with respect to the threshold criteria "; 
1416                
1417                if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART){
1418                    notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
1419                } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_ACCOUNTTYPE){
1420                    notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();       
1421                    notetxt += " - Account Type " + receivingThreshold.getAccountTypeCode();                       
1422                } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_SUBFUND){
1423                    notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();  
1424                    notetxt += " - Sub-Fund " + receivingThreshold.getSubFundGroupCode();       
1425                } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_COMMODITYCODE){
1426                    notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();      
1427                    notetxt += " - Commodity Code " + receivingThreshold.getPurchasingCommodityCode();
1428                } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_OBJECTCODE){
1429                    notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();     
1430                    notetxt += " - Object code " + receivingThreshold.getFinancialObjectCode();
1431                } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_ORGANIZATIONCODE){
1432                    notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();    
1433                    notetxt += " - Organization " + receivingThreshold.getOrganizationCode();
1434                } else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_VENDOR){
1435                    notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();  
1436                    notetxt += " - Vendor " + receivingThreshold.getVendorNumber();
1437                }
1438    
1439                try { 
1440                    Note note = documentService.createNoteFromDocument(po, notetxt);
1441                    documentService.addNoteToDocument(po, note);
1442                    noteService.save(note);
1443                }
1444                catch (Exception e) {
1445                    throw new RuntimeException(e);
1446                }
1447            }
1448        }
1449    
1450        /**
1451         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#hasNewUnorderedItem(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
1452         */
1453        public boolean hasNewUnorderedItem(PurchaseOrderDocument po){
1454            
1455            boolean itemAdded = false;
1456            
1457            for(PurchaseOrderItem poItem: (List<PurchaseOrderItem>)po.getItems()){
1458                //only check, active, above the line, unordered items
1459                if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode()) ) {
1460                    
1461                    //if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1462                    if( poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(po.getPurapDocumentIdentifier()) )){
1463                        itemAdded = true;
1464                        break;    
1465                    }                
1466                }
1467            }
1468            
1469            return itemAdded;
1470        }
1471        
1472        public boolean isNewUnorderedItem(PurchaseOrderItem poItem){
1473            
1474            boolean itemAdded = false;
1475            
1476            //only check, active, above the line, unordered items
1477            if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode()) ) {
1478                
1479                //if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1480                if( poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(poItem.getPurchaseOrder().getPurapDocumentIdentifier()) )){
1481                    itemAdded = true;                 
1482                }                
1483            }
1484            
1485            return itemAdded;
1486        }
1487    
1488        public boolean isNewItemForAmendment(PurchaseOrderItem poItem){
1489            
1490            boolean itemAdded = false;
1491            
1492            //only check, active, above the line, unordered items
1493            if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator()) {
1494                
1495                //if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1496                if( poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(poItem.getPurchaseOrder().getPurapDocumentIdentifier()) )){
1497                    itemAdded = true;                 
1498                }                
1499            }
1500            
1501            return itemAdded;
1502        }
1503        
1504        /**
1505         * Sends an FYI to fiscal officers for new unordered items.
1506         * 
1507         * @param po
1508         */
1509        protected void sendFyiForNewUnorderedItems(PurchaseOrderDocument po){
1510    
1511            List<AdHocRoutePerson> fyiList = createFyiFiscalOfficerListForNewUnorderedItems(po);
1512            String annotation = "Notification of New Unordered Items for Purchase Order" + po.getPurapDocumentIdentifier() + "(document id " + po.getDocumentNumber() + ")";
1513            String responsibilityNote = "Purchase Order Amendment Routed By User";
1514            
1515            for(AdHocRoutePerson adHocPerson: fyiList){
1516                try{
1517                    po.appSpecificRouteDocumentToUser(
1518                            po.getDocumentHeader().getWorkflowDocument(),
1519                            adHocPerson.getId(),
1520                            annotation,
1521                            responsibilityNote);
1522                }catch (WorkflowException e) {
1523                    throw new RuntimeException("Error routing fyi for document with id " + po.getDocumentNumber(), e);
1524                }
1525    
1526            }
1527        }
1528        
1529        /**
1530         * Creates a list of fiscal officers for new unordered items added to a purchase order.
1531         * 
1532         * @param po
1533         * @return
1534         */
1535        protected List<AdHocRoutePerson> createFyiFiscalOfficerListForNewUnorderedItems(PurchaseOrderDocument po){
1536    
1537            List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
1538            Map fiscalOfficers = new HashMap();
1539            AdHocRoutePerson adHocRoutePerson = null;
1540            
1541            for(PurchaseOrderItem poItem: (List<PurchaseOrderItem>)po.getItems()){
1542                //only check, active, above the line, unordered items
1543                if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode()) ) {
1544                    
1545                    //if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
1546                    if( poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(po.getPurapDocumentIdentifier()) )){
1547    
1548                        // loop through accounts and pull off fiscal officer
1549                        for(PurApAccountingLine account : poItem.getSourceAccountingLines()){
1550    
1551                            //check for dupes of fiscal officer
1552                            if( fiscalOfficers.containsKey(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName()) == false ){
1553                            
1554                                //add fiscal officer to list
1555                                fiscalOfficers.put(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName(), account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
1556                                
1557                                //create AdHocRoutePerson object and add to list
1558                                adHocRoutePerson = new AdHocRoutePerson();
1559                                adHocRoutePerson.setId(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
1560                                adHocRoutePerson.setActionRequested(KFSConstants.WORKFLOW_FYI_REQUEST);
1561                                adHocRoutePersons.add(adHocRoutePerson);
1562                            }
1563                        }
1564                    }                
1565                }
1566            }
1567    
1568            return adHocRoutePersons;
1569        }
1570        
1571        /**
1572         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#categorizeItemsForSplit(java.util.List)
1573         */
1574        public HashMap<String, List<PurchaseOrderItem>> categorizeItemsForSplit(List<PurchaseOrderItem> items) {
1575            HashMap<String, List<PurchaseOrderItem>> movingOrNot =  new HashMap<String, List<PurchaseOrderItem>>(3);
1576            List<PurchaseOrderItem> movingPOItems = new TypedArrayList(PurchaseOrderItem.class);
1577            List<PurchaseOrderItem> remainingPOItems = new TypedArrayList(PurchaseOrderItem.class);
1578            List<PurchaseOrderItem> remainingPOLineItems = new TypedArrayList(PurchaseOrderItem.class);
1579            for (PurchaseOrderItem item : items) {
1580                if(item.isMovingToSplit()) {
1581                    movingPOItems.add(item);
1582                }          
1583                else {
1584                    remainingPOItems.add(item);
1585                    if (item.getItemType().isLineItemIndicator()) {
1586                        remainingPOLineItems.add(item);
1587                    }
1588                }
1589            }
1590            movingOrNot.put(PODocumentsStrings.ITEMS_MOVING_TO_SPLIT, movingPOItems);
1591            movingOrNot.put(PODocumentsStrings.ITEMS_REMAINING, remainingPOItems);
1592            movingOrNot.put(PODocumentsStrings.LINE_ITEMS_REMAINING, remainingPOLineItems);
1593            return movingOrNot;
1594        }
1595    
1596        /**
1597         * @see org.kuali.module.purap.service.PurchaseOrderService#populateQuoteWithVendor(java.lang.Integer, java.lang.Integer, java.lang.String)
1598         */
1599        public PurchaseOrderVendorQuote populateQuoteWithVendor(Integer headerId, Integer detailId, String documentNumber) {
1600            VendorDetail vendor = vendorService.getVendorDetail(headerId, detailId);
1601            updateDefaultVendorAddress(vendor);
1602            PurchaseOrderVendorQuote newPOVendorQuote = populateAddressForPOVendorQuote(vendor, documentNumber);
1603    
1604            //Set the vendorPhoneNumber on the quote to be the first "phone number" type phone
1605            //found on the list. If there's no "phone number" type found, the quote's 
1606            //vendorPhoneNumber will be blank regardless of any other types of phone found on the list.
1607            for (VendorPhoneNumber phone : vendor.getVendorPhoneNumbers()) {
1608                if (VendorConstants.PhoneTypes.PHONE.equals(phone.getVendorPhoneTypeCode())) {
1609                    newPOVendorQuote.setVendorPhoneNumber(phone.getVendorPhoneNumber());
1610                    break;
1611                }
1612            }
1613            
1614            return newPOVendorQuote;
1615        }
1616        
1617        /**
1618         * Creates the new PurchaseOrderVendorQuote and populate the address fields for it.
1619         *
1620         * @param newVendor       The VendorDetail object from which we obtain the values for the address fields.
1621         * @param documentNumber  The documentNumber of the PurchaseOrderDocument containing the PurchaseOrderVendorQuote.
1622         * @return
1623         */
1624        protected PurchaseOrderVendorQuote populateAddressForPOVendorQuote(VendorDetail newVendor, String documentNumber) {
1625            PurchaseOrderVendorQuote newPOVendorQuote = new PurchaseOrderVendorQuote();
1626            newPOVendorQuote.setVendorName(newVendor.getVendorName());
1627            newPOVendorQuote.setVendorHeaderGeneratedIdentifier(newVendor.getVendorHeaderGeneratedIdentifier());
1628            newPOVendorQuote.setVendorDetailAssignedIdentifier(newVendor.getVendorDetailAssignedIdentifier());
1629            newPOVendorQuote.setDocumentNumber(documentNumber);
1630            boolean foundAddress = false;
1631            for (VendorAddress address : newVendor.getVendorAddresses()) {
1632                if (AddressTypes.QUOTE.equals(address.getVendorAddressTypeCode())) {
1633                    newPOVendorQuote.setVendorCityName(address.getVendorCityName());
1634                    newPOVendorQuote.setVendorCountryCode(address.getVendorCountryCode());
1635                    newPOVendorQuote.setVendorLine1Address(address.getVendorLine1Address());
1636                    newPOVendorQuote.setVendorLine2Address(address.getVendorLine2Address());
1637                    newPOVendorQuote.setVendorPostalCode(address.getVendorZipCode());
1638                    newPOVendorQuote.setVendorStateCode(address.getVendorStateCode());
1639                    newPOVendorQuote.setVendorFaxNumber(address.getVendorFaxNumber());
1640                    foundAddress = true;
1641                    break;
1642                }
1643            }
1644            if (!foundAddress) {
1645                newPOVendorQuote.setVendorCityName(newVendor.getDefaultAddressCity());
1646                newPOVendorQuote.setVendorCountryCode(newVendor.getDefaultAddressCountryCode());
1647                newPOVendorQuote.setVendorLine1Address(newVendor.getDefaultAddressLine1());
1648                newPOVendorQuote.setVendorLine2Address(newVendor.getDefaultAddressLine2());
1649                newPOVendorQuote.setVendorPostalCode(newVendor.getDefaultAddressPostalCode());
1650                newPOVendorQuote.setVendorStateCode(newVendor.getDefaultAddressStateCode());
1651                newPOVendorQuote.setVendorFaxNumber(newVendor.getDefaultFaxNumber());
1652            }
1653            return newPOVendorQuote;
1654        }
1655        
1656        /**
1657         * Obtains the defaultAddress of the vendor and setting the default address fields on
1658         * the vendor.
1659         * 
1660         * @param vendor The VendorDetail object whose default address we'll obtain and set the fields.
1661         */
1662        protected void updateDefaultVendorAddress(VendorDetail vendor) {
1663            VendorAddress defaultAddress = SpringContext.getBean(VendorService.class).getVendorDefaultAddress(vendor.getVendorAddresses(), vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode(), "");
1664            if (defaultAddress != null ) {
1665                if (defaultAddress.getVendorState() != null) {
1666                    vendor.setVendorStateForLookup(defaultAddress.getVendorState().getPostalStateName());
1667                }
1668                vendor.setDefaultAddressLine1(defaultAddress.getVendorLine1Address());
1669                vendor.setDefaultAddressLine2(defaultAddress.getVendorLine2Address());
1670                vendor.setDefaultAddressCity(defaultAddress.getVendorCityName());
1671                vendor.setDefaultAddressPostalCode(defaultAddress.getVendorZipCode());
1672                vendor.setDefaultAddressStateCode(defaultAddress.getVendorStateCode());
1673                vendor.setDefaultAddressInternationalProvince(defaultAddress.getVendorAddressInternationalProvinceName());
1674                vendor.setDefaultAddressCountryCode(defaultAddress.getVendorCountryCode());
1675                vendor.setDefaultFaxNumber(defaultAddress.getVendorFaxNumber());
1676            }
1677        }
1678        
1679        /**
1680         * 
1681         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#processACMReq(org.kuali.kfs.module.purap.document.ContractManagerAssignmentDocument)
1682         */
1683        public void processACMReq(ContractManagerAssignmentDocument acmDoc) {
1684            List<ContractManagerAssignmentDetail> acmDetails = acmDoc.getContractManagerAssignmentDetails();
1685            for (Iterator iter = acmDetails.iterator(); iter.hasNext();) {
1686                ContractManagerAssignmentDetail detail = (ContractManagerAssignmentDetail) iter.next();
1687    
1688                if (ObjectUtils.isNotNull(detail.getContractManagerCode())) {
1689                    // Get the requisition for this ContractManagerAssignmentDetail.
1690                    RequisitionDocument req = SpringContext.getBean(RequisitionService.class).getRequisitionById(detail.getRequisitionIdentifier());
1691    
1692                    if (req.getStatusCode().equals(PurapConstants.RequisitionStatuses.AWAIT_CONTRACT_MANAGER_ASSGN)) {
1693                        // only update REQ if code is empty and status is correct
1694                        purapService.updateStatus(req, PurapConstants.RequisitionStatuses.CLOSED);
1695                        purapService.saveDocumentNoValidation(req);
1696                        createPurchaseOrderDocument(req, KFSConstants.SYSTEM_USER, detail.getContractManagerCode());
1697                    }
1698                }
1699    
1700            }// endfor
1701        }
1702        
1703        /**
1704         * 
1705         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#autoCloseFullyDisencumberedOrders()
1706         */
1707        public boolean autoCloseFullyDisencumberedOrders() {
1708            LOG.info("autoCloseFullyDisencumberedOrders() started");
1709    
1710            List<AutoClosePurchaseOrderView> purchaseOrderAutoCloseList = purchaseOrderDao.getAllOpenPurchaseOrders(getExcludedVendorChoiceCodes());
1711    
1712            for (AutoClosePurchaseOrderView poAutoClose : purchaseOrderAutoCloseList) {
1713                if ((poAutoClose.getTotalAmount() != null) && ((KualiDecimal.ZERO.compareTo(poAutoClose.getTotalAmount())) != 0)) {
1714                    LOG.info("autoCloseFullyDisencumberedOrders() PO ID " + poAutoClose.getPurapDocumentIdentifier() + " with total " + poAutoClose.getTotalAmount().doubleValue() + " will be closed");
1715                    String newStatus = PurapConstants.PurchaseOrderStatuses.PENDING_CLOSE;
1716                    String annotation = "This PO was automatically closed in batch.";
1717                    String documentType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT;
1718                    PurchaseOrderDocument document = getPurchaseOrderByDocumentNumber(poAutoClose.getDocumentNumber());
1719                    createNoteForAutoCloseOrders(document, annotation);
1720                    createAndRoutePotentialChangeDocument(poAutoClose.getDocumentNumber(), documentType, annotation, null, newStatus);
1721    
1722                }
1723            }
1724            LOG.info("autoCloseFullyDisencumberedOrders() ended");
1725    
1726            return true;
1727        }
1728    
1729        /**
1730         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#autoCloseRecurringOrders()
1731         */
1732        public boolean autoCloseRecurringOrders() {
1733            LOG.info("autoCloseRecurringOrders() started");
1734            boolean shouldSendEmail = true;
1735            MailMessage message = new MailMessage();
1736            String parameterEmail = parameterService.getParameterValue(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_TO_EMAIL_ADDRESSES);
1737            
1738            if (StringUtils.isEmpty(parameterEmail)) {
1739                // Don't stop the show if the email address is wrong, log it and continue.
1740                LOG.error("autoCloseRecurringOrders(): parameterEmail is missing, we'll not send out any emails for this job.");
1741                shouldSendEmail = false;
1742            }
1743            if (shouldSendEmail) {
1744                message = setMessageAddressesAndSubject(message, parameterEmail);
1745            }
1746            StringBuffer emailBody = new StringBuffer();
1747            // There should always be a "AUTO_CLOSE_RECURRING_ORDER_DT"
1748            // row in the table, this method sets it to "mm/dd/yyyy" after processing.
1749            String recurringOrderDateString = parameterService.getParameterValue(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_DATE);
1750            boolean validDate = true;
1751            java.util.Date recurringOrderDate = null;
1752            try {
1753                recurringOrderDate = dateTimeService.convertToDate(recurringOrderDateString);
1754            }
1755            catch (ParseException pe) {
1756                validDate = false;
1757            }
1758            if (StringUtils.isEmpty(recurringOrderDateString) || recurringOrderDateString.equalsIgnoreCase("mm/dd/yyyy") || (!validDate)) {
1759                if (recurringOrderDateString.equalsIgnoreCase("mm/dd/yyyy")) {
1760                    LOG.debug("autoCloseRecurringOrders(): mm/dd/yyyy " + "was found in the Application Settings table. No orders will be closed, method will end.");
1761                    if (shouldSendEmail) {
1762                        emailBody.append("The AUTO_CLOSE_RECURRING_ORDER_DT found in the Application Settings table " + "was mm/dd/yyyy. No recurring PO's were closed.");
1763                    }
1764                }
1765                else {
1766                    LOG.debug("autoCloseRecurringOrders(): An invalid autoCloseRecurringOrdersDate " + "was found in the Application Settings table: " + recurringOrderDateString + ". Method will end.");
1767                    if (shouldSendEmail) {
1768                        emailBody.append("An invalid AUTO_CLOSE_RECURRING_ORDER_DT was found in the Application Settings table: " + recurringOrderDateString + ". No recurring PO's were closed.");
1769                    }
1770                }
1771                if (shouldSendEmail) {
1772                    sendMessage(message, emailBody.toString());
1773                }
1774                LOG.info("autoCloseRecurringOrders() ended");
1775                
1776                return false;
1777            }
1778            LOG.info("autoCloseRecurringOrders() The autoCloseRecurringOrdersDate found in the Application Settings table was " + recurringOrderDateString);
1779            if (shouldSendEmail) {
1780                emailBody.append("The autoCloseRecurringOrdersDate found in the Application Settings table was " + recurringOrderDateString + ".");
1781            }
1782            Calendar appSettingsDate = dateTimeService.getCalendar(recurringOrderDate);
1783            Timestamp appSettingsDay = new Timestamp(appSettingsDate.getTime().getTime());
1784    
1785            Calendar todayMinusThreeMonths = getTodayMinusThreeMonths();
1786            Timestamp threeMonthsAgo = new Timestamp(todayMinusThreeMonths.getTime().getTime());
1787    
1788            if (appSettingsDate.after(todayMinusThreeMonths)) {
1789                LOG.info("autoCloseRecurringOrders() The appSettingsDate: " + appSettingsDay + " is after todayMinusThreeMonths: " + threeMonthsAgo + ". The program will end.");
1790                if (shouldSendEmail) {
1791                    emailBody.append("\n\nThe autoCloseRecurringOrdersDate: " + appSettingsDay + " is after todayMinusThreeMonths: " + threeMonthsAgo + ". The program will end.");
1792                    sendMessage(message, emailBody.toString());
1793                }
1794                LOG.info("autoCloseRecurringOrders() ended");
1795                
1796                return false;
1797            }
1798    
1799            List<AutoClosePurchaseOrderView> purchaseOrderAutoCloseList = purchaseOrderDao.getAutoCloseRecurringPurchaseOrders(getExcludedVendorChoiceCodes());
1800            LOG.info("autoCloseRecurringOrders(): " + purchaseOrderAutoCloseList.size() + " PO's were returned for processing.");
1801            int counter = 0;
1802            for (AutoClosePurchaseOrderView poAutoClose : purchaseOrderAutoCloseList) {
1803                LOG.info("autoCloseRecurringOrders(): Testing PO ID " + poAutoClose.getPurapDocumentIdentifier() + ". recurringPaymentEndDate: " + poAutoClose.getRecurringPaymentEndDate());
1804                if (poAutoClose.getRecurringPaymentEndDate().before(appSettingsDay)) {
1805                    String newStatus = PurapConstants.PurchaseOrderStatuses.PENDING_CLOSE;
1806                    String annotation = "This recurring PO was automatically closed in batch.";
1807                    String documentType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT;                
1808                    PurchaseOrderDocument document = getPurchaseOrderByDocumentNumber(poAutoClose.getDocumentNumber());                
1809                    boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedRouteDocumentEvent("", document));                                
1810                    
1811                                    boolean success = true;
1812                    if (success) {
1813                        ++counter;
1814                        if (counter == 1) {
1815                            emailBody.append("\n\nThe following recurring Purchase Orders will be closed by auto close recurring batch job \n");
1816                        }
1817                        LOG.info("autoCloseRecurringOrders() PO ID " + poAutoClose.getPurapDocumentIdentifier() + " will be closed.");
1818                        createNoteForAutoCloseOrders(document, annotation);
1819                        createAndRoutePotentialChangeDocument(poAutoClose.getDocumentNumber(), documentType, annotation, null, newStatus);
1820                        if (shouldSendEmail) {
1821                            emailBody.append("\n\n" + counter + " PO ID: " + poAutoClose.getPurapDocumentIdentifier() + ", End Date: " + poAutoClose.getRecurringPaymentEndDate() + ", Status: " + poAutoClose.getPurchaseOrderStatusCode() + ", VendorChoice: " + poAutoClose.getVendorChoiceCode() + ", RecurringPaymentType: " + poAutoClose.getRecurringPaymentTypeCode());
1822                        }
1823                    }
1824                    else {
1825                        //If it was unsuccessful, we have to clear the error map in the GlobalVariables so that the previous
1826                        //error would not still be lingering around and the next PO in the list can be validated.
1827                        GlobalVariables.getMessageMap().clear();
1828                    }
1829                }
1830            }
1831            if (counter == 0) {
1832                LOG.info("\n\nNo recurring PO's fit the conditions for closing.");
1833                if (shouldSendEmail) {
1834                    emailBody.append("\n\nNo recurring PO's fit the conditions for closing.");
1835                }
1836            }
1837            if (shouldSendEmail) {
1838                sendMessage(message, emailBody.toString());
1839            }
1840            resetAutoCloseRecurringOrderDateParameter();
1841            LOG.info("autoCloseRecurringOrders() ended");
1842            
1843            return true;
1844        }
1845    
1846        /**
1847         * Creates and returns a Calendar object of today minus three months.
1848         * 
1849         * @return Calendar object of today minus three months.
1850         */
1851        protected Calendar getTodayMinusThreeMonths() {
1852            Calendar todayMinusThreeMonths = Calendar.getInstance(); // Set to today.
1853            todayMinusThreeMonths.add(Calendar.MONTH, -3); // Back up 3 months.
1854            todayMinusThreeMonths.set(Calendar.HOUR, 12);
1855            todayMinusThreeMonths.set(Calendar.MINUTE, 0);
1856            todayMinusThreeMonths.set(Calendar.SECOND, 0);
1857            todayMinusThreeMonths.set(Calendar.MILLISECOND, 0);
1858            todayMinusThreeMonths.set(Calendar.AM_PM, Calendar.AM);
1859            return todayMinusThreeMonths;
1860        }
1861        
1862        /**
1863         * Sets the to addresses, from address and the subject of the email.
1864         * 
1865         * @param message         The MailMessage object of the email to be sent.
1866         * @param parameterEmail  The String of email addresses with delimiters of ";" 
1867         *                        obtained from the system parameter.
1868         * @return                The MailMessage object after the to addresses, from 
1869         *                        address and the subject have been set.
1870         */
1871        protected MailMessage setMessageAddressesAndSubject(MailMessage message, String parameterEmail) {
1872            String toAddressList[] = parameterEmail.split(";");
1873    
1874            if (toAddressList.length > 0) {
1875                for (int i = 0; i < toAddressList.length; i++) {
1876                    if (toAddressList[i] != null) {
1877                        message.addToAddress(toAddressList[i].trim());
1878                    }
1879                }
1880            }
1881    
1882            message.setFromAddress(toAddressList[0]); 
1883    
1884            if (kualiConfigurationService.isProductionEnvironment()) {
1885                message.setSubject("Auto Close Recurring Purchase Orders");
1886            }
1887            else {
1888                message.setSubject(kualiConfigurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY) + " - Auto Close Recurring Purchase Orders");
1889            }
1890            return message;
1891        }
1892        
1893        /**
1894         * Sends the email by calling the sendMessage method in mailService and log error if exception occurs
1895         * during the attempt to send the message.
1896         * 
1897         * @param message    The MailMessage object containing information to be sent.
1898         * @param emailBody  The String containing the body of the email to be sent.
1899         */
1900        protected void sendMessage(MailMessage message, String emailBody) {
1901            message.setMessage(emailBody);
1902            try {
1903                mailService.sendMessage(message);
1904            }
1905            catch (Exception e) {
1906                // Don't stop the show if the email has problem, log it and continue.
1907                LOG.error("autoCloseRecurringOrders(): email problem. Message not sent.", e);
1908            }        
1909        }
1910        
1911        /**
1912         * Resets the AUTO_CLOSE_RECURRING_ORDER_DT system parameter to "mm/dd/yyyy".
1913         * 
1914         */
1915        protected void resetAutoCloseRecurringOrderDateParameter() {
1916            Map<String, String> fieldValues = new HashMap<String, String>();
1917            fieldValues.put("parameterName", PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_DATE);
1918            
1919            Collection result = businessObjectService.findMatching(Parameter.class, fieldValues);
1920            Parameter autoCloseRecurringPODate = (Parameter)result.iterator().next();
1921            autoCloseRecurringPODate.setParameterValue("mm/dd/yyyy");
1922            businessObjectService.save(autoCloseRecurringPODate);
1923        }
1924        
1925        /**
1926         * Gets a List of excluded vendor choice codes from PurapConstants.
1927         * 
1928         * @return a List of excluded vendor choice codes
1929         */
1930        protected List<String> getExcludedVendorChoiceCodes() {
1931            List<String> excludedVendorChoiceCodes = new ArrayList<String>();
1932            for (int i = 0; i < PurapConstants.AUTO_CLOSE_EXCLUSION_VNDR_CHOICE_CODES.length; i++) {
1933                String excludedCode = PurapConstants.AUTO_CLOSE_EXCLUSION_VNDR_CHOICE_CODES[i];
1934                excludedVendorChoiceCodes.add(excludedCode);
1935            }
1936            return excludedVendorChoiceCodes;
1937        }
1938        
1939        /**
1940         * Creates and add a note to the purchase order document using the annotation String
1941         * in the input parameter. This method is used by the autoCloseRecurringOrders() and
1942         * autoCloseFullyDisencumberedOrders to add a note to the purchase order to
1943         * indicate that the purchase order was closed by the batch job.
1944         * 
1945         * @param purchaseOrderDocument The purchase order document that is being closed by the batch job.
1946         * @param annotation            The string to appear on the note to be attached to the purchase order.
1947         */
1948        protected void createNoteForAutoCloseOrders(PurchaseOrderDocument purchaseOrderDocument, String annotation) {
1949            try {
1950                Note noteObj = documentService.createNoteFromDocument(purchaseOrderDocument, annotation);
1951                documentService.addNoteToDocument(purchaseOrderDocument, noteObj);
1952                noteService.save(noteObj);
1953            }
1954            catch(Exception e){
1955                String errorMessage = "Error creating and saving close note for purchase order with document service";
1956                LOG.error("createNoteForAutoCloseRecurringOrders " + errorMessage, e);
1957                throw new RuntimeException(errorMessage, e);
1958            }   
1959        }
1960    
1961        /**
1962         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetItemsForIndividual(java.lang.Integer)
1963         */
1964        public List<PurchasingCapitalAssetItem> retrieveCapitalAssetItemsForIndividual(Integer poId) {
1965            PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
1966            if (ObjectUtils.isNotNull(po)) {
1967                return po.getPurchasingCapitalAssetItems();
1968            }
1969            return null;
1970        }
1971    
1972        /**
1973         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetSystemForOneSystem(java.lang.Integer)
1974         */
1975        public CapitalAssetSystem retrieveCapitalAssetSystemForOneSystem(Integer poId) {
1976            PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
1977            if (ObjectUtils.isNotNull(po)) {
1978                List<CapitalAssetSystem> systems = po.getPurchasingCapitalAssetSystems();
1979                if (ObjectUtils.isNotNull(systems)) {
1980                    //for one system, there should only ever be one system
1981                    return systems.get(0);
1982                }
1983            }
1984            return null;
1985        }
1986    
1987        /**
1988         * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetSystemsForMultipleSystem(java.lang.Integer)
1989         */
1990        public List<CapitalAssetSystem> retrieveCapitalAssetSystemsForMultipleSystem(Integer poId) {
1991            PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
1992            if (ObjectUtils.isNotNull(po)) {
1993                return po.getPurchasingCapitalAssetSystems();
1994            }
1995            return null;
1996        }
1997    
1998        /**
1999         * This method fixes the item references in this document
2000         */
2001        protected void fixItemReferences(PurchaseOrderDocument po) {
2002            //fix item and account references in case this is a new doc (since they will be lost)
2003            for (PurApItem item : (List<PurApItem>)po.getItems()) {
2004                item.setPurapDocument(po);
2005                item.fixAccountReferences();
2006            }
2007        }
2008        
2009        public List getPendingPurchaseOrderFaxes() {
2010            return purchaseOrderDao.getPendingPurchaseOrdersForFaxing();
2011        }
2012    
2013        /**
2014         * @return Returns the personService.
2015         */
2016        protected PersonService<Person> getPersonService() {
2017            if(personService==null)
2018                personService = SpringContext.getBean(PersonService.class);
2019            return personService;
2020        }
2021    
2022        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
2023            this.dataDictionaryService = dataDictionaryService;
2024        }
2025    
2026    }