001/*
002 * Copyright 2007 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 */
016package org.kuali.ole.module.purap.document.web.struts;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.struts.action.ActionForm;
020import org.apache.struts.action.ActionForward;
021import org.apache.struts.action.ActionMapping;
022import org.kuali.ole.module.purap.PurapConstants;
023import org.kuali.ole.module.purap.PurapConstants.*;
024import org.kuali.ole.module.purap.PurapKeyConstants;
025import org.kuali.ole.module.purap.PurapPropertyConstants;
026import org.kuali.ole.module.purap.SingleConfirmationQuestion;
027import org.kuali.ole.module.purap.businessobject.PurApAccountingLine;
028import org.kuali.ole.module.purap.businessobject.PurApItem;
029import org.kuali.ole.module.purap.document.*;
030import org.kuali.ole.module.purap.document.service.*;
031import org.kuali.ole.module.purap.document.validation.event.AttributedCalculateAccountsPayableEvent;
032import org.kuali.ole.module.purap.document.validation.event.AttributedPreCalculateAccountsPayableEvent;
033import org.kuali.ole.module.purap.service.PurapAccountingService;
034import org.kuali.ole.module.purap.util.PurQuestionCallback;
035import org.kuali.ole.select.document.service.OleSelectDocumentService;
036import org.kuali.ole.sys.OLEConstants;
037import org.kuali.ole.sys.OLEPropertyConstants;
038import org.kuali.ole.sys.context.SpringContext;
039import org.kuali.ole.vnd.VendorConstants;
040import org.kuali.ole.vnd.businessobject.VendorAddress;
041import org.kuali.rice.core.api.config.property.ConfigurationService;
042import org.kuali.rice.core.api.util.RiceKeyConstants;
043import org.kuali.rice.core.api.util.type.KualiDecimal;
044import org.kuali.rice.kew.api.exception.WorkflowException;
045import org.kuali.rice.kns.question.ConfirmationQuestion;
046import org.kuali.rice.kns.service.DataDictionaryService;
047import org.kuali.rice.kns.util.KNSGlobalVariables;
048import org.kuali.rice.kns.util.MessageList;
049import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
050import org.kuali.rice.krad.bo.Note;
051import org.kuali.rice.krad.exception.ValidationException;
052import org.kuali.rice.krad.service.BusinessObjectService;
053import org.kuali.rice.krad.service.KualiRuleService;
054import org.kuali.rice.krad.util.GlobalVariables;
055import org.kuali.rice.krad.util.ObjectUtils;
056import org.kuali.rice.krad.util.UrlFactory;
057
058import javax.servlet.http.HttpServletRequest;
059import javax.servlet.http.HttpServletResponse;
060import java.util.Iterator;
061import java.util.List;
062import java.util.Properties;
063import java.util.TreeMap;
064import java.util.Date;
065import java.sql.Timestamp;
066
067/**
068 * Struts Action for Accounts Payable documents.
069 */
070public class AccountsPayableActionBase extends PurchasingAccountsPayableActionBase {
071    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountsPayableActionBase.class);
072
073    private OleSelectDocumentService oleSelectDocumentService;
074
075    @Override
076    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
077        PurchasingAccountsPayableFormBase baseForm = (PurchasingAccountsPayableFormBase) form;
078
079        ActionForward fwd = super.execute(mapping, form, request, response);
080
081        AccountsPayableDocumentBase document = (AccountsPayableDocumentBase) baseForm.getDocument();
082        boolean foundAccountExpiredWarning = false;
083        for (int i = 0; i < KNSGlobalVariables.getMessageList().size(); i++) {
084            if (StringUtils.equals(KNSGlobalVariables.getMessageList().get(i).getErrorKey(), PurapKeyConstants.MESSAGE_CLOSED_OR_EXPIRED_ACCOUNTS_REPLACED)) {
085                foundAccountExpiredWarning = true;
086            }
087        }
088
089        if (!foundAccountExpiredWarning) {
090            SpringContext.getBean(AccountsPayableService.class).generateExpiredOrClosedAccountWarning(document);
091        }
092
093        return fwd;
094
095    }
096
097    /**
098     * Performs refresh of objects after a lookup.
099     *
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}