View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.module.purap.document.web.struts;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.struts.action.ActionForm;
20  import org.apache.struts.action.ActionForward;
21  import org.apache.struts.action.ActionMapping;
22  import org.kuali.ole.module.purap.PurapConstants;
23  import org.kuali.ole.module.purap.PurapConstants.*;
24  import org.kuali.ole.module.purap.PurapKeyConstants;
25  import org.kuali.ole.module.purap.PurapPropertyConstants;
26  import org.kuali.ole.module.purap.SingleConfirmationQuestion;
27  import org.kuali.ole.module.purap.businessobject.PurApAccountingLine;
28  import org.kuali.ole.module.purap.businessobject.PurApItem;
29  import org.kuali.ole.module.purap.document.*;
30  import org.kuali.ole.module.purap.document.service.*;
31  import org.kuali.ole.module.purap.document.validation.event.AttributedCalculateAccountsPayableEvent;
32  import org.kuali.ole.module.purap.document.validation.event.AttributedPreCalculateAccountsPayableEvent;
33  import org.kuali.ole.module.purap.service.PurapAccountingService;
34  import org.kuali.ole.module.purap.util.PurQuestionCallback;
35  import org.kuali.ole.select.document.service.OleSelectDocumentService;
36  import org.kuali.ole.sys.OLEConstants;
37  import org.kuali.ole.sys.OLEPropertyConstants;
38  import org.kuali.ole.sys.context.SpringContext;
39  import org.kuali.ole.vnd.VendorConstants;
40  import org.kuali.ole.vnd.businessobject.VendorAddress;
41  import org.kuali.rice.core.api.config.property.ConfigurationService;
42  import org.kuali.rice.core.api.util.RiceKeyConstants;
43  import org.kuali.rice.core.api.util.type.KualiDecimal;
44  import org.kuali.rice.kew.api.exception.WorkflowException;
45  import org.kuali.rice.kns.question.ConfirmationQuestion;
46  import org.kuali.rice.kns.service.DataDictionaryService;
47  import org.kuali.rice.kns.util.KNSGlobalVariables;
48  import org.kuali.rice.kns.util.MessageList;
49  import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
50  import org.kuali.rice.krad.bo.Note;
51  import org.kuali.rice.krad.exception.ValidationException;
52  import org.kuali.rice.krad.service.BusinessObjectService;
53  import org.kuali.rice.krad.service.KualiRuleService;
54  import org.kuali.rice.krad.util.GlobalVariables;
55  import org.kuali.rice.krad.util.ObjectUtils;
56  import org.kuali.rice.krad.util.UrlFactory;
57  
58  import javax.servlet.http.HttpServletRequest;
59  import javax.servlet.http.HttpServletResponse;
60  import java.util.Iterator;
61  import java.util.List;
62  import java.util.Properties;
63  import java.util.TreeMap;
64  import java.util.Date;
65  import java.sql.Timestamp;
66  
67  /**
68   * Struts Action for Accounts Payable documents.
69   */
70  public class AccountsPayableActionBase extends PurchasingAccountsPayableActionBase {
71      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountsPayableActionBase.class);
72  
73      private OleSelectDocumentService oleSelectDocumentService;
74  
75      @Override
76      public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
77          PurchasingAccountsPayableFormBase baseForm = (PurchasingAccountsPayableFormBase) form;
78  
79          ActionForward fwd = super.execute(mapping, form, request, response);
80  
81          AccountsPayableDocumentBase document = (AccountsPayableDocumentBase) baseForm.getDocument();
82          boolean foundAccountExpiredWarning = false;
83          for (int i = 0; i < KNSGlobalVariables.getMessageList().size(); i++) {
84              if (StringUtils.equals(KNSGlobalVariables.getMessageList().get(i).getErrorKey(), PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED)) {
85                  foundAccountExpiredWarning = true;
86              }
87          }
88  
89          if (!foundAccountExpiredWarning) {
90              SpringContext.getBean(AccountsPayableService.class).generateExpiredOrClosedAccountWarning(document);
91          }
92  
93          return fwd;
94  
95      }
96  
97      /**
98       * Performs refresh of objects after a lookup.
99       *
100      * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#refresh(org.apache.struts.action.ActionMapping,
101      *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
102      */
103     @Override
104     public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
105         PurchasingAccountsPayableFormBase baseForm = (PurchasingAccountsPayableFormBase) form;
106 
107         AccountsPayableDocumentBase document = (AccountsPayableDocumentBase) baseForm.getDocument();
108 
109         if (StringUtils.equals(baseForm.getRefreshCaller(), VendorConstants.VENDOR_ADDRESS_LOOKUPABLE_IMPL)) {
110             if (StringUtils.isNotBlank(request.getParameter(OLEPropertyConstants.DOCUMENT + "." + PurapPropertyConstants.VENDOR_ADDRESS_ID))) {
111                 Integer vendorAddressGeneratedId = document.getVendorAddressGeneratedIdentifier();
112                 VendorAddress refreshVendorAddress = new VendorAddress();
113                 refreshVendorAddress.setVendorAddressGeneratedIdentifier(vendorAddressGeneratedId);
114                 refreshVendorAddress = (VendorAddress) SpringContext.getBean(BusinessObjectService.class).retrieve(refreshVendorAddress);
115                 document.templateVendorAddress(refreshVendorAddress);
116             }
117         }
118 
119         return super.refresh(mapping, form, request, response);
120     }
121 
122     /**
123      * Checks the continuation account indicator and generates warnings if continuation accounts were used to replace original
124      * accounts on the document.
125      *
126      * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
127      */
128     @Override
129     protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
130         super.loadDocument(kualiDocumentFormBase);
131         AccountsPayableDocument document = (AccountsPayableDocument) kualiDocumentFormBase.getDocument();
132 
133         SpringContext.getBean(AccountsPayableService.class).generateExpiredOrClosedAccountWarning(document);
134 
135         SpringContext.getBean(AccountsPayableService.class).updateItemList(document);
136         ((AccountsPayableFormBase) kualiDocumentFormBase).updateItemCounts();
137     }
138 
139     /**
140      * Perform calculation on item line.
141      *
142      * @param mapping  An ActionMapping
143      * @param form     An ActionForm
144      * @param request  The HttpServletRequest
145      * @param response The HttpServletResponse
146      * @return An ActionForward
147      */
148     @Override
149     public ActionForward calculate(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
150         AccountsPayableFormBase apForm = (AccountsPayableFormBase) form;
151         AccountsPayableDocument apDoc = (AccountsPayableDocument) apForm.getDocument();
152 
153         //   //recalculate the amounts and percents on the accounting line.
154         SpringContext.getBean(PurapAccountingService.class).updateAccountAmounts(apDoc);
155 
156         // call precalculate
157         if (SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedPreCalculateAccountsPayableEvent(apDoc))) {
158             customCalculate(apDoc);
159 
160             // set calculated flag according to document type and status
161             if (apForm instanceof PaymentRequestForm && apDoc.getApplicationDocumentStatus().equals(PaymentRequestStatuses.APPDOC_AWAITING_TAX_REVIEW)) {
162                 // set calculated tax flag for tax area calculation
163                 PaymentRequestForm preqForm = (PaymentRequestForm) apForm;
164                 preqForm.setCalculatedTax(true);
165             } else {
166                 // set calculated flag for document calculation, whether or not the process calculation rule passes, since it only gives warning
167                 apForm.setCalculated(true);
168             }
169         }
170 
171         return super.calculate(mapping, form, request, response);
172     }
173 
174     @Override
175     public ActionForward clearAllTaxes(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
176         AccountsPayableFormBase payableForm = (AccountsPayableFormBase) form;
177         AccountsPayableDocument apDoc = (AccountsPayableDocument) payableForm.getDocument();
178 
179         SpringContext.getBean(PurapService.class).clearAllTaxes(apDoc);
180 
181         return super.clearAllTaxes(mapping, form, request, response);
182     }
183 
184     /**
185      * Checks if calculation is required. Currently it is required when it has not already been calculated and full document entry
186      * status has not already passed.
187      *
188      * @param apForm A Form, which must inherit from <code>AccountsPayableFormBase</code>
189      * @return true if calculation is required, false otherwise
190      */
191     protected boolean requiresCaculate(AccountsPayableFormBase apForm) {
192         boolean requiresCalculate = true;
193         PurchasingAccountsPayableDocument purapDocument = (PurchasingAccountsPayableDocument) apForm.getDocument();
194         requiresCalculate = !apForm.isCalculated() && !SpringContext.getBean(PurapService.class).isFullDocumentEntryCompleted(purapDocument);
195 
196         return requiresCalculate;
197     }
198 
199     /**
200      * Returns the current action name.
201      *
202      * @return A String. Set to null!
203      */
204     public String getActionName() {
205         return null;
206     }
207 
208     /**
209      * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#route(org.apache.struts.action.ActionMapping,
210      *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
211      */
212     @Override
213     public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
214         AccountsPayableFormBase apForm = (AccountsPayableFormBase) form;
215 
216         // set the last update user id
217         AccountsPayableDocumentBase document = (AccountsPayableDocumentBase) apForm.getDocument();
218         document.setLastActionPerformedByPersonId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
219 
220         // if form is not yet calculated, return and prompt user to calculate
221         if (requiresCaculate(apForm)) {
222             GlobalVariables.getMessageMap().putError(OLEConstants.DOCUMENT_ERRORS, PurapKeyConstants.ERROR_APPROVE_REQUIRES_CALCULATE);
223             return mapping.findForward(OLEConstants.MAPPING_BASIC);
224         }
225 
226         // recalculate
227         customCalculate((AccountsPayableDocument) apForm.getDocument());
228 
229         // route
230         ActionForward forward = super.route(mapping, form, request, response);
231 
232         // if successful, then redirect back to init
233         boolean successMessageFound = false;
234         MessageList messageList = KNSGlobalVariables.getMessageList();
235         for (int i = 0; i < messageList.size(); i++) {
236             if (StringUtils.equals(messageList.get(i).getErrorKey(), RiceKeyConstants.MESSAGE_ROUTE_SUCCESSFUL)) {
237                 successMessageFound = true;
238                 break;
239             }
240         }
241 
242         if (successMessageFound) {
243             String basePath = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(OLEConstants.APPLICATION_URL_KEY);
244 
245             Properties parameters = new Properties();
246             parameters.put(OLEConstants.DISPATCH_REQUEST_PARAMETER, OLEConstants.DOC_HANDLER_METHOD);
247             parameters.put(OLEConstants.PARAMETER_COMMAND, "initiate");
248             parameters.put(OLEConstants.DOCUMENT_TYPE_NAME, apForm.getDocTypeName());
249 
250             String lookupUrl = UrlFactory.parameterizeUrl(basePath + "/" + "purap" + this.getActionName() + ".do", parameters);
251             forward = new ActionForward(lookupUrl, true);
252         }
253 
254         return forward;
255     }
256 
257     /**
258      * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#save(org.apache.struts.action.ActionMapping,
259      *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
260      */
261     @Override
262     public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
263         AccountsPayableFormBase apForm = (AccountsPayableFormBase) form;
264 
265         if (!requiresCaculate(apForm)) {
266             return super.save(mapping, form, request, response);
267         }
268 
269         GlobalVariables.getMessageMap().putError(OLEConstants.DOCUMENT_ERRORS, PurapKeyConstants.ERROR_SAVE_REQUIRES_CALCULATE);
270         return mapping.findForward(OLEConstants.MAPPING_BASIC);
271 
272     }
273 
274     /**
275      * A wrapper method which prompts for a reason to hold a payment request or credit memo.
276      *
277      * @param mapping      An ActionMapping
278      * @param form         An ActionForm
279      * @param request      The HttpServletRequest
280      * @param response     The HttpServletResponse
281      * @param questionType A String used to distinguish which question is being asked
282      * @param notePrefix   A String explaining what action was taken, to be prepended to the note containing the reason, which gets
283      *                     written to the document
284      * @param operation    A one-word String description of the action to be taken, to be substituted into the message. (Can be an
285      *                     empty String for some messages.)
286      * @param messageKey   A key to the message which will appear on the question screen
287      * @param callback     A PurQuestionCallback
288      * @return An ActionForward
289      * @throws Exception
290      */
291     protected ActionForward askQuestionWithInput(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String questionType, String notePrefix, String operation, String messageKey, PurQuestionCallback callback) throws Exception {
292         TreeMap<String, PurQuestionCallback> questionsAndCallbacks = new TreeMap<String, PurQuestionCallback>();
293         questionsAndCallbacks.put(questionType, callback);
294         return askQuestionWithInput(mapping, form, request, response, questionType, notePrefix, operation, messageKey, questionsAndCallbacks, "", mapping.findForward(OLEConstants.MAPPING_BASIC));
295     }
296 
297     /**
298      * Builds and asks questions which require text input by the user for a payment request or a credit memo.
299      *
300      * @param mapping               An ActionMapping
301      * @param form                  An ActionForm
302      * @param request               The HttpServletRequest
303      * @param response              The HttpServletResponse
304      * @param questionType          A String used to distinguish which question is being asked
305      * @param notePrefix            A String explaining what action was taken, to be prepended to the note containing the reason, which gets
306      *                              written to the document
307      * @param operation             A one-word String description of the action to be taken, to be substituted into the message. (Can be an
308      *                              empty String for some messages.)
309      * @param messageKey            A (whole) key to the message which will appear on the question screen
310      * @param questionsAndCallbacks A TreeMap associating the type of question to be asked and the type of callback which should
311      *                              happen in that case
312      * @param messagePrefix         The most general part of a key to a message text to be retrieved from ConfigurationService,
313      *                              Describes a collection of questions.
314      * @param redirect              An ActionForward to return to if done with questions
315      * @return An ActionForward
316      * @throws Exception
317      */
318     protected ActionForward askQuestionWithInput(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String questionType, String notePrefix, String operation, String messageKey, TreeMap<String, PurQuestionCallback> questionsAndCallbacks, String messagePrefix, ActionForward redirect) throws Exception {
319         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
320         AccountsPayableDocumentBase apDocument = (AccountsPayableDocumentBase) kualiDocumentFormBase.getDocument();
321 
322         String question = request.getParameter(OLEConstants.QUESTION_INST_ATTRIBUTE_NAME);
323         String reason = request.getParameter(OLEConstants.QUESTION_REASON_ATTRIBUTE_NAME);
324         String noteText = "";
325 
326         ConfigurationService kualiConfiguration = SpringContext.getBean(ConfigurationService.class);
327         String firstQuestion = questionsAndCallbacks.firstKey();
328         PurQuestionCallback callback = null;
329         Iterator questions = questionsAndCallbacks.keySet().iterator();
330         String mapQuestion = null;
331         String key = null;
332 
333         // Start in logic for confirming the close.
334         if (question == null) {
335             key = getQuestionProperty(messageKey, messagePrefix, kualiConfiguration, firstQuestion);
336             String message = StringUtils.replace(key, "{0}", operation);
337 
338             // Ask question if not already asked.
339             return this.performQuestionWithInput(mapping, form, request, response, firstQuestion, message, OLEConstants.CONFIRMATION_QUESTION, questionType, "");
340         } else {
341             // find callback for this question
342             while (questions.hasNext()) {
343                 mapQuestion = (String) questions.next();
344 
345                 if (StringUtils.equals(mapQuestion, question)) {
346                     callback = questionsAndCallbacks.get(mapQuestion);
347                     break;
348                 }
349             }
350             key = getQuestionProperty(messageKey, messagePrefix, kualiConfiguration, mapQuestion);
351 
352             Object buttonClicked = request.getParameter(OLEConstants.QUESTION_CLICKED_BUTTON);
353             if (question.equals(mapQuestion) && buttonClicked.equals(ConfirmationQuestion.NO)) {
354                 // If 'No' is the button clicked, just reload the doc
355 
356                 String nextQuestion = null;
357                 // ask another question if more left
358                 if (questions.hasNext()) {
359                     nextQuestion = (String) questions.next();
360                     key = getQuestionProperty(messageKey, messagePrefix, kualiConfiguration, nextQuestion);
361 
362                     return this.performQuestionWithInput(mapping, form, request, response, nextQuestion, key, OLEConstants.CONFIRMATION_QUESTION, questionType, "");
363                 } else {
364 
365                     return mapping.findForward(OLEConstants.MAPPING_BASIC);
366                 }
367             }
368             // Have to check length on value entered.
369             String introNoteMessage = notePrefix + OLEConstants.BLANK_SPACE;
370 
371             // Build out full message.
372             noteText = introNoteMessage + reason;
373             int noteTextLength = noteText.length();
374 
375             // Get note text max length from DD.
376             int noteTextMaxLength = SpringContext.getBean(DataDictionaryService.class).getAttributeMaxLength(Note.class, OLEConstants.NOTE_TEXT_PROPERTY_NAME).intValue();
377             if (StringUtils.isBlank(reason) || (noteTextLength > noteTextMaxLength)) {
378                 // Figure out exact number of characters that the user can enter.
379                 int reasonLimit = noteTextMaxLength - noteTextLength;
380                 if (reason == null) {
381                     // Prevent a NPE by setting the reason to a blank string.
382                     reason = "";
383                 }
384 
385                 return this.performQuestionWithInputAgainBecauseOfErrors(mapping, form, request, response, mapQuestion, key, OLEConstants.CONFIRMATION_QUESTION, questionType, "", reason, PurapKeyConstants.ERROR_PAYMENT_REQUEST_REASON_REQUIRED, OLEConstants.QUESTION_REASON_ATTRIBUTE_NAME, new Integer(reasonLimit).toString());
386             }
387         }
388 
389         // make callback
390         if (ObjectUtils.isNotNull(callback)) {
391             AccountsPayableDocument refreshedApDocument = callback.doPostQuestion(apDocument, noteText);
392             kualiDocumentFormBase.setDocument(refreshedApDocument);
393         }
394         String nextQuestion = null;
395         // ask another question if more left
396         if (questions.hasNext()) {
397             nextQuestion = (String) questions.next();
398             key = getQuestionProperty(messageKey, messagePrefix, kualiConfiguration, nextQuestion);
399 
400             return this.performQuestionWithInput(mapping, form, request, response, nextQuestion, key, OLEConstants.CONFIRMATION_QUESTION, questionType, "");
401         }
402 
403         return redirect;
404     }
405 
406     /**
407      * Used to look up messages to be displayed, from the ConfigurationService, given either a whole key or two parts of a key
408      * that may be concatenated together.
409      *
410      * @param messageKey         String. One of the message keys in PurapKeyConstants.
411      * @param messagePrefix      String. A prefix to the question key, such as "ap.question." that, concatenated with the question,
412      *                           comprises the whole key of the message.
413      * @param kualiConfiguration An instance of ConfigurationService
414      * @param question           String. The most specific part of the message key in PurapKeyConstants.
415      * @return The message to be displayed given the key
416      */
417     protected String getQuestionProperty(String messageKey, String messagePrefix, ConfigurationService kualiConfiguration, String question) {
418         return kualiConfiguration.getPropertyValueAsString((StringUtils.isEmpty(messagePrefix)) ? messageKey : messagePrefix + question);
419     }
420 
421     public ActionForward reopenPo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
422         LOG.debug("Reopen PO started");
423         return askQuestionsAndPerformReopenPurchaseOrder(mapping, form, request, response);
424     }
425 
426     /**
427      * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#cancel(org.apache.struts.action.ActionMapping,
428      *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
429      */
430     @Override
431     public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
432         return askCancelQuestion(mapping, form, request, response);
433     }
434 
435     /**
436      * Constructs and asks the question as to whether the user wants to cancel, for payment requests and credit memos.
437      *
438      * @param mapping  An ActionMapping
439      * @param form     An ActionForm
440      * @param request  The HttpServletRequest
441      * @param response The HttpServletResponse
442      * @return An ActionForward
443      * @throws Exception
444      */
445     protected ActionForward askCancelQuestion(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
446         PurchasingAccountsPayableFormBase apForm = (PurchasingAccountsPayableFormBase) form;
447 
448         String operation = "Cancel ";
449         PurQuestionCallback callback = cancelCallbackMethod();
450         TreeMap<String, PurQuestionCallback> questionsAndCallbacks = new TreeMap<String, PurQuestionCallback>();
451         questionsAndCallbacks.put("cancelAP", callback);
452 
453         return askQuestionWithInput(mapping, form, request, response, CMDocumentsStrings.CANCEL_CM_QUESTION, AccountsPayableDocumentStrings.CANCEL_NOTE_PREFIX, operation, PurapKeyConstants.CREDIT_MEMO_QUESTION_CANCEL_DOCUMENT, questionsAndCallbacks, PurapKeyConstants.AP_QUESTION_PREFIX, mapping.findForward(OLEConstants.MAPPING_PORTAL));
454     }
455 
456     /**
457      * Returns a question callback for the Cancel Purchase Order action.
458      *
459      * @return A PurQuestionCallback with a post-question activity appropriate to the Cancel PO action
460      */
461     protected PurQuestionCallback cancelPOActionCallbackMethod() {
462 
463         return new PurQuestionCallback() {
464             @Override
465             public AccountsPayableDocument doPostQuestion(AccountsPayableDocument document, String noteText) throws Exception {
466                 // base impl do nothing
467                 return document;
468             }
469         };
470     }
471 
472     /**
473      * Returns a question callback for the Cancel action.
474      *
475      * @return A PurQuestionCallback which does post-question tasks appropriate to Cancellation.
476      */
477     protected PurQuestionCallback cancelCallbackMethod() {
478         return new PurQuestionCallback() {
479             @Override
480             public AccountsPayableDocument doPostQuestion(AccountsPayableDocument document, String noteText) throws Exception {
481                 SpringContext.getBean(AccountsPayableService.class).cancelAccountsPayableDocumentByCheckingDocumentStatus(document, noteText);
482                 return document;
483             }
484         };
485     }
486 
487     protected ActionForward askQuestionsAndPerformReopenPurchaseOrder(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
488         LOG.debug("askQuestionsAndPerformDocumentAction started.");
489         KualiDocumentFormBase kualiDocumentFormBase = (KualiDocumentFormBase) form;
490         AccountsPayableDocumentBase apDoc = (AccountsPayableDocumentBase) kualiDocumentFormBase.getDocument();
491         Object question = request.getParameter(OLEConstants.QUESTION_INST_ATTRIBUTE_NAME);
492         String questionType = PODocumentsStrings.REOPEN_PO_QUESTION;
493         String confirmType = PODocumentsStrings.CONFIRM_REOPEN_QUESTION;
494         String messageType = PurapKeyConstants.PURCHASE_ORDER_MESSAGE_REOPEN_DOCUMENT;
495         String operation = "Reopen ";
496 
497         try {
498             ConfigurationService kualiConfiguration = SpringContext.getBean(ConfigurationService.class);
499 
500             // Start in logic for confirming the proposed operation.
501             if (ObjectUtils.isNull(question)) {
502                 String key = kualiConfiguration.getPropertyValueAsString(PurapKeyConstants.PURCHASE_ORDER_QUESTION_DOCUMENT);
503                 String message = StringUtils.replace(key, "{0}", operation);
504                 return this.performQuestionWithoutInput(mapping, form, request, response, questionType, message, OLEConstants.CONFIRMATION_QUESTION, questionType, "");
505             } else {
506                 Object buttonClicked = request.getParameter(OLEConstants.QUESTION_CLICKED_BUTTON);
507                 if (question.equals(questionType) && buttonClicked.equals(ConfirmationQuestion.NO)) {
508                     // If 'No' is the button clicked, just reload the doc
509                     return mapping.findForward(OLEConstants.MAPPING_BASIC);
510                 } else if (question.equals(confirmType) && buttonClicked.equals(SingleConfirmationQuestion.OK)) {
511                     // This is the case when the user clicks on "OK" in the end; redirect to the preq doc
512                     return mapping.findForward(OLEConstants.MAPPING_BASIC);
513                 }
514             }
515 
516             PurchaseOrderDocument po = apDoc.getPurchaseOrderDocument();
517             if (!po.isPendingActionIndicator() && PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED.equals(po.getApplicationDocumentStatus())) {
518                 /*
519                  * Below if-else code block calls PurchaseOrderService methods that will throw ValidationException objects if errors
520                  * occur during any process in the attempt to perform its actions. Assume, if these return successfully, that the
521                  * PurchaseOrderDocument object returned from each is the newly created document and that all actions in the method
522                  * were run correctly. NOTE: IF BELOW IF-ELSE IS EDITED THE NEW METHODS CALLED MUST THROW ValidationException OBJECT
523                  * IF AN ERROR IS ADDED TO THE GlobalVariables
524                  */
525                 po = initiateReopenPurchaseOrder(po, kualiDocumentFormBase.getAnnotation());
526 
527                 if (!GlobalVariables.getMessageMap().hasNoErrors()) {
528                     throw new ValidationException("errors occurred during new PO creation");
529                 }
530 
531                 if (StringUtils.isNotEmpty(messageType)) {
532                     KNSGlobalVariables.getMessageList().add(messageType);
533                 }
534                 return this.performQuestionWithoutInput(mapping, form, request, response, confirmType, kualiConfiguration.getPropertyValueAsString(messageType), PODocumentsStrings.SINGLE_CONFIRMATION_QUESTION, questionType, "");
535             } else {
536                 return this.performQuestionWithoutInput(mapping, form, request, response, confirmType, "Unable to reopen the PO at this time due to the incorrect PO status or a pending PO change document.", PODocumentsStrings.SINGLE_CONFIRMATION_QUESTION, questionType, "");
537             }
538 
539         } catch (ValidationException ve) {
540             throw ve;
541         }
542     }
543 
544     public PurchaseOrderDocument initiateReopenPurchaseOrder(PurchaseOrderDocument po, String annotation) {
545         try {
546             LogicContainer logicToRun = new LogicContainer() {
547                 @Override
548                 public Object runLogic(Object[] objects) throws Exception {
549                     PurchaseOrderDocument po = (PurchaseOrderDocument) objects[0];
550 
551                     Note cancelNote = new Note();
552                     cancelNote.setNoteTypeCode(po.getNoteType().getCode());
553                     cancelNote.setAuthorUniversalIdentifier(GlobalVariables.getUserSession().getPerson().getPrincipalId());
554                     cancelNote.setNoteText(SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(PurapKeyConstants.AP_REOPENS_PURCHASE_ORDER_NOTE));
555                    // Added to include posted Date for the Note JIRA OLE-5520
556                     Timestamp postTime = new Timestamp((new Date()).getTime());
557                     cancelNote.setNotePostedTimestamp(postTime);
558                     po.addNote(cancelNote);
559                     SpringContext.getBean(PurapService.class).saveDocumentNoValidation(po);
560 
561                     return SpringContext.getBean(PurchaseOrderService.class).createAndRoutePotentialChangeDocument(po.getDocumentNumber(), PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT, (String) objects[1], null, PurchaseOrderStatuses.APPDOC_PENDING_REOPEN);
562                 }
563             };
564             return (PurchaseOrderDocument) SpringContext.getBean(PurapService.class).performLogicWithFakedUserSession(getOleSelectDocumentService().getSelectParameterValue(OLEConstants.SYSTEM_USER), logicToRun, new Object[]{po, annotation});
565         } catch (WorkflowException e) {
566             String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
567             LOG.error(errorMsg, e);
568             throw new RuntimeException(errorMsg, e);
569         } catch (Exception e) {
570             throw new RuntimeException(e);
571         }
572     }
573 
574     /**
575      * gets the item from preq and restores the values from the original PO and then
576      * redistributes the amounts based on extended cost and quantity
577      *
578      * @param mapping
579      * @param form
580      * @param request
581      * @param response
582      * @return actionForward
583      * @throws Exception
584      */
585     public ActionForward recalculateItemAccountsAmounts(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
586         AccountsPayableFormBase payableForm = (AccountsPayableFormBase) form;
587         AccountsPayableDocument apDoc = (AccountsPayableDocument) payableForm.getDocument();
588 
589         PurapAccountingService purapAccountingService = SpringContext.getBean(PurapAccountingService.class);
590 
591         String[] indexes = getSelectedItemNumber(request);
592         int itemIndex = Integer.parseInt(indexes[0]);
593 
594         PurApItem item = apDoc.getItem((itemIndex));
595 
596         //first reset the the corresponding po items accounts amounts to this item
597         restoreItemAccountsAmounts(apDoc, item);
598 
599         item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE);
600 
601         final KualiDecimal itemExtendedPrice = (item.getExtendedPrice() == null) ? KualiDecimal.ZERO : item.getExtendedPrice();
602         ;
603         if (item.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
604             KualiDecimal newExtendedPrice = item.calculateExtendedPrice();
605             item.setExtendedPrice(newExtendedPrice);
606         }
607 
608         PaymentRequestDocument preqDoc = (PaymentRequestDocument) apDoc;
609 
610         // set amounts on any empty
611         preqDoc.updateExtendedPriceOnItems();
612 
613         // calculation just for the tax area, only at tax review stage
614         // by now, the general calculation shall have been done.
615         if (preqDoc.getApplicationDocumentStatus().equals(PaymentRequestStatuses.APPDOC_AWAITING_TAX_REVIEW)) {
616             SpringContext.getBean(PaymentRequestService.class).calculateTaxArea(preqDoc);
617         }
618 
619         // notice we're ignoring whether the boolean, because these are just warnings they shouldn't halt anything
620         //Calculate Payment request before rules since the rule check totalAmount.
621         SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc, true);
622         SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedCalculateAccountsPayableEvent(preqDoc));
623 
624 //        PurchasingAccountsPayableDocumentBase document = (PurchasingAccountsPayableDocumentBase) apDoc;
625 //        String accountDistributionMethod = document.getAccountDistributionMethod();
626 //
627 //        if (PurapConstants.AccountDistributionMethodCodes.SEQUENTIAL_CODE.equalsIgnoreCase(accountDistributionMethod)) {
628 //            // update the accounts amounts for PREQ and distribution method = sequential
629 //            purapAccountingService.updatePreqItemAccountAmounts(item);
630 //        }
631 //        else {
632 //                List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
633 //                for (PurApAccountingLine acctLine : sourceAccountingLines) {
634 //                    acctLine.setAmount(KualiDecimal.ZERO);
635 //                }
636 //
637 //                purapAccountingService.updatePreqProportionalItemAccountAmounts(item);
638 //             }
639 
640         return mapping.findForward(OLEConstants.MAPPING_BASIC);
641     }
642 
643     /**
644      * gets the item from preq and restores the values from the original PO
645      *
646      * @param mapping
647      * @param form
648      * @param request
649      * @param response
650      * @return actionForward
651      * @throws Exception
652      */
653     public ActionForward restoreItemAccountsAmounts(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
654         AccountsPayableFormBase payableForm = (AccountsPayableFormBase) form;
655         AccountsPayableDocument apDoc = (AccountsPayableDocument) payableForm.getDocument();
656 
657         String[] indexes = getSelectedItemNumber(request);
658         int itemIndex = Integer.parseInt(indexes[0]);
659 
660         PurApItem item = apDoc.getItem((itemIndex));
661 
662         //first reset the the corresponding po items accounts amounts to this item
663         restoreItemAccountsAmounts(apDoc, item);
664 
665         item.setItemQuantity(null);
666         item.setItemTaxAmount(null);
667 
668         item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE);
669 
670         final KualiDecimal itemExtendedPrice = (item.getExtendedPrice() == null) ? KualiDecimal.ZERO : item.getExtendedPrice();
671         ;
672         if (item.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
673             KualiDecimal newExtendedPrice = item.calculateExtendedPrice();
674             item.setExtendedPrice(newExtendedPrice);
675         }
676 
677         return mapping.findForward(OLEConstants.MAPPING_BASIC);
678     }
679 
680     /**
681      * Will return an array of Strings containing 2 indexes, the first String is the item index and the second String is the account
682      * index. These are obtained by parsing the method to call parameter from the request, between the word ".line" and "." The
683      * indexes are separated by a semicolon (:)
684      *
685      * @param request The HttpServletRequest
686      * @return An array of Strings containing pairs of two indices, an item index
687      */
688     protected String[] getSelectedItemNumber(HttpServletRequest request) {
689         String itemString = new String();
690         String parameterName = (String) request.getAttribute(OLEConstants.METHOD_TO_CALL_ATTRIBUTE);
691         if (StringUtils.isNotBlank(parameterName)) {
692             itemString = StringUtils.substringBetween(parameterName, ".line", ".");
693         }
694         String[] result = StringUtils.split(itemString, ":");
695 
696         return result;
697     }
698 
699     /**
700      * restores the preq preqItem' accounts amounts with po's item's account lines
701      * amounts.
702      *
703      * @param apDoc
704      * @param preqItem
705      */
706     protected void restoreItemAccountsAmounts(AccountsPayableDocument apDoc, PurApItem preqItem) {
707         List<PurApItem> pOItems = apDoc.getPurchaseOrderDocument().getItems();
708 
709         PurApItem pOItem = getPOItem(pOItems, preqItem.getItemLineNumber());
710         if (ObjectUtils.isNotNull(pOItem)) {
711             //   preqItem.setItemUnitPrice(pOItem.getItemUnitPrice());
712             List<PurApAccountingLine> preqAccountingLines = preqItem.getSourceAccountingLines();
713             for (PurApAccountingLine lineAcct : preqAccountingLines) {
714                 updateItemAccountLine(pOItem, lineAcct);
715             }
716         }
717     }
718 
719     /**
720      * returns the matching po item based on matching item identifier and item line numbner
721      * from preq items.
722      *
723      * @param purchaseItems
724      * @param itemLineNumber
725      * @return pOItem
726      */
727     protected PurApItem getPOItem(List<PurApItem> pOItems, Integer itemLineNumber) {
728         PurApItem pOItem = null;
729 
730         for (PurApItem poItem : pOItems) {
731             if (poItem.getItemLineNumber().compareTo(itemLineNumber) == 0) {
732                 //found the matching preqItem so return it...
733                 return poItem;
734             }
735         }
736 
737         return pOItem;
738     }
739 
740     /**
741      * finds the line with matching sequence number, chart code, account number, financial
742      * object code and updates amount/percent on the account line.
743      *
744      * @param pOItem
745      * @param lineAcct
746      */
747     protected void updateItemAccountLine(PurApItem pOItem, PurApAccountingLine lineAcct) {
748         List<PurApAccountingLine> pOAccountingLines = pOItem.getSourceAccountingLines();
749         for (PurApAccountingLine pOLineAcct : pOAccountingLines) {
750             if (lineAcct.getChartOfAccountsCode().equalsIgnoreCase(pOLineAcct.getChartOfAccountsCode()) &&
751                     lineAcct.getAccountNumber().equalsIgnoreCase(pOLineAcct.getAccountNumber()) &&
752                     lineAcct.getFinancialObjectCode().equalsIgnoreCase(pOLineAcct.getFinancialObjectCode())) {
753                 lineAcct.setAmount(pOLineAcct.getAmount());
754                 lineAcct.setAccountLinePercent(pOLineAcct.getAccountLinePercent());
755             }
756         }
757     }
758 
759     public OleSelectDocumentService getOleSelectDocumentService() {
760         if(oleSelectDocumentService == null){
761             oleSelectDocumentService = SpringContext.getBean(OleSelectDocumentService.class);
762         }
763         return oleSelectDocumentService;
764     }
765 
766     public void setOleSelectDocumentService(OleSelectDocumentService oleSelectDocumentService) {
767         this.oleSelectDocumentService = oleSelectDocumentService;
768     }
769 }