001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.purap.document.web.struts;
017    
018    import java.util.HashMap;
019    import java.util.List;
020    
021    import javax.servlet.http.HttpServletRequest;
022    import javax.servlet.http.HttpServletResponse;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.struts.action.ActionForm;
026    import org.apache.struts.action.ActionForward;
027    import org.apache.struts.action.ActionMapping;
028    import org.kuali.kfs.module.purap.PurapConstants;
029    import org.kuali.kfs.module.purap.PurapKeyConstants;
030    import org.kuali.kfs.module.purap.PurapPropertyConstants;
031    import org.kuali.kfs.module.purap.PurapConstants.PREQDocumentsStrings;
032    import org.kuali.kfs.module.purap.PurapConstants.PaymentRequestStatuses;
033    import org.kuali.kfs.module.purap.document.AccountsPayableDocument;
034    import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
035    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
036    import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
037    import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
038    import org.kuali.kfs.module.purap.document.service.PurapService;
039    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
040    import org.kuali.kfs.module.purap.document.validation.event.AttributedCalculateAccountsPayableEvent;
041    import org.kuali.kfs.module.purap.document.validation.event.AttributedContinuePurapEvent;
042    import org.kuali.kfs.module.purap.document.validation.event.AttributedPreCalculateAccountsPayableEvent;
043    import org.kuali.kfs.module.purap.service.PurapAccountingService;
044    import org.kuali.kfs.module.purap.util.PurQuestionCallback;
045    import org.kuali.kfs.sys.KFSConstants;
046    import org.kuali.kfs.sys.KFSKeyConstants;
047    import org.kuali.kfs.sys.KFSPropertyConstants;
048    import org.kuali.kfs.module.purap.PurapKeyConstants;
049    import org.kuali.kfs.sys.context.SpringContext;
050    import org.kuali.kfs.sys.service.UniversityDateService;
051    import org.kuali.rice.kew.exception.WorkflowException;
052    import org.kuali.rice.kim.util.KimConstants;
053    import org.kuali.rice.kns.question.ConfirmationQuestion;
054    import org.kuali.rice.kns.service.DocumentHelperService;
055    import org.kuali.rice.kns.service.KualiConfigurationService;
056    import org.kuali.rice.kns.service.KualiRuleService;
057    import org.kuali.rice.kns.util.GlobalVariables;
058    import org.kuali.rice.kns.util.KNSConstants;
059    import org.kuali.rice.kns.util.ObjectUtils;
060    import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
061    
062    /**
063     * Struts Action for Payment Request document.
064     */
065    public class PaymentRequestAction extends AccountsPayableActionBase {
066        static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PaymentRequestAction.class);
067    
068        /**
069         * Do initialization for a new payment request.
070         * 
071         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
072         */
073        @Override
074        protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
075            super.createDocument(kualiDocumentFormBase);
076            ((PaymentRequestDocument) kualiDocumentFormBase.getDocument()).initiateDocument();
077        }
078    
079        /**
080         * @see org.kuali.rice.kns.web.struts.action.KualiAction#refresh(org.apache.struts.action.ActionMapping,
081         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
082         */
083        @Override
084        public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
085            PaymentRequestForm preqForm = (PaymentRequestForm) form;
086            PaymentRequestDocument document = (PaymentRequestDocument) preqForm.getDocument();
087    
088            return super.refresh(mapping, form, request, response);
089        }
090    
091        /**
092         * Executes the continue action on a payment request. Populates and initializes the rest of the payment request besides what was
093         * shown on the init screen.
094         * 
095         * @param mapping An ActionMapping
096         * @param form An ActionForm
097         * @param request The HttpServletRequest
098         * @param response The HttpServletResponse
099         * @throws Exception
100         * @return An ActionForward
101         */
102        public ActionForward continuePREQ(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
103            LOG.debug("continuePREQ() method");
104            PaymentRequestForm preqForm = (PaymentRequestForm) form;
105            PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument) preqForm.getDocument();
106    
107            boolean poNotNull = true;
108           
109            boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedContinuePurapEvent(paymentRequestDocument));
110            if (!rulePassed){
111                return mapping.findForward(KFSConstants.MAPPING_BASIC);
112            }
113            
114            GlobalVariables.getMessageMap().clearErrorPath();
115            GlobalVariables.getMessageMap().addToErrorPath(KFSPropertyConstants.DOCUMENT);
116            
117          //check for a po id
118            if (ObjectUtils.isNull(paymentRequestDocument.getPurchaseOrderIdentifier())) {
119                GlobalVariables.getMessageMap().putError(PurapPropertyConstants.PURCHASE_ORDER_IDENTIFIER, KFSKeyConstants.ERROR_REQUIRED, PREQDocumentsStrings.PURCHASE_ORDER_ID);
120                poNotNull = false;
121            }
122    
123            if (ObjectUtils.isNull(paymentRequestDocument.getInvoiceDate())) {
124                GlobalVariables.getMessageMap().putError(PurapPropertyConstants.INVOICE_DATE, KFSKeyConstants.ERROR_REQUIRED, PREQDocumentsStrings.INVOICE_DATE);
125                poNotNull = false;
126            }
127    
128            if (ObjectUtils.isNull(paymentRequestDocument.getInvoiceNumber())) {
129                GlobalVariables.getMessageMap().putError(PurapPropertyConstants.INVOICE_NUMBER, KFSKeyConstants.ERROR_REQUIRED, PREQDocumentsStrings.INVOICE_NUMBER);
130                poNotNull = false;
131            }
132            paymentRequestDocument.setInvoiceNumber(paymentRequestDocument.getInvoiceNumber().toUpperCase());
133    
134            if (ObjectUtils.isNull(paymentRequestDocument.getVendorInvoiceAmount())) {
135                GlobalVariables.getMessageMap().putError(PurapPropertyConstants.VENDOR_INVOICE_AMOUNT, KFSKeyConstants.ERROR_REQUIRED, PREQDocumentsStrings.VENDOR_INVOICE_AMOUNT);
136                poNotNull = false;
137            }
138            
139            //exit early as the po is null, no need to proceed further until this is taken care of
140            if(poNotNull == false){
141                return mapping.findForward(KFSConstants.MAPPING_BASIC);
142            }
143            
144            
145            PurchaseOrderDocument po = SpringContext.getBean(PurchaseOrderService.class).getCurrentPurchaseOrder(paymentRequestDocument.getPurchaseOrderIdentifier());
146            if (ObjectUtils.isNotNull(po)) {
147                // TODO figure out a more straightforward way to do this.  ailish put this in so the link id would be set and the perm check would work
148                paymentRequestDocument.setAccountsPayablePurchasingDocumentLinkIdentifier(po.getAccountsPayablePurchasingDocumentLinkIdentifier());
149    
150                //check to see if user is allowed to initiate doc based on PO sensitive data
151                if (!SpringContext.getBean(DocumentHelperService.class).getDocumentAuthorizer(paymentRequestDocument).isAuthorizedByTemplate(paymentRequestDocument, KNSConstants.KNS_NAMESPACE, KimConstants.PermissionTemplateNames.OPEN_DOCUMENT, GlobalVariables.getUserSession().getPrincipalId())) {
152                    throw buildAuthorizationException("initiate document", paymentRequestDocument);
153                }
154            } 
155            
156            if(!SpringContext.getBean(PaymentRequestService.class).isPurchaseOrderValidForPaymentRequestDocumentCreation(paymentRequestDocument,po))
157            {
158                return mapping.findForward(KFSConstants.MAPPING_BASIC);
159            }
160            
161            // perform duplicate check which will forward to a question prompt if one is found
162            ActionForward forward = performDuplicatePaymentRequestAndEncumberFiscalYearCheck(mapping, form, request, response, paymentRequestDocument);
163            if (forward != null) {
164                return forward;
165            }
166    
167            // If we are here either there was no duplicate or there was a duplicate and the user hits continue, in either case we need
168            // to validate the business rules
169            SpringContext.getBean(PaymentRequestService.class).populateAndSavePaymentRequest(paymentRequestDocument);
170    
171            // force calculation
172            preqForm.setCalculated(false);
173    
174            //TODO if better, move this to the action just before preq goes into ATAX status
175            // force calculation for tax
176            preqForm.setCalculatedTax(false);
177            
178            // sort below the line
179            SpringContext.getBean(PurapService.class).sortBelowTheLine(paymentRequestDocument);
180    
181            // update the counts on the form
182            preqForm.updateItemCounts();
183    
184            return mapping.findForward(KFSConstants.MAPPING_BASIC);
185        }
186    
187    
188        /**
189         * Clears the initial fields on the <code>PaymentRequestDocument</code> which should be accessible from the given form.
190         * 
191         * @param mapping An ActionMapping
192         * @param form An ActionForm, which must be a PaymentRequestForm
193         * @param request The HttpServletRequest
194         * @param response The HttpServletResponse
195         * @throws Exception
196         * @return An ActionForward
197         */
198        public ActionForward clearInitFields(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
199            LOG.debug("clearInitValues() method");
200            PaymentRequestForm preqForm = (PaymentRequestForm) form;
201            PaymentRequestDocument paymentRequestDocument = (PaymentRequestDocument) preqForm.getDocument();
202            paymentRequestDocument.clearInitFields();
203    
204            return super.refresh(mapping, form, request, response);
205        }
206    
207        /**
208         * This method runs two checks based on the user input on PREQ initiate screen: Encumber next fiscal year check and Duplicate
209         * payment request check. Encumber next fiscal year is checked first and will display a warning message to the user if it's the
210         * case. Duplicate payment request check calls <code>PaymentRequestService</code> to perform the duplicate payment request
211         * check. If one is found, a question is setup and control is forwarded to the question action method. Coming back from the
212         * question prompt the button that was clicked is checked and if 'no' was selected they are forward back to the page still in
213         * init mode.
214         * 
215         * @param mapping An ActionMapping
216         * @param form An ActionForm
217         * @param request The HttpServletRequest
218         * @param response The HttpServletResponse
219         * @param paymentRequestDocument The PaymentRequestDocument
220         * @throws Exception
221         * @return An ActionForward
222         * @see org.kuali.kfs.module.purap.document.service.PaymentRequestService
223         */
224        protected ActionForward performDuplicatePaymentRequestAndEncumberFiscalYearCheck(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, PaymentRequestDocument paymentRequestDocument) throws Exception {
225            ActionForward forward = null;
226            Object question = request.getParameter(KFSConstants.QUESTION_INST_ATTRIBUTE_NAME);
227            if (question == null) {
228                // perform encumber next fiscal year check and prompt warning message if needs
229                if (isEncumberNextFiscalYear(paymentRequestDocument)) {
230                    String questionText = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(PurapKeyConstants.WARNING_ENCUMBER_NEXT_FY);
231                    return this.performQuestionWithoutInput(mapping, form, request, response, PREQDocumentsStrings.ENCUMBER_NEXT_FISCAL_YEAR_QUESTION, questionText, KFSConstants.CONFIRMATION_QUESTION, KFSConstants.ROUTE_METHOD, "");
232                }
233                else {
234                    // perform duplicate payment request check
235                    HashMap<String, String> duplicateMessages = SpringContext.getBean(PaymentRequestService.class).paymentRequestDuplicateMessages(paymentRequestDocument);
236                    if (!duplicateMessages.isEmpty()) {
237                        return this.performQuestionWithoutInput(mapping, form, request, response, PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, duplicateMessages.get(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION), KFSConstants.CONFIRMATION_QUESTION, KFSConstants.ROUTE_METHOD, "");
238                    }
239                }
240            }
241            else {
242                Object buttonClicked = request.getParameter(KFSConstants.QUESTION_CLICKED_BUTTON);
243                // If the user replies 'Yes' to the encumber-next-year-question, proceed with duplicate payment check
244                if (PurapConstants.PREQDocumentsStrings.ENCUMBER_NEXT_FISCAL_YEAR_QUESTION.equals(question) && ConfirmationQuestion.YES.equals(buttonClicked)) {
245                    HashMap<String, String> duplicateMessages = SpringContext.getBean(PaymentRequestService.class).paymentRequestDuplicateMessages(paymentRequestDocument);
246                    if (!duplicateMessages.isEmpty()) {
247                        return this.performQuestionWithoutInput(mapping, form, request, response, PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION, duplicateMessages.get(PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION), KFSConstants.CONFIRMATION_QUESTION, KFSConstants.ROUTE_METHOD, "");
248                    }
249                }
250                // If the user replies 'No' to either of the questions, redirect to the PREQ initiate page.
251                else if ((PurapConstants.PREQDocumentsStrings.ENCUMBER_NEXT_FISCAL_YEAR_QUESTION.equals(question) || PurapConstants.PREQDocumentsStrings.DUPLICATE_INVOICE_QUESTION.equals(question)) && ConfirmationQuestion.NO.equals(buttonClicked)) {
252                    paymentRequestDocument.setStatusCode(PurapConstants.PaymentRequestStatuses.INITIATE);
253                    forward = mapping.findForward(KFSConstants.MAPPING_BASIC);
254                }
255    
256            }
257    
258            return forward;
259        }
260    
261        /**
262         * Check if the current PREQ encumber next fiscal year from PO document.
263         * 
264         * @param paymentRequestDocument
265         * @return
266         */
267        protected boolean isEncumberNextFiscalYear(PaymentRequestDocument paymentRequestDocument) {
268            Integer fiscalYear = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear();
269            if (paymentRequestDocument.getPurchaseOrderDocument().getPostingYear().intValue() > fiscalYear) {
270                return true;
271            }
272            return false;
273        }
274        
275        /**
276         * Puts a payment on hold, prompting for a reason beforehand. This stops further approvals or routing.
277         * 
278         * @param mapping An ActionMapping
279         * @param form An ActionForm
280         * @param request The HttpServletRequest
281         * @param response The HttpServletResponse
282         * @throws Exception
283         * @return An ActionForward
284         */
285        public ActionForward addHoldOnPayment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
286            String operation = "Hold ";
287    
288            PurQuestionCallback callback = new PurQuestionCallback() {
289                public AccountsPayableDocument doPostQuestion(AccountsPayableDocument document, String noteText) throws Exception {
290                    document = SpringContext.getBean(PaymentRequestService.class).addHoldOnPaymentRequest((PaymentRequestDocument) document, noteText);
291                    return document;
292                }
293            };
294    
295            return askQuestionWithInput(mapping, form, request, response, PREQDocumentsStrings.HOLD_PREQ_QUESTION, PREQDocumentsStrings.HOLD_NOTE_PREFIX, operation, PurapKeyConstants.PAYMENT_REQUEST_MESSAGE_HOLD_DOCUMENT, callback);
296        }
297    
298        /**
299         * Removes a hold on the payment request.
300         * 
301         * @param mapping An ActionMapping
302         * @param form An ActionForm
303         * @param request The HttpServletRequest
304         * @param response The HttpServletResponse
305         * @throws Exception
306         * @return An ActionForward
307         */
308        public ActionForward removeHoldFromPayment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
309            String operation = "Remove ";
310    
311            PurQuestionCallback callback = new PurQuestionCallback() {
312                public AccountsPayableDocument doPostQuestion(AccountsPayableDocument document, String noteText) throws Exception {
313                    document = SpringContext.getBean(PaymentRequestService.class).removeHoldOnPaymentRequest((PaymentRequestDocument) document, noteText);
314                    return document;
315                }
316            };
317    
318            return askQuestionWithInput(mapping, form, request, response, PREQDocumentsStrings.REMOVE_HOLD_PREQ_QUESTION, PREQDocumentsStrings.REMOVE_HOLD_NOTE_PREFIX, operation, PurapKeyConstants.PAYMENT_REQUEST_MESSAGE_REMOVE_HOLD_DOCUMENT, callback);
319        }
320    
321        /**
322         * This action requests a cancel on a preq, prompting for a reason before hand. This stops further approvals or routing.
323         * 
324         * @param mapping An ActionMapping
325         * @param form An ActionForm
326         * @param request The HttpServletRequest
327         * @param response The HttpServletResponse
328         * @throws Exception
329         * @return An ActionForward
330         */
331        public ActionForward requestCancelOnPayment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
332            String operation = "Cancel ";
333    
334            PurQuestionCallback callback = new PurQuestionCallback() {
335                public AccountsPayableDocument doPostQuestion(AccountsPayableDocument document, String noteText) throws Exception {
336                    SpringContext.getBean(PaymentRequestService.class).requestCancelOnPaymentRequest((PaymentRequestDocument) document, noteText);
337                    return document;
338                }
339            };
340    
341            return askQuestionWithInput(mapping, form, request, response, PREQDocumentsStrings.CANCEL_PREQ_QUESTION, PREQDocumentsStrings.CANCEL_NOTE_PREFIX, operation, PurapKeyConstants.PAYMENT_REQUEST_MESSAGE_CANCEL_DOCUMENT, callback);
342        }
343    
344        /**
345         * @see org.kuali.kfs.module.purap.document.web.struts.AccountsPayableActionBase#cancelPOActionCallbackMethod()
346         */
347    //    @Override
348    //    protected PurQuestionCallback cancelPOActionCallbackMethod() {
349    //
350    //        return new PurQuestionCallback() {
351    //            public AccountsPayableDocument doPostQuestion(AccountsPayableDocument document, String noteText) throws Exception {
352    //                PaymentRequestDocument preqDocument = (PaymentRequestDocument) document;
353    //                preqDocument.setReopenPurchaseOrderIndicator(true);
354    //                return preqDocument;
355    //            }
356    //        };
357    //    }
358    
359        /**
360         * Removes a request for cancel on a payment request.
361         * 
362         * @param mapping An ActionMapping
363         * @param form An ActionForm
364         * @param request The HttpServletRequest
365         * @param response The HttpServletResponse
366         * @throws Exception
367         * @return An ActionForward
368         */
369        public ActionForward removeCancelRequestFromPayment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
370            String operation = "Cancel ";
371    
372            PurQuestionCallback callback = new PurQuestionCallback() {
373                public AccountsPayableDocument doPostQuestion(AccountsPayableDocument document, String noteText) throws Exception {
374                    SpringContext.getBean(PaymentRequestService.class).removeRequestCancelOnPaymentRequest((PaymentRequestDocument) document, noteText);
375                    return document;
376                }
377            };
378    
379            return askQuestionWithInput(mapping, form, request, response, PREQDocumentsStrings.REMOVE_CANCEL_PREQ_QUESTION, PREQDocumentsStrings.REMOVE_CANCEL_NOTE_PREFIX, operation, PurapKeyConstants.PAYMENT_REQUEST_MESSAGE_REMOVE_CANCEL_DOCUMENT, callback);
380        }
381    
382        /**
383         * Calls a service method to calculate for a payment request document.
384         * 
385         * @param apDoc The AccountsPayableDocument
386         */
387        @Override
388        protected void customCalculate(PurchasingAccountsPayableDocument apDoc) {
389            PaymentRequestDocument preqDoc = (PaymentRequestDocument) apDoc;
390    
391            // set amounts on any empty
392            preqDoc.updateExtendedPriceOnItems();
393    
394            // calculation just for the tax area, only at tax review stage
395            // by now, the general calculation shall have been done.
396            if (preqDoc.getStatusCode().equals(PaymentRequestStatuses.AWAITING_TAX_REVIEW)) {
397                SpringContext.getBean(PaymentRequestService.class).calculateTaxArea(preqDoc);
398                return;
399            }
400            
401            // notice we're ignoring whether the boolean, because these are just warnings they shouldn't halt anything
402            //Calculate Payment request before rules since the rule check totalAmount.
403            SpringContext.getBean(PaymentRequestService.class).calculatePaymentRequest(preqDoc, true);
404            SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedCalculateAccountsPayableEvent(preqDoc));
405        }
406    
407        /**
408         * @see org.kuali.kfs.module.purap.document.web.struts.AccountsPayableActionBase#getActionName()
409         */
410        @Override
411        public String getActionName() {
412            return PurapConstants.PAYMENT_REQUEST_ACTION_NAME;
413        }
414        
415        public ActionForward useAlternateVendor(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
416            PaymentRequestForm preqForm = (PaymentRequestForm) form;
417            PaymentRequestDocument document = (PaymentRequestDocument) preqForm.getDocument();
418                            
419            SpringContext.getBean(PaymentRequestService.class).changeVendor(
420                    document, document.getAlternateVendorHeaderGeneratedIdentifier(), document.getAlternateVendorDetailAssignedIdentifier());
421            
422            return mapping.findForward(KFSConstants.MAPPING_BASIC);
423        }
424    
425        public ActionForward useOriginalVendor(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
426            PaymentRequestForm preqForm = (PaymentRequestForm) form;
427            PaymentRequestDocument document = (PaymentRequestDocument) preqForm.getDocument();
428    
429            SpringContext.getBean(PaymentRequestService.class).changeVendor(
430                    document, document.getOriginalVendorHeaderGeneratedIdentifier(), document.getOriginalVendorDetailAssignedIdentifier());
431            
432            return mapping.findForward(KFSConstants.MAPPING_BASIC);
433        }
434        
435        public ActionForward route (ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
436            PaymentRequestDocument preq = ((PaymentRequestForm)form).getPaymentRequestDocument();
437            SpringContext.getBean(PurapService.class).prorateForTradeInAndFullOrderDiscount(preq);
438            SpringContext.getBean(PurapAccountingService.class).updateAccountAmounts(preq);
439            if (preq.isClosePurchaseOrderIndicator()) {
440                PurchaseOrderDocument po = preq.getPurchaseOrderDocument();
441                if (po.canClosePOForTradeIn()) {
442                    return super.route(mapping, form, request, response);
443                }
444                else {
445                    return mapping.findForward(KFSConstants.MAPPING_BASIC);
446                }
447            }
448            else {
449                return super.route(mapping, form, request, response);
450            }
451        }
452    
453        /**
454         * Overrides to invoke the updateAccountAmounts so that the account percentage will be 
455         * correctly updated before validation for account percent is called.
456         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#approve(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
457         */
458        public ActionForward approve(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
459            PaymentRequestDocument preq = ((PaymentRequestForm)form).getPaymentRequestDocument();
460            
461            SpringContext.getBean(PurapService.class).prorateForTradeInAndFullOrderDiscount(preq);
462            // if tax is required but not yet calculated, return and prompt user to calculate
463            if (requiresCalculateTax((PaymentRequestForm)form)) {
464                GlobalVariables.getMessageMap().putError(KFSConstants.DOCUMENT_ERRORS, PurapKeyConstants.ERROR_APPROVE_REQUIRES_CALCULATE);
465                return mapping.findForward(KFSConstants.MAPPING_BASIC);
466            }
467    
468            // enforce calculating tax again upon approval, just in case user changes tax data without calculation
469            // other wise there will be a loophole, because the taxCalculated indicator is already set upon first calculation
470            // and thus system wouldn't know it's not re-calculated after tax data are changed
471            if (SpringContext.getBean(KualiRuleService.class).applyRules(new AttributedPreCalculateAccountsPayableEvent(preq))) {
472                // pre-calculation rules succeed, calculate tax again and go ahead with approval
473                customCalculate(preq);
474                SpringContext.getBean(PurapAccountingService.class).updateAccountAmounts(preq);
475                return super.approve(mapping, form, request, response);
476            }
477            else { 
478                // pre-calculation rules fail, go back to same page with error messages            
479                return mapping.findForward(KFSConstants.MAPPING_BASIC);
480            }        
481        }
482        
483        /**
484         * Checks if tax calculation is required. 
485         * Currently it is required when preq is awaiting for tax approval and tax has not already been calculated. 
486         * 
487         * @param apForm A Form, which must inherit from <code>AccountsPayableFormBase</code>
488         * @return true if calculation is required, false otherwise
489         */
490        protected boolean requiresCalculateTax(PaymentRequestForm preqForm) {
491            PaymentRequestDocument preq = (PaymentRequestDocument) preqForm.getDocument();
492            boolean requiresCalculateTax = StringUtils.equals(preq.getStatusCode(), PaymentRequestStatuses.AWAITING_TAX_REVIEW) && !preqForm.isCalculatedTax(); 
493            return requiresCalculateTax;
494        }
495    
496        public ActionForward changeUseTaxIndicator(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
497            PurchasingAccountsPayableDocument document = (PurchasingAccountsPayableDocument) ((PurchasingAccountsPayableFormBase) form).getDocument();
498            
499            //clear/recalculate tax and recreate GL entries        
500            SpringContext.getBean(PurapService.class).updateUseTaxIndicator(document, !document.isUseTaxIndicator());
501            SpringContext.getBean(PurapService.class).calculateTax(document);
502            
503            //TODO: add recalculate GL entries hook here
504            
505            return mapping.findForward(KFSConstants.MAPPING_BASIC);
506        }
507    
508    }