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