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.sys.web.struts;
017    
018    import static org.kuali.kfs.sys.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_INVALID_ACCOUNT;
019    import static org.kuali.kfs.sys.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_REQUIRED;
020    import static org.kuali.kfs.sys.KFSKeyConstants.ERROR_REQUIRED;
021    
022    import java.io.FileNotFoundException;
023    import java.io.IOException;
024    import java.util.ArrayList;
025    import java.util.Arrays;
026    import java.util.HashMap;
027    import java.util.HashSet;
028    import java.util.Iterator;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Properties;
032    import java.util.Set;
033    
034    import javax.servlet.http.HttpServletRequest;
035    import javax.servlet.http.HttpServletResponse;
036    
037    import org.apache.commons.lang.StringUtils;
038    import org.apache.struts.action.ActionForm;
039    import org.apache.struts.action.ActionForward;
040    import org.apache.struts.action.ActionMapping;
041    import org.apache.struts.upload.FormFile;
042    import org.kuali.kfs.coa.service.AccountService;
043    import org.kuali.kfs.fp.businessobject.CapitalAssetInformation;
044    import org.kuali.kfs.fp.businessobject.CapitalAssetInformationDetail;
045    import org.kuali.kfs.fp.businessobject.SalesTax;
046    import org.kuali.kfs.fp.document.CapitalAssetEditable;
047    import org.kuali.kfs.sys.KFSConstants;
048    import org.kuali.kfs.sys.KFSKeyConstants;
049    import org.kuali.kfs.sys.KFSPropertyConstants;
050    import org.kuali.kfs.sys.businessobject.AccountingLine;
051    import org.kuali.kfs.sys.businessobject.AccountingLineOverride;
052    import org.kuali.kfs.sys.businessobject.AccountingLineParser;
053    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
054    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
055    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
056    import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
057    import org.kuali.kfs.sys.context.SpringContext;
058    import org.kuali.kfs.sys.document.AccountingDocument;
059    import org.kuali.kfs.sys.document.AmountTotaling;
060    import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent;
061    import org.kuali.kfs.sys.document.validation.event.DeleteAccountingLineEvent;
062    import org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER;
063    import org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase;
064    import org.kuali.kfs.sys.exception.AccountingLineParserException;
065    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
066    import org.kuali.rice.kew.exception.WorkflowException;
067    import org.kuali.rice.kns.service.BusinessObjectService;
068    import org.kuali.rice.kns.service.DataDictionaryService;
069    import org.kuali.rice.kns.service.DictionaryValidationService;
070    import org.kuali.rice.kns.service.KualiRuleService;
071    import org.kuali.rice.kns.service.ParameterEvaluator;
072    import org.kuali.rice.kns.service.ParameterService;
073    import org.kuali.rice.kns.service.PersistenceService;
074    import org.kuali.rice.kns.util.GlobalVariables;
075    import org.kuali.rice.kns.util.KNSConstants;
076    import org.kuali.rice.kns.util.ObjectUtils;
077    import org.kuali.rice.kns.util.Timer;
078    import org.kuali.rice.kns.util.UrlFactory;
079    import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
080    
081    /**
082     * This class handles UI actions for all shared methods of financial documents.
083     */
084    public class KualiAccountingDocumentActionBase extends FinancialSystemTransactionalDocumentActionBase {
085        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiAccountingDocumentActionBase.class);
086    
087        /**
088         * Adds check for accountingLine updates, generates and dispatches any events caused by such updates
089         * 
090         * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm,
091         *      javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
092         */
093        @Override
094        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
095            Timer t0 = new Timer("KualiFinancialDocumentFormBase.execute");
096            KualiAccountingDocumentFormBase transForm = (KualiAccountingDocumentFormBase) form;
097    
098            // handle changes to accountingLines
099            if (transForm.hasDocumentId()) {
100                AccountingDocument financialDocument = (AccountingDocument) transForm.getDocument();
101    
102                processAccountingLines(financialDocument, transForm, KFSConstants.SOURCE);
103                processAccountingLines(financialDocument, transForm, KFSConstants.TARGET);
104            }
105    
106            // This is after a potential handleUpdate(), to display automatically cleared overrides following a route or save.
107            processAccountingLineOverrides(transForm);
108    
109            // proceed as usual
110            ActionForward result = super.execute(mapping, form, request, response);
111            t0.log();
112            return result;
113        }
114    
115        /**
116         * All document-load operations get routed through here
117         * 
118         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
119         */
120        @Override
121        protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
122            super.loadDocument(kualiDocumentFormBase);
123    
124    
125            KualiAccountingDocumentFormBase tform = (KualiAccountingDocumentFormBase) kualiDocumentFormBase;
126    
127            // clear out the new accounting line holders
128            tform.setNewSourceLine(null);
129            tform.setNewTargetLine(null);
130    
131            processAccountingLineOverrides(tform);
132        }
133    
134        /**
135         * Needed to override this to keep from losing Sales Tax information
136         * 
137         * @see org.kuali.rice.kns.web.struts.action.KualiAction#refresh(org.apache.struts.action.ActionMapping,
138         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
139         */
140        @Override
141        public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
142            super.refresh(mapping, form, request, response);
143            refreshSalesTaxInfo(form);
144    
145            return mapping.findForward(KFSConstants.MAPPING_BASIC);
146        }
147    
148        /**
149         * Needed to override this to keep from losing Sales Tax information
150         * 
151         * @see org.kuali.rice.kns.web.struts.action.KualiAction#toggleTab(org.apache.struts.action.ActionMapping,
152         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
153         */
154        @Override
155        public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
156            super.toggleTab(mapping, form, request, response);
157            refreshSalesTaxInfo(form);
158    
159            return mapping.findForward(KFSConstants.MAPPING_BASIC);
160        }
161    
162    
163        // Set of actions for which updateEvents should be generated
164        protected static final Set UPDATE_EVENT_ACTIONS;
165        static {
166            String[] updateEventActions = { KFSConstants.SAVE_METHOD, KFSConstants.ROUTE_METHOD, KFSConstants.APPROVE_METHOD, KFSConstants.BLANKET_APPROVE_METHOD };
167            UPDATE_EVENT_ACTIONS = new HashSet();
168            for (int i = 0; i < updateEventActions.length; ++i) {
169                UPDATE_EVENT_ACTIONS.add(updateEventActions[i]);
170            }
171        }
172    
173        /**
174         * @param transForm
175         */
176        protected void processAccountingLineOverrides(KualiAccountingDocumentFormBase transForm) {
177            processAccountingLineOverrides(transForm.getNewSourceLine());
178            processAccountingLineOverrides(transForm.getNewTargetLine());
179            if (transForm.hasDocumentId()) {
180                AccountingDocument financialDocument = (AccountingDocument) transForm.getDocument();
181    
182                processAccountingLineOverrides(financialDocument.getSourceAccountingLines());
183                processAccountingLineOverrides(financialDocument.getTargetAccountingLines());
184            }
185        }
186    
187        /**
188         * @param line
189         */
190        protected void processAccountingLineOverrides(AccountingLine line) {
191            processAccountingLineOverrides(Arrays.asList(new AccountingLine[] { line }));
192        }
193    
194        /**
195         * @param accountingLines
196         */
197        protected void processAccountingLineOverrides(List accountingLines) {
198            if (!accountingLines.isEmpty()) {
199                SpringContext.getBean(PersistenceService.class).retrieveReferenceObjects(accountingLines, AccountingLineOverride.REFRESH_FIELDS);
200    
201                for (Iterator i = accountingLines.iterator(); i.hasNext();) {
202                    AccountingLine line = (AccountingLine) i.next();
203                    AccountingLineOverride.processForOutput(line);
204                }
205            }
206        }
207    
208        /**
209         * @param transDoc
210         * @param transForm
211         * @param lineSet
212         */
213        protected void processAccountingLines(AccountingDocument transDoc, KualiAccountingDocumentFormBase transForm, String lineSet) {
214            // figure out which set of lines we're looking at
215            List formLines;
216            String pathPrefix;
217            boolean source;
218            if (lineSet.equals(KFSConstants.SOURCE)) {
219                formLines = transDoc.getSourceAccountingLines();
220                pathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_SOURCE_ACCT_LINE_PROPERTY_NAME;
221                source = true;
222            }
223            else {
224                formLines = transDoc.getTargetAccountingLines();
225                pathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_TARGET_ACCT_LINE_PROPERTY_NAME;
226                source = false;
227            }
228    
229            // find and process corresponding form and baselines
230            int index = 0;
231            for (Iterator i = formLines.iterator(); i.hasNext(); index++) {
232                AccountingLine formLine = (AccountingLine) i.next();
233    
234                // update sales tax required attribute for view
235                // handleSalesTaxRequired(transDoc, formLine, source, false, index);
236                checkSalesTax(transDoc, formLine, source, false, index);
237            }
238        }
239    
240        /**
241         * Automatically clears any overrides that have become unneeded. This is for accounting lines that were changed right before
242         * final actions like route. Normally the unneeded overrides are cleared in accountingLineOverrideField.tag instead, but that
243         * requires another form submit. This method shouldn't be called on lines that haven't changed, to avoid automatically changing
244         * read-only lines. This cannot be done in the Rule because Rules cannot change the AccountingLines; they only get a deepCopy.
245         * 
246         * @param formLine
247         */
248        protected void clearOverridesThatBecameUnneeded(AccountingLine formLine) {
249            AccountingLineOverride currentlyNeeded = AccountingLineOverride.determineNeededOverrides(formLine);
250            AccountingLineOverride currentOverride = AccountingLineOverride.valueOf(formLine.getOverrideCode());
251            if (!currentOverride.isValidMask(currentlyNeeded)) {
252                // todo: handle unsupported combinations of overrides (not a problem until we allow certain multiple overrides)
253            }
254            formLine.setOverrideCode(currentOverride.mask(currentlyNeeded).getCode());
255        }
256    
257        /**
258         * This method will remove a TargetAccountingLine from a FinancialDocument. This assumes that the user presses the delete button
259         * for a specific accounting line on the document and that the document is represented by a FinancialDocumentFormBase.
260         * 
261         * @param mapping
262         * @param form
263         * @param request
264         * @param response
265         * @return ActionForward
266         * @throws Exception
267         */
268        public ActionForward deleteTargetLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
269            KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;
270    
271            int deleteIndex = getLineToDelete(request);
272            String errorPath = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_TARGET_ACCT_LINE_PROPERTY_NAME + "[" + deleteIndex + "]";
273            boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new DeleteAccountingLineEvent(errorPath, financialDocumentForm.getDocument(), ((AccountingDocument) financialDocumentForm.getDocument()).getTargetAccountingLine(deleteIndex), false));
274    
275            // if the rule evaluation passed, let's delete it
276            if (rulePassed) {
277                deleteAccountingLine(false, financialDocumentForm, deleteIndex);
278            }
279            else {
280                String[] errorParams = new String[] { "target", Integer.toString(deleteIndex + 1) };
281                GlobalVariables.getMessageMap().putError(errorPath, KFSKeyConstants.ERROR_ACCOUNTINGLINE_DELETERULE_INVALIDACCOUNT, errorParams);
282            }
283    
284            return mapping.findForward(KFSConstants.MAPPING_BASIC);
285        }
286    
287        /**
288         * This method will remove a SourceAccountingLine from a FinancialDocument. This assumes that the user presses the delete button
289         * for a specific accounting line on the document and that the document is represented by a FinancialDocumentFormBase.
290         * 
291         * @param mapping
292         * @param form
293         * @param request
294         * @param response
295         * @return ActionForward
296         * @throws Exception
297         */
298        public ActionForward deleteSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
299            KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;
300    
301            int deleteIndex = getLineToDelete(request);
302            String errorPath = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_SOURCE_ACCT_LINE_PROPERTY_NAME + "[" + deleteIndex + "]";
303            boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new DeleteAccountingLineEvent(errorPath, financialDocumentForm.getDocument(), ((AccountingDocument) financialDocumentForm.getDocument()).getSourceAccountingLine(deleteIndex), false));
304    
305            // if the rule evaluation passed, let's delete it
306            if (rulePassed) {
307                deleteAccountingLine(true, financialDocumentForm, deleteIndex);
308            }
309            else {
310                String[] errorParams = new String[] { "source", Integer.toString(deleteIndex + 1) };
311                GlobalVariables.getMessageMap().putError(errorPath, KFSKeyConstants.ERROR_ACCOUNTINGLINE_DELETERULE_INVALIDACCOUNT, errorParams);
312            }
313    
314            return mapping.findForward(KFSConstants.MAPPING_BASIC);
315        }
316    
317    
318        /**
319         * Deletes the source or target accountingLine with the given index from the given form. Assumes that the rule- and form-
320         * validation have already occurred.
321         * 
322         * @param isSource
323         * @param financialDocumentForm
324         * @param deleteIndex
325         */
326        protected void deleteAccountingLine(boolean isSource, KualiAccountingDocumentFormBase financialDocumentForm, int deleteIndex) {
327            if (isSource) {
328                // remove from document
329                financialDocumentForm.getFinancialDocument().getSourceAccountingLines().remove(deleteIndex);
330    
331            }
332            else {
333                // remove from document
334                financialDocumentForm.getFinancialDocument().getTargetAccountingLines().remove(deleteIndex);
335            }
336            // update the doc total
337            AccountingDocument tdoc = (AccountingDocument) financialDocumentForm.getDocument();
338            if (tdoc instanceof AmountTotaling) {
339                ((FinancialSystemDocumentHeader) financialDocumentForm.getDocument().getDocumentHeader()).setFinancialDocumentTotalAmount(((AmountTotaling) tdoc).getTotalDollarAmount());
340            }
341    
342        }
343    
344    
345        /**
346         * This action executes a call to upload CSV accounting line values as TargetAccountingLines for a given transactional document.
347         * The "uploadAccountingLines()" method handles the multi-part request.
348         * 
349         * @param mapping
350         * @param form
351         * @param request
352         * @param response
353         * @return ActionForward
354         * @throws Exception
355         */
356        public ActionForward uploadTargetLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
357    
358            // call method that sourceform and destination list
359            uploadAccountingLines(false, form);
360    
361            return mapping.findForward(KFSConstants.MAPPING_BASIC);
362        }
363    
364    
365        /**
366         * This action executes a call to upload CSV accounting line values as SourceAccountingLines for a given transactional document.
367         * The "uploadAccountingLines()" method handles the multi-part request.
368         * 
369         * @param mapping
370         * @param form
371         * @param request
372         * @param response
373         * @return ActionForward
374         * @throws FileNotFoundException
375         * @throws IOException
376         */
377        public ActionForward uploadSourceLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws FileNotFoundException, IOException {
378            LOG.info("Uploading source accounting lines");
379            // call method that sourceform and destination list
380            uploadAccountingLines(true, form);
381    
382            return mapping.findForward(KFSConstants.MAPPING_BASIC);
383        }
384    
385        /**
386         * This method determines whether we are uploading source or target lines, and then calls uploadAccountingLines directly on the
387         * document object. This method handles retrieving the actual upload file as an input stream into the document.
388         * 
389         * @param isSource
390         * @param form
391         * @throws FileNotFoundException
392         * @throws IOException
393         */
394        protected void uploadAccountingLines(boolean isSource, ActionForm form) throws FileNotFoundException, IOException {
395            KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
396    
397            List importedLines = null;
398    
399            AccountingDocument financialDocument = tmpForm.getFinancialDocument();
400            AccountingLineParser accountingLineParser = financialDocument.getAccountingLineParser();
401    
402            // import the lines
403            String errorPathPrefix = null;
404            try {
405                if (isSource) {
406                    errorPathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS;
407                    FormFile sourceFile = tmpForm.getSourceFile();
408                    checkUploadFile(sourceFile);
409                    importedLines = accountingLineParser.importSourceAccountingLines(sourceFile.getFileName(), sourceFile.getInputStream(), financialDocument);
410                }
411                else {
412                    errorPathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS;
413                    FormFile targetFile = tmpForm.getTargetFile();
414                    checkUploadFile(targetFile);
415                    importedLines = accountingLineParser.importTargetAccountingLines(targetFile.getFileName(), targetFile.getInputStream(), financialDocument);
416                }
417            }
418            catch (AccountingLineParserException e) {
419                GlobalVariables.getMessageMap().putError(errorPathPrefix, e.getErrorKey(), e.getErrorParameters());
420            }
421    
422            // add line to list for those lines which were successfully imported
423            if (importedLines != null) {
424                for (Iterator i = importedLines.iterator(); i.hasNext();) {
425                    AccountingLine importedLine = (AccountingLine) i.next();
426                    insertAccountingLine(isSource, tmpForm, importedLine);
427                }
428            }
429        }
430    
431        protected void checkUploadFile(FormFile file) {
432            if (file == null) {
433                throw new AccountingLineParserException("invalid (null) upload file", KFSKeyConstants.ERROR_UPLOADFILE_NULL);
434            }
435        }
436    
437        /**
438         * This method will add a TargetAccountingLine to a FinancialDocument. This assumes that the user presses the add button for a
439         * specific accounting line on the document and that the document is represented by a FinancialDocumentFormBase. It first
440         * validates the line for data integrity and then checks appropriate business rules.
441         * 
442         * @param mapping
443         * @param form
444         * @param request
445         * @param response
446         * @return ActionForward
447         * @throws Exception
448         */
449        public ActionForward insertTargetLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
450            KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;
451            TargetAccountingLine line = financialDocumentForm.getNewTargetLine();
452            
453            // populate chartOfAccountsCode from account number if accounts cant cross chart and Javascript is turned off
454            //SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(line);
455            
456            boolean rulePassed = true;
457            // before we check the regular rules we need to check the sales tax rules
458            // TODO: Refactor rules so we no longer have to call this before a copy of the
459            // accountingLine
460            rulePassed &= checkSalesTax((AccountingDocument) financialDocumentForm.getDocument(), line, false, true, 0);
461    
462            // check any business rules
463            rulePassed &= SpringContext.getBean(KualiRuleService.class).applyRules(new AddAccountingLineEvent(KFSConstants.NEW_TARGET_ACCT_LINE_PROPERTY_NAME, financialDocumentForm.getDocument(), line));
464    
465            // if the rule evaluation passed, let's add it
466            if (rulePassed) {
467                // add accountingLine
468                SpringContext.getBean(PersistenceService.class).refreshAllNonUpdatingReferences(line);
469                insertAccountingLine(false, financialDocumentForm, line);
470    
471                // clear the used newTargetLine
472                financialDocumentForm.setNewTargetLine(null);
473            }
474    
475            return mapping.findForward(KFSConstants.MAPPING_BASIC);
476        }
477    
478    
479        /**
480         * This action executes an insert of a SourceAccountingLine into a document only after validating the accounting line and
481         * checking any appropriate business rules.
482         * 
483         * @param mapping
484         * @param form
485         * @param request
486         * @param response
487         * @return ActionForward
488         * @throws Exception
489         */
490        public ActionForward insertSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
491            KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;       
492            SourceAccountingLine line = financialDocumentForm.getNewSourceLine();
493            
494            // populate chartOfAccountsCode from account number if accounts cant cross chart and Javascript is turned off
495            //SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(line);
496            
497            boolean rulePassed = true;
498            // before we check the regular rules we need to check the sales tax rules
499            // TODO: Refactor rules so we no longer have to call this before a copy of the
500            // accountingLine
501            rulePassed &= checkSalesTax((AccountingDocument) financialDocumentForm.getDocument(), line, true, true, 0);
502            // check any business rules
503            rulePassed &= SpringContext.getBean(KualiRuleService.class).applyRules(new AddAccountingLineEvent(KFSConstants.NEW_SOURCE_ACCT_LINE_PROPERTY_NAME, financialDocumentForm.getDocument(), line));
504    
505            if (rulePassed) {
506                // add accountingLine
507                SpringContext.getBean(PersistenceService.class).refreshAllNonUpdatingReferences(line);
508                insertAccountingLine(true, financialDocumentForm, line);
509    
510                // clear the used newTargetLine
511                financialDocumentForm.setNewSourceLine(null);
512            }
513    
514            return mapping.findForward(KFSConstants.MAPPING_BASIC);
515        }
516    
517        /**
518         * Adds the given accountingLine to the appropriate form-related datastructures.
519         * 
520         * @param isSource
521         * @param financialDocumentForm
522         * @param line
523         */
524        protected void insertAccountingLine(boolean isSource, KualiAccountingDocumentFormBase financialDocumentForm, AccountingLine line) {
525            AccountingDocument tdoc = financialDocumentForm.getFinancialDocument();
526            if (isSource) {
527                // add it to the document
528                tdoc.addSourceAccountingLine((SourceAccountingLine) line);
529    
530                // add PK fields to sales tax if needed
531                if (line.isSalesTaxRequired()) {
532                    populateSalesTax(line);
533                }
534    
535                // Update the doc total
536                if (tdoc instanceof AmountTotaling)
537                    ((FinancialSystemDocumentHeader) financialDocumentForm.getDocument().getDocumentHeader()).setFinancialDocumentTotalAmount(((AmountTotaling) tdoc).getTotalDollarAmount());
538            }
539            else {
540                // add it to the document
541                tdoc.addTargetAccountingLine((TargetAccountingLine) line);
542    
543                // add PK fields to sales tax if needed
544                if (line.isSalesTaxRequired()) {
545                    populateSalesTax(line);
546                }
547            }
548        }
549    
550        /**
551         * TODO: remove this method once baseline accounting lines has been removed
552         */
553        protected List deepCopyAccountingLinesList(List originals) {
554            if (originals == null) {
555                return null;
556            }
557            List copiedLines = new ArrayList();
558            for (int i = 0; i < originals.size(); i++) {
559                copiedLines.add(ObjectUtils.deepCopy((AccountingLine) originals.get(i)));
560            }
561            return copiedLines;
562        }
563    
564        /**
565         * This action changes the value of the hide field in the user interface so that when the page is rendered, the UI knows to show
566         * all of the labels for each of the accounting line values.
567         * 
568         * @param mapping
569         * @param form
570         * @param request
571         * @param response
572         * @return ActionForward
573         * @throws Exception
574         */
575        public ActionForward showDetails(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
576            KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
577            tmpForm.setHideDetails(false);
578            return mapping.findForward(KFSConstants.MAPPING_BASIC);
579        }
580    
581        /**
582         * This method is triggered when the user toggles the show/hide button to "hide" thus making the UI render without any of the
583         * accounting line labels/descriptions showing up underneath the values in the UI.
584         * 
585         * @param mapping
586         * @param form
587         * @param request
588         * @param response
589         * @return ActionForward
590         * @throws Exception
591         */
592        public ActionForward hideDetails(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
593            KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
594            tmpForm.setHideDetails(true);
595            return mapping.findForward(KFSConstants.MAPPING_BASIC);
596        }
597    
598        /**
599         * Takes care of storing the action form in the User session and forwarding to the balance inquiry report menu action for a
600         * source accounting line.
601         * 
602         * @param mapping
603         * @param form
604         * @param request
605         * @param response
606         * @return ActionForward
607         * @throws Exception
608         */
609        public ActionForward performBalanceInquiryForSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
610            SourceAccountingLine line = this.getSourceAccountingLine(form, request);
611            return performBalanceInquiryForAccountingLine(mapping, form, request, line);
612        }
613    
614        /**
615         * Takes care of storing the action form in the User session and forwarding to the balance inquiry report menu action for a
616         * target accounting line.
617         * 
618         * @param mapping
619         * @param form
620         * @param request
621         * @param response
622         * @return ActionForward
623         * @throws Exception
624         */
625        public ActionForward performBalanceInquiryForTargetLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
626            int lineIndex = getSelectedLine(request);
627    
628            TargetAccountingLine line = this.getTargetAccountingLine(form, request);
629    
630            return performBalanceInquiryForAccountingLine(mapping, form, request, line);
631        }
632    
633        /**
634         * This method is a helper method that will return a source accounting line. The reason we're making it protected in here is so
635         * that we can override this method in some of the modules. PurchasingActionBase is one of the subclasses that will be
636         * overriding this, because in PurchasingActionBase, we'll need to get the source accounting line using both an item index and
637         * an account index.
638         * 
639         * @param form
640         * @param request
641         * @param isSource
642         * @return
643         */
644        protected SourceAccountingLine getSourceAccountingLine(ActionForm form, HttpServletRequest request) {
645            int lineIndex = getSelectedLine(request);
646            SourceAccountingLine line = (SourceAccountingLine) ObjectUtils.deepCopy(((KualiAccountingDocumentFormBase) form).getFinancialDocument().getSourceAccountingLine(lineIndex));
647            return line;
648        }
649    
650        protected TargetAccountingLine getTargetAccountingLine(ActionForm form, HttpServletRequest request) {
651            int lineIndex = getSelectedLine(request);
652            TargetAccountingLine line = (TargetAccountingLine) ((KualiAccountingDocumentFormBase) form).getFinancialDocument().getTargetAccountingLine(lineIndex);
653    
654            return line;
655        }
656    
657        /**
658         * This method handles preparing all of the accounting line data so that it can be pushed up to the balance inquiries for
659         * populating the search criteria of each.
660         * 
661         * @param mapping
662         * @param form
663         * @param request
664         * @param line
665         * @return ActionForward
666         */
667        protected ActionForward performBalanceInquiryForAccountingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, AccountingLine line) {
668            // build out base path for return location
669            String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
670    
671            // build out the actual form key that will be used to retrieve the form on refresh
672            String callerDocFormKey = GlobalVariables.getUserSession().addObject(form);
673    
674            // now add required parameters
675            Properties parameters = new Properties();
676            parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, KFSConstants.START_METHOD);
677            // need this next param b/c the lookup's return back will overwrite
678            // the original doc form key
679            parameters.put(KFSConstants.BALANCE_INQUIRY_REPORT_MENU_CALLER_DOC_FORM_KEY, callerDocFormKey);
680            parameters.put(KFSConstants.DOC_FORM_KEY, callerDocFormKey);
681            parameters.put(KFSConstants.BACK_LOCATION, basePath + mapping.getPath() + ".do");
682    
683            if (line.getPostingYear() != null) {
684                parameters.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, line.getPostingYear().toString());
685            }
686            if (StringUtils.isNotBlank(line.getReferenceOriginCode())) {
687                parameters.put("referenceOriginCode", line.getReferenceOriginCode());
688            }
689            if (StringUtils.isNotBlank(line.getReferenceNumber())) {
690                parameters.put("referenceNumber", line.getReferenceNumber());
691            }
692            if (StringUtils.isNotBlank(line.getReferenceTypeCode())) {
693                parameters.put("referenceTypeCode", line.getReferenceTypeCode());
694            }
695            if (StringUtils.isNotBlank(line.getDebitCreditCode())) {
696                parameters.put("debitCreditCode", line.getDebitCreditCode());
697            }
698            if (StringUtils.isNotBlank(line.getChartOfAccountsCode())) {
699                parameters.put("chartOfAccountsCode", line.getChartOfAccountsCode());
700            }
701            if (StringUtils.isNotBlank(line.getAccountNumber())) {
702                parameters.put("accountNumber", line.getAccountNumber());
703            }
704            if (StringUtils.isNotBlank(line.getFinancialObjectCode())) {
705                parameters.put("financialObjectCode", line.getFinancialObjectCode());
706            }
707            if (StringUtils.isNotBlank(line.getSubAccountNumber())) {
708                parameters.put("subAccountNumber", line.getSubAccountNumber());
709            }
710            if (StringUtils.isNotBlank(line.getFinancialSubObjectCode())) {
711                parameters.put("financialSubObjectCode", line.getFinancialSubObjectCode());
712            }
713            if (StringUtils.isNotBlank(line.getProjectCode())) {
714                parameters.put("projectCode", line.getProjectCode());
715            }
716            if (StringUtils.isNotBlank(getObjectTypeCodeFromLine(line))) {
717                if (!StringUtils.isBlank(line.getObjectTypeCode())) {
718                    parameters.put("objectTypeCode", line.getObjectTypeCode());
719                }
720                else {
721                    line.refreshReferenceObject("objectCode");
722                    parameters.put("objectTypeCode", line.getObjectCode().getFinancialObjectTypeCode());
723                }
724            }
725    
726            String lookupUrl = UrlFactory.parameterizeUrl(basePath + "/" + KFSConstants.BALANCE_INQUIRY_REPORT_MENU_ACTION, parameters);
727    
728            // register that we're going to come back w/ to this form w/ a refresh methodToCall
729            ((KualiAccountingDocumentFormBase) form).registerEditableProperty(KNSConstants.DISPATCH_REQUEST_PARAMETER);
730    
731            return new ActionForward(lookupUrl, true);
732        }
733    
734        /**
735         * A hook so that most accounting lines - which don't have object types - can have their object type codes used in balance
736         * inquiries
737         * 
738         * @param line the line to get the object type code from
739         * @return the object type code the line would use
740         */
741        protected String getObjectTypeCodeFromLine(AccountingLine line) {
742            line.refreshReferenceObject("objectCode");
743            return line.getObjectCode().getFinancialObjectTypeCode();
744        }
745    
746        @Override
747        public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
748            KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
749            this.applyCapitalAssetInformation(tmpForm);
750    
751            ActionForward forward = super.save(mapping, form, request, response);
752    
753            // need to check on sales tax for all the accounting lines
754            checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getSourceAccountingLines());
755            checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getTargetAccountingLines());
756            return forward;
757        }
758    
759        @Override
760        public ActionForward approve(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
761            KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
762            this.applyCapitalAssetInformation(tmpForm);
763    
764            ActionForward forward = super.approve(mapping, form, request, response);
765    
766            // need to check on sales tax for all the accounting lines
767            checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getSourceAccountingLines());
768            checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getTargetAccountingLines());
769            return forward;
770        }
771    
772        @Override
773        public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
774            KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
775            this.applyCapitalAssetInformation(tmpForm);
776    
777            ActionForward forward = super.route(mapping, form, request, response);
778    
779            checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getSourceAccountingLines());
780            checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getTargetAccountingLines());
781    
782            return forward;
783        }
784    
785        @Override
786        public ActionForward blanketApprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
787            KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
788            this.applyCapitalAssetInformation(tmpForm);
789    
790            ActionForward forward = super.blanketApprove(mapping, form, request, response);
791    
792            return forward;
793        }
794    
795        /**
796         * Encapsulate the rule check so we can call it from multiple places
797         * 
798         * @param document
799         * @param line
800         * @return true if sales is either not required or it contains sales tax
801         */
802        protected boolean checkSalesTax(AccountingDocument document, AccountingLine line, boolean source, boolean newLine, int index) {
803            boolean passed = true;
804            if (isSalesTaxRequired(document, line)) {
805                // then set the salesTaxRequired on the accountingLine
806                line.setSalesTaxRequired(true);
807                populateSalesTax(line);
808                // check to see if the sales tax info has been put in
809                passed &= isValidSalesTaxEntered(line, source, newLine, index);
810            }
811            return passed;
812        }
813    
814        /**
815         * This method checks to see if this doctype needs sales tax If it does then it checks to see if the account and object code
816         * require sales tax If it does then it returns true. Note - this is hackish as we shouldn't have to call rules directly from
817         * the action class But we need to in this instance because we are copying the lines before calling rules and need a way to
818         * modify them before they go on
819         * 
820         * @param accountingLine
821         * @return true if sales tax check is needed, false otherwise
822         */
823        protected boolean isSalesTaxRequired(AccountingDocument financialDocument, AccountingLine accountingLine) {
824            boolean required = false;
825            String docType = SpringContext.getBean(DataDictionaryService.class).getDocumentTypeNameByClass(financialDocument.getClass());
826            // first we need to check just the doctype to see if it needs the sales tax check
827            ParameterService parameterService = SpringContext.getBean(ParameterService.class);
828            // apply the rule, see if it fails
829            ParameterEvaluator docTypeSalesTaxCheckEvaluator = SpringContext.getBean(ParameterService.class).getParameterEvaluator(KfsParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class, APPLICATION_PARAMETER.DOCTYPE_SALES_TAX_CHECK, docType);
830            if (docTypeSalesTaxCheckEvaluator.evaluationSucceeds()) {
831                required = true;
832            }
833    
834            // second we need to check the account and object code combination to see if it needs sales tax
835            if (required) {
836                // get the object code and account
837                String objCd = accountingLine.getFinancialObjectCode();
838                String account = accountingLine.getAccountNumber();
839                if (!StringUtils.isEmpty(objCd) && !StringUtils.isEmpty(account)) {
840                    String compare = account + ":" + objCd;
841                    ParameterEvaluator salesTaxApplicableAcctAndObjectEvaluator = SpringContext.getBean(ParameterService.class).getParameterEvaluator(KfsParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class, APPLICATION_PARAMETER.SALES_TAX_APPLICABLE_ACCOUNTS_AND_OBJECT_CODES, compare);
842                    if (!salesTaxApplicableAcctAndObjectEvaluator.evaluationSucceeds()) {
843                        required = false;
844                    }
845                }
846                else {
847                    // the two fields are currently empty and we don't need to check yet
848                    required = false;
849                }
850            }
851            return required;
852        }
853    
854        /**
855         * This method checks to see if the sales tax information was put into the accounting line
856         * 
857         * @param accountingLine
858         * @return true if entered correctly, false otherwise
859         */
860        protected boolean isValidSalesTaxEntered(AccountingLine accountingLine, boolean source, boolean newLine, int index) {
861            boolean valid = true;
862            DictionaryValidationService dictionaryValidationService = SpringContext.getBean(DictionaryValidationService.class);
863            BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);
864            String objCd = accountingLine.getFinancialObjectCode();
865            String account = accountingLine.getAccountNumber();
866            SalesTax salesTax = accountingLine.getSalesTax();
867            String pathPrefix = "";
868            if (source && !newLine) {
869                pathPrefix = "document." + KFSConstants.EXISTING_SOURCE_ACCT_LINE_PROPERTY_NAME + "[" + index + "]";
870            }
871            else if (!source && !newLine) {
872                pathPrefix = "document." + KFSConstants.EXISTING_TARGET_ACCT_LINE_PROPERTY_NAME + "[" + index + "]";
873            }
874            else if (source && newLine) {
875                pathPrefix = KFSConstants.NEW_SOURCE_ACCT_LINE_PROPERTY_NAME;
876            }
877            else if (!source && newLine) {
878                pathPrefix = KFSConstants.NEW_TARGET_ACCT_LINE_PROPERTY_NAME;
879            }
880            GlobalVariables.getMessageMap().addToErrorPath(pathPrefix);
881            if (ObjectUtils.isNull(salesTax)) {
882                valid &= false;
883                GlobalVariables.getMessageMap().putError("salesTax.chartOfAccountsCode", ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_REQUIRED, account, objCd);
884            }
885            else {
886    
887                if (StringUtils.isBlank(salesTax.getChartOfAccountsCode())) {
888                    valid &= false;
889                    GlobalVariables.getMessageMap().putError("salesTax.chartOfAccountsCode", ERROR_REQUIRED, "Chart of Accounts");
890                }
891                if (StringUtils.isBlank(salesTax.getAccountNumber())) {
892                    valid &= false;
893                    GlobalVariables.getMessageMap().putError("salesTax.accountNumber", ERROR_REQUIRED, "Account Number");
894                }
895                if (salesTax.getFinancialDocumentGrossSalesAmount() == null) {
896                    valid &= false;
897                    GlobalVariables.getMessageMap().putError("salesTax.financialDocumentGrossSalesAmount", ERROR_REQUIRED, "Gross Sales Amount");
898                }
899                if (salesTax.getFinancialDocumentTaxableSalesAmount() == null) {
900                    valid &= false;
901                    GlobalVariables.getMessageMap().putError("salesTax.financialDocumentTaxableSalesAmount", ERROR_REQUIRED, "Taxable Sales Amount");
902                }
903                if (salesTax.getFinancialDocumentSaleDate() == null) {
904                    valid &= false;
905                    GlobalVariables.getMessageMap().putError("salesTax.financialDocumentSaleDate", ERROR_REQUIRED, "Sale Date");
906                }
907                if (StringUtils.isNotBlank(salesTax.getChartOfAccountsCode()) && StringUtils.isNotBlank(salesTax.getAccountNumber())) {
908    
909                    if (boService.getReferenceIfExists(salesTax, "account") == null) {
910                        valid &= false;
911                        GlobalVariables.getMessageMap().putError("salesTax.accountNumber", ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_INVALID_ACCOUNT, salesTax.getChartOfAccountsCode(), salesTax.getAccountNumber());
912    
913                    }
914                }
915                if (!valid) {
916                    GlobalVariables.getMessageMap().putError("salesTax.chartOfAccountsCode", ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_REQUIRED, account, objCd);
917                }
918            }
919            GlobalVariables.getMessageMap().removeFromErrorPath(pathPrefix);
920            return valid;
921        }
922    
923        /**
924         * This method removes the sales tax information from a line that no longer requires it
925         * 
926         * @param accountingLine
927         */
928        protected void removeSalesTax(AccountingLine accountingLine) {
929            SalesTax salesTax = accountingLine.getSalesTax();
930            if (ObjectUtils.isNotNull(salesTax)) {
931                accountingLine.setSalesTax(null);
932            }
933        }
934    
935    
936        /**
937         * This method checks to see if the given accounting needs sales tax and if it does it sets the salesTaxRequired variable on the
938         * line If it doesn't and it has it then it removes the sales tax information from the line This method is called from the
939         * execute() on all accounting lines that have been edited or lines that have already been added to the document, not on new
940         * lines
941         * 
942         * @param transDoc
943         * @param formLine
944         * @param baseLine
945         */
946        protected void handleSalesTaxRequired(AccountingDocument transDoc, AccountingLine formLine, boolean source, boolean newLine, int index) {
947            boolean salesTaxRequired = isSalesTaxRequired(transDoc, formLine);
948            if (salesTaxRequired) {
949                formLine.setSalesTaxRequired(true);
950                populateSalesTax(formLine);
951            }
952            else if (!salesTaxRequired && hasSalesTaxBeenEntered(formLine, source, newLine, index)) {
953                // remove it if it has been added but is no longer required
954                removeSalesTax(formLine);
955            }
956        }
957    
958        protected boolean hasSalesTaxBeenEntered(AccountingLine accountingLine, boolean source, boolean newLine, int index) {
959            boolean entered = true;
960            String objCd = accountingLine.getFinancialObjectCode();
961            String account = accountingLine.getAccountNumber();
962            SalesTax salesTax = accountingLine.getSalesTax();
963            if (ObjectUtils.isNull(salesTax)) {
964                return false;
965            }
966            if (StringUtils.isBlank(salesTax.getChartOfAccountsCode())) {
967                entered &= false;
968            }
969            if (StringUtils.isBlank(salesTax.getAccountNumber())) {
970                entered &= false;
971            }
972            if (salesTax.getFinancialDocumentGrossSalesAmount() == null) {
973                entered &= false;
974            }
975            if (salesTax.getFinancialDocumentTaxableSalesAmount() == null) {
976                entered &= false;
977            }
978            if (salesTax.getFinancialDocumentSaleDate() == null) {
979                entered &= false;
980            }
981            return entered;
982        }
983    
984        /**
985         * This method is called from the createDocument and processes through all the accouting lines and checks to see if they need
986         * sales tax fields
987         * 
988         * @param kualiDocumentFormBase
989         * @param baselineSourceLines
990         */
991        protected void handleSalesTaxRequiredAllLines(KualiDocumentFormBase kualiDocumentFormBase, List<AccountingLine> baselineAcctingLines) {
992            AccountingDocument accoutingDocument = (AccountingDocument) kualiDocumentFormBase.getDocument();
993            int index = 0;
994            for (AccountingLine accountingLine : baselineAcctingLines) {
995                boolean source = false;
996                if (accountingLine.isSourceAccountingLine()) {
997                    source = true;
998                }
999                handleSalesTaxRequired(accoutingDocument, accountingLine, source, false, index);
1000                index++;
1001            }
1002    
1003        }
1004    
1005        protected boolean checkSalesTaxRequiredAllLines(KualiDocumentFormBase kualiDocumentFormBase, List<AccountingLine> baselineAcctingLines) {
1006            AccountingDocument accoutingDocument = (AccountingDocument) kualiDocumentFormBase.getDocument();
1007            boolean passed = true;
1008            int index = 0;
1009            for (AccountingLine accountingLine : baselineAcctingLines) {
1010                boolean source = false;
1011                if (accountingLine.isSourceAccountingLine()) {
1012                    source = true;
1013                }
1014                passed &= checkSalesTax(accoutingDocument, accountingLine, source, false, index);
1015                index++;
1016            }
1017            return passed;
1018        }
1019    
1020        /**
1021         * This method refreshes the sales tax fields on a refresh or tab toggle so that all the information that was there before is
1022         * still there after a state change
1023         * 
1024         * @param form
1025         */
1026        protected void refreshSalesTaxInfo(ActionForm form) {
1027            KualiAccountingDocumentFormBase accountingForm = (KualiAccountingDocumentFormBase) form;
1028            AccountingDocument document = (AccountingDocument) accountingForm.getDocument();
1029            List sourceLines = document.getSourceAccountingLines();
1030            List targetLines = document.getTargetAccountingLines();
1031            handleSalesTaxRequiredAllLines(accountingForm, sourceLines);
1032            handleSalesTaxRequiredAllLines(accountingForm, targetLines);
1033    
1034            AccountingLine newTargetLine = accountingForm.getNewTargetLine();
1035            AccountingLine newSourceLine = accountingForm.getNewSourceLine();
1036            if (newTargetLine != null) {
1037                handleSalesTaxRequired(document, newTargetLine, false, true, 0);
1038            }
1039            if (newSourceLine != null) {
1040                handleSalesTaxRequired(document, newSourceLine, true, true, 0);
1041            }
1042        }
1043    
1044        /**
1045         * This method populates the sales tax for a given accounting line with the appropriate primary key fields from the accounting
1046         * line since OJB won't do it automatically for us
1047         * 
1048         * @param line
1049         */
1050        protected void populateSalesTax(AccountingLine line) {
1051            SalesTax salesTax = line.getSalesTax();
1052    
1053            if (ObjectUtils.isNotNull(salesTax)) {
1054                salesTax.setDocumentNumber(line.getDocumentNumber());
1055                salesTax.setFinancialDocumentLineTypeCode(line.getFinancialDocumentLineTypeCode());
1056                salesTax.setFinancialDocumentLineNumber(line.getSequenceNumber());
1057            }
1058        }
1059    
1060        @Override
1061        public ActionForward performLookup(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1062            // parse out the business object name from our methodToCall parameter
1063            String fullParameter = (String) request.getAttribute(KFSConstants.METHOD_TO_CALL_ATTRIBUTE);
1064            String boClassName = StringUtils.substringBetween(fullParameter, KFSConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, KFSConstants.METHOD_TO_CALL_BOPARM_RIGHT_DEL);
1065    
1066            if (!StringUtils.equals(boClassName, GeneralLedgerPendingEntry.class.getName())) {
1067                return super.performLookup(mapping, form, request, response);
1068            }
1069    
1070            String path = super.performLookup(mapping, form, request, response).getPath();
1071            path = path.replaceFirst(KFSConstants.LOOKUP_ACTION, KFSConstants.GL_MODIFIED_INQUIRY_ACTION);
1072    
1073            return new ActionForward(path, true);
1074        }
1075    
1076        /**
1077         * clear up the capital asset information
1078         */
1079        public ActionForward clearCapitalAssetInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1080            LOG.debug("clearCapitalAssetInfo() - start");
1081    
1082            KualiAccountingDocumentFormBase kualiAccountingDocumentFormBase = (KualiAccountingDocumentFormBase) form;
1083            CapitalAssetInformation capitalAssetInformation = this.getCurrentCapitalAssetInformationObject(kualiAccountingDocumentFormBase);
1084            if (capitalAssetInformation == null) {
1085                return mapping.findForward(KFSConstants.MAPPING_BASIC);
1086            }
1087    
1088            this.resetCapitalAssetInfo(capitalAssetInformation);
1089    
1090            return mapping.findForward(KFSConstants.MAPPING_BASIC);
1091        }
1092    
1093        /**
1094         * add the capital asset information
1095         */
1096        public ActionForward addCapitalAssetInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1097            LOG.debug("addCapitalAssetInfoDetail() - start");
1098    
1099            KualiAccountingDocumentFormBase kualiAccountingDocumentFormBase = (KualiAccountingDocumentFormBase) form;
1100            CapitalAssetInformation capitalAssetInformation = this.getCurrentCapitalAssetInformationObject(kualiAccountingDocumentFormBase);
1101            if (capitalAssetInformation == null) {
1102                return mapping.findForward(KFSConstants.MAPPING_BASIC);
1103            }
1104    
1105            this.addCapitalAssetInfoDetailLines(capitalAssetInformation);
1106    
1107            return mapping.findForward(KFSConstants.MAPPING_BASIC);
1108        }
1109    
1110        /**
1111         * delete a detail line from the capital asset information
1112         */
1113        public ActionForward deleteCapitalAssetInfoDetailLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1114            LOG.debug("deleteCapitalAssetDetailInfo() - start");
1115    
1116            KualiAccountingDocumentFormBase kualiAccountingDocumentFormBase = (KualiAccountingDocumentFormBase) form;
1117            CapitalAssetInformation capitalAssetInformation = this.getCurrentCapitalAssetInformationObject(kualiAccountingDocumentFormBase);
1118            if (capitalAssetInformation == null) {
1119                return mapping.findForward(KFSConstants.MAPPING_BASIC);
1120            }
1121    
1122            int lineToDelete = this.getLineToDelete(request);
1123            List<CapitalAssetInformationDetail> detailLines = capitalAssetInformation.getCapitalAssetInformationDetails();
1124    
1125            detailLines.remove(lineToDelete);
1126    
1127            return mapping.findForward(KFSConstants.MAPPING_BASIC);
1128        }
1129    
1130        /**
1131         * get the capital asset information object currently associated with the document
1132         */
1133        protected CapitalAssetInformation getCurrentCapitalAssetInformationObject(KualiAccountingDocumentFormBase kualiAccountingDocumentFormBase) {
1134            LOG.debug("getCurrentCapitalAssetInformationObject() - start");
1135    
1136            AccountingDocument financialDocument = kualiAccountingDocumentFormBase.getFinancialDocument();
1137            if (!(financialDocument instanceof CapitalAssetEditable) || !(kualiAccountingDocumentFormBase instanceof CapitalAssetEditable)) {
1138                return null;
1139            }
1140    
1141            CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) financialDocument;
1142            CapitalAssetInformation capitalAssetInformation = capitalAssetEditable.getCapitalAssetInformation();
1143            if (ObjectUtils.isNotNull(capitalAssetInformation)) {
1144                return capitalAssetInformation;
1145            }
1146    
1147            CapitalAssetEditable capitalAssetEditableForm = (CapitalAssetEditable) kualiAccountingDocumentFormBase;
1148            CapitalAssetInformation newCapitalAssetInformation = capitalAssetEditableForm.getCapitalAssetInformation();
1149    
1150            return newCapitalAssetInformation;
1151        }
1152    
1153        /**
1154         * add detail lines into the given capital asset information
1155         * 
1156         * @param capitalAssetInformation the given capital asset information
1157         */
1158        protected void addCapitalAssetInfoDetailLines(CapitalAssetInformation capitalAssetInformation) {
1159            LOG.debug("addCapitalAssetInfoDetailLines() - start");
1160    
1161            if (ObjectUtils.isNull(capitalAssetInformation)) {
1162                return;
1163            }
1164    
1165            Integer quantity = capitalAssetInformation.getCapitalAssetQuantity();
1166            if (quantity == null || quantity <= 0) {
1167                String errorPath = KFSPropertyConstants.DOCUMENT + "." + KFSPropertyConstants.CAPITAL_ASSET_INFORMATION;
1168                GlobalVariables.getMessageMap().putError(errorPath, KFSKeyConstants.ERROR_INVALID_CAPITAL_ASSET_QUANTITY);
1169                return;
1170            }
1171    
1172            List<CapitalAssetInformationDetail> detailLines = capitalAssetInformation.getCapitalAssetInformationDetails();
1173            // If details collection has old lines, this loop will add new lines to make the total equal to the quantity.
1174            for (int index = 1; detailLines.size() < quantity; index++) {
1175                CapitalAssetInformationDetail detailLine = new CapitalAssetInformationDetail();
1176                detailLine.setItemLineNumber(getNextItemLineNumberAndIncremented(capitalAssetInformation));
1177                detailLines.add(detailLine);
1178            }
1179        }
1180    
1181        /**
1182         * Get next available item line number. If it's already stored in the session, pick it up and increment by 1. Otherwise get it
1183         * from the DB and save it to session.
1184         * 
1185         * @param capitalAssetInformation
1186         * @return
1187         */
1188        protected Integer getNextItemLineNumberAndIncremented(CapitalAssetInformation capitalAssetInformation) {
1189            Integer nextItemLineNumber = capitalAssetInformation.getNextItemLineNumber();
1190            if (nextItemLineNumber == null) {
1191                nextItemLineNumber = new Integer(getMaxItemLineNumber(capitalAssetInformation) + 1);
1192            }
1193            capitalAssetInformation.setNextItemLineNumber(new Integer(nextItemLineNumber.intValue() + 1));
1194            return nextItemLineNumber;
1195        }
1196    
1197        /**
1198         * Get the maximum item line number from DB.
1199         * 
1200         * @param capitalAssetInformation
1201         * @return
1202         */
1203        protected int getMaxItemLineNumber(CapitalAssetInformation capitalAssetInformation) {
1204            int maxItemLineNumber = 0;
1205            if (ObjectUtils.isNotNull(capitalAssetInformation)) {
1206                List<CapitalAssetInformationDetail> detailLines = capitalAssetInformation.getCapitalAssetInformationDetails();
1207    
1208                if (detailLines != null && !detailLines.isEmpty()) {
1209                    maxItemLineNumber = detailLines.size();
1210                }
1211    
1212                Map<String, Object> fieldValues = new HashMap<String, Object>();
1213                fieldValues.put(KFSPropertyConstants.DOCUMENT_NUMBER, capitalAssetInformation.getDocumentNumber());
1214                List<CapitalAssetInformationDetail> perisitentDetails = (List<CapitalAssetInformationDetail>) getBusinessObjectService().findMatching(CapitalAssetInformationDetail.class, fieldValues);
1215                for (CapitalAssetInformationDetail persistentDetail : perisitentDetails) {
1216                    if (persistentDetail.getItemLineNumber().intValue() > maxItemLineNumber) {
1217                        maxItemLineNumber = persistentDetail.getItemLineNumber().intValue();
1218                    }
1219                }
1220            }
1221            return maxItemLineNumber;
1222        }
1223    
1224        /**
1225         * reset the nonkey fields of the given capital asset information
1226         * 
1227         * @param capitalAssetInformation the given capital asset information
1228         */
1229        protected void resetCapitalAssetInfo(CapitalAssetInformation capitalAssetInformation) {
1230            if (capitalAssetInformation != null) {
1231                capitalAssetInformation.setCapitalAssetDescription(null);
1232                capitalAssetInformation.setCapitalAssetManufacturerModelNumber(null);
1233                capitalAssetInformation.setCapitalAssetManufacturerName(null);
1234    
1235                capitalAssetInformation.setCapitalAssetNumber(null);
1236                capitalAssetInformation.setCapitalAssetTypeCode(null);
1237                capitalAssetInformation.setCapitalAssetQuantity(null);
1238    
1239                capitalAssetInformation.setVendorDetailAssignedIdentifier(null);
1240                capitalAssetInformation.setVendorHeaderGeneratedIdentifier(null);
1241                // Set the BO to null cause it won't be updated automatically when vendorDetailAssetIdentifier and
1242                // VendorHeanderGeneratedIndentifier set to null.
1243                capitalAssetInformation.setVendorDetail(null);
1244                capitalAssetInformation.setVendorName(null);
1245    
1246                capitalAssetInformation.getCapitalAssetInformationDetails().clear();
1247            }
1248        }
1249    
1250        // assoicate the new capital asset information with the current document if any
1251        protected void applyCapitalAssetInformation(KualiAccountingDocumentFormBase kualiAccountingDocumentFormBase) {
1252            LOG.debug("applyCapitalAssetInformation() - start");
1253    
1254            // do nothing if the given document is not required to have capital asset collection
1255            AccountingDocument document = kualiAccountingDocumentFormBase.getFinancialDocument();
1256            if (!(document instanceof CapitalAssetEditable)) {
1257                return;
1258            }
1259    
1260            // do nothing if there exists capital asset information associated with the current document
1261            CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) document;
1262            CapitalAssetInformation capitalAssetInformation = capitalAssetEditable.getCapitalAssetInformation();
1263            if (capitalAssetInformation != null || !(kualiAccountingDocumentFormBase instanceof CapitalAssetEditable)) {
1264                return;
1265            }
1266    
1267            CapitalAssetEditable capitalAssetEditableForm = (CapitalAssetEditable) kualiAccountingDocumentFormBase;
1268            CapitalAssetInformation newCapitalAssetInformation = capitalAssetEditableForm.getCapitalAssetInformation();
1269            // apply capitalAsset information if there is at least one movable object code associated with the source accounting
1270            // lines
1271            newCapitalAssetInformation.setDocumentNumber(document.getDocumentNumber());
1272            capitalAssetEditable.setCapitalAssetInformation(newCapitalAssetInformation);
1273        }
1274    
1275        /**
1276         * Overridden to guarantee that form of copied document is set to whatever the entry mode of the document is
1277         * @see org.kuali.rice.kns.web.struts.action.KualiTransactionalDocumentActionBase#copy(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
1278         */
1279        @Override
1280        public ActionForward copy(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
1281            ActionForward forward = super.copy(mapping, form, request, response);
1282            
1283            // if the copied document has capital asset collection, remove the collection
1284            KualiAccountingDocumentFormBase kualiAccountingDocumentFormBase = (KualiAccountingDocumentFormBase) form;
1285            AccountingDocument document = kualiAccountingDocumentFormBase.getFinancialDocument();
1286            if (document instanceof CapitalAssetEditable) {
1287    
1288                CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) document;
1289                resetCapitalAssetInfo(capitalAssetEditable.getCapitalAssetInformation());
1290            }
1291    
1292            return forward;
1293        }
1294    
1295    }