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 }