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.io.FileNotFoundException;
019    import java.io.IOException;
020    import java.util.Enumeration;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Map.Entry;
026    
027    import javax.servlet.http.HttpServletRequest;
028    import javax.servlet.http.HttpServletResponse;
029    
030    import org.apache.commons.lang.StringUtils;
031    import org.apache.struts.action.ActionForm;
032    import org.apache.struts.action.ActionForward;
033    import org.apache.struts.action.ActionMapping;
034    import org.apache.struts.upload.FormFile;
035    import org.kuali.kfs.coa.service.AccountService;
036    import org.kuali.kfs.module.purap.PurapConstants;
037    import org.kuali.kfs.module.purap.PurapPropertyConstants;
038    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
039    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLineParser;
040    import org.kuali.kfs.module.purap.businessobject.PurApItem;
041    import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
042    import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase;
043    import org.kuali.kfs.module.purap.document.service.PurapService;
044    import org.kuali.kfs.module.purap.service.PurapAccountingService;
045    import org.kuali.kfs.sys.KFSConstants;
046    import org.kuali.kfs.sys.KFSPropertyConstants;
047    import org.kuali.kfs.sys.businessobject.AccountingLine;
048    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
049    import org.kuali.kfs.sys.context.SpringContext;
050    import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent;
051    import org.kuali.kfs.sys.exception.AccountingLineParserException;
052    import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase;
053    import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase;
054    import org.kuali.rice.core.util.RiceConstants;
055    import org.kuali.rice.kew.exception.WorkflowException;
056    import org.kuali.rice.kns.service.DocumentService;
057    import org.kuali.rice.kns.service.KualiRuleService;
058    import org.kuali.rice.kns.service.PersistenceService;
059    import org.kuali.rice.kns.util.GlobalVariables;
060    import org.kuali.rice.kns.util.ObjectUtils;
061    import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
062    import org.kuali.rice.kns.web.struts.form.KualiForm;
063    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
064    
065    /**
066     * Struts Action for Purchasing and Accounts Payable documents
067     */
068    public class PurchasingAccountsPayableActionBase extends KualiAccountingDocumentActionBase {
069    
070        /**
071         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
072         */
073        @Override
074        protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
075            super.loadDocument(kualiDocumentFormBase);
076            PurchasingAccountsPayableFormBase purapForm = (PurchasingAccountsPayableFormBase) kualiDocumentFormBase;
077            PurchasingAccountsPayableDocument document = (PurchasingAccountsPayableDocument) purapForm.getDocument();
078    
079            // refresh the account summary (note this also updates the account amounts)
080            purapForm.refreshAccountSummmary();
081    
082            for (org.kuali.rice.kns.bo.Note note : (java.util.List<org.kuali.rice.kns.bo.Note>) document.getDocumentBusinessObject().getBoNotes()) {
083                note.refreshReferenceObject("attachment");
084            }
085    
086            // sort the below the line
087            SpringContext.getBean(PurapService.class).sortBelowTheLine(document);
088    
089            updateBaseline(document, (PurchasingAccountsPayableFormBase) kualiDocumentFormBase);
090        }
091        
092        /**
093         * Updates the baseline accounts on form and doc.
094         * 
095         * @param document A descendant of PurchasingAccountsPayableDocument
096         */
097        protected <T extends PurchasingAccountsPayableDocument, V extends KualiAccountingDocumentFormBase> void updateBaseline(T document, V form) {
098            // clear out the old lines first
099            for (PurApItem item : document.getItems()) {
100                // clear out the old lines first
101                item.getBaselineSourceAccountingLines().clear();
102    
103                for (PurApAccountingLine sourceAccount : item.getSourceAccountingLines()) {
104                    // JHK: KFSMI-287 - removed deep copy since this object will be thrown away after the page renders, we just need a
105                    // different path to have them stored on the form
106                    // ESPECIALLY since PURAP does not allow lines to be reverted (see calls to setRevertible)
107                    item.getBaselineSourceAccountingLines().add(sourceAccount);
108                }
109            }
110        }
111    
112        /**
113         * Invokes a service method to refresh the account summary.
114         * 
115         * @param mapping An ActionMapping
116         * @param form An ActionForm
117         * @param request The HttpServletRequest
118         * @param response The HttpServletResponse
119         * @throws Exception
120         * @return An ActionForward
121         */
122        public ActionForward refreshAccountSummary(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
123            PurchasingAccountsPayableFormBase purapForm = (PurchasingAccountsPayableFormBase) form;
124            PurchasingAccountsPayableDocument document = (PurchasingAccountsPayableDocument) purapForm.getDocument();
125            SpringContext.getBean(PurapAccountingService.class).updateAccountAmounts(document);
126            purapForm.refreshAccountSummmary();
127            return mapping.findForward(KFSConstants.MAPPING_BASIC);
128        }
129        
130        /**
131         * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#uploadAccountingLines(boolean,org.apache.struts.action.ActionForm)
132         */
133        @Override
134        protected void uploadAccountingLines(boolean isSource, ActionForm form) throws FileNotFoundException, IOException {
135            PurchasingAccountsPayableFormBase purapForm = (PurchasingAccountsPayableFormBase) form;
136            PurchasingAccountsPayableDocumentBase purapDocument = (PurchasingAccountsPayableDocumentBase)purapForm.getFinancialDocument();        
137            PurApAccountingLineParser accountingLineParser = (PurApAccountingLineParser)purapDocument.getAccountingLineParser();
138            List importedLines = null;
139            String errorPathPrefix = PurapConstants.ACCOUNT_DISTRIBUTION_ERROR_KEY;
140            //String errorPathPrefix = "accountDistributionnewSourceLine";
141    
142            // import the lines
143            try {
144                 FormFile sourceFile = purapForm.getSourceFile();
145                 checkUploadFile(sourceFile);
146                 GlobalVariables.getMessageMap().clearErrorPath();
147                 GlobalVariables.getMessageMap().addToErrorPath(errorPathPrefix);                
148                 importedLines = accountingLineParser.importSourceAccountingLines(sourceFile.getFileName(), sourceFile.getInputStream(), purapDocument);
149                 GlobalVariables.getMessageMap().removeFromErrorPath(errorPathPrefix);            
150            }
151            catch (AccountingLineParserException e) {
152                GlobalVariables.getMessageMap().putError(errorPathPrefix, e.getErrorKey(), e.getErrorParameters());
153            }
154    
155            // add to list those lines successfully imported
156            if (importedLines != null) {
157                for (Iterator iter = importedLines.iterator(); iter.hasNext();) {
158                    PurApAccountingLine importedLine = (PurApAccountingLine) iter.next();
159                    //boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new AddAccountingLineEvent(errorPathPrefix, purapForm.getDocument(), (AccountingLine) importedLine));
160                    //if (rulePassed) {
161                    // add accountingLine
162                    SpringContext.getBean(PersistenceService.class).retrieveNonKeyFields(importedLine);
163                    ((PurchasingFormBase)purapForm).addAccountDistributionsourceAccountingLine(importedLine);
164                    //}
165                }
166            }
167        }    
168    
169        /**
170         * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#insertSourceLine(org.apache.struts.action.ActionMapping,
171         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
172         */
173        @Override
174        public ActionForward insertSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
175            // It would be preferable to find a way to genericize the KualiAccountingDocument methods but this will work for now
176            PurchasingAccountsPayableFormBase purapForm = (PurchasingAccountsPayableFormBase) form;
177            
178            // index of item selected
179            int itemIndex = getSelectedLine(request);
180            PurApItem item = null;
181            
182            // if custom processing of an accounting line is not done then insert a line generically.
183            if (processCustomInsertAccountingLine(purapForm, request) == false) {
184                String errorPrefix = null;
185                PurApAccountingLine line = null;
186                boolean rulePassed = false;
187                if (itemIndex >= 0) {
188                    item = (PurApItem) ((PurchasingAccountsPayableDocument) purapForm.getDocument()).getItem((itemIndex));
189                    line = (PurApAccountingLine) ObjectUtils.deepCopy(item.getNewSourceLine());
190                    //SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(line);
191                    errorPrefix = KFSPropertyConstants.DOCUMENT + "." + PurapPropertyConstants.ITEM + "[" + Integer.toString(itemIndex) + "]." + KFSConstants.NEW_SOURCE_ACCT_LINE_PROPERTY_NAME;
192                    rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new AddAccountingLineEvent(errorPrefix, purapForm.getDocument(), (AccountingLine) line));
193                }
194                else if (itemIndex == -2){
195                    //corrected: itemIndex == -2 is the only case for distribute account
196                    //This is the case when we're inserting an accounting line for distribute account.
197                    line = ((PurchasingFormBase)purapForm).getAccountDistributionnewSourceLine();
198                    //SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(line);
199                    errorPrefix = PurapPropertyConstants.ACCOUNT_DISTRIBUTION_NEW_SRC_LINE;
200                    rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new AddAccountingLineEvent(errorPrefix, purapForm.getDocument(), (AccountingLine) line));
201                }
202    
203                if (rulePassed) {
204                    // add accountingLine
205                    SpringContext.getBean(PersistenceService.class).retrieveNonKeyFields(line);
206                    if (itemIndex >=0) {
207                        insertAccountingLine(purapForm, item, line);
208                        // clear the temp account
209                        item.resetAccount();
210                    }
211                    else if (itemIndex == -2) {
212                        //this is the case for distribute account
213                        ((PurchasingFormBase)purapForm).addAccountDistributionsourceAccountingLine(line);
214                    }
215                }
216            }
217    
218            return mapping.findForward(KFSConstants.MAPPING_BASIC);
219        }
220    
221        /**
222         * Insert the given Accounting Line in several appropriate places in the given item and given form.
223         * 
224         * @param financialDocumentForm A form that inherits from PurchasingAccountsPaybleFormBase
225         * @param item A PurApItem
226         * @param line A PurApAccountingLine
227         */
228        protected void insertAccountingLine(PurchasingAccountsPayableFormBase financialDocumentForm, PurApItem item, PurApAccountingLine line) {
229            PurchasingAccountsPayableDocument preq = (PurchasingAccountsPayableDocument) financialDocumentForm.getDocument();
230    
231            // add it to the item
232            item.getSourceAccountingLines().add(line);
233        }
234    
235        /**
236         * Allows the custom processing of an accounting line during a call to insert source line. If a custom method for inserting an
237         * accounting line was performed, then a value of true must be returned.
238         * 
239         * @param purapForm
240         * @param request
241         * @return boolean indicating if validation succeeded
242         */
243        public boolean processCustomInsertAccountingLine(PurchasingAccountsPayableFormBase purapForm, HttpServletRequest request) {
244            return false;
245        }
246    
247        /**
248         * Insert the given Accounting Line in several appropriate places in the given item and given form.
249         * 
250         * @param financialDocumentForm A form that inherits from KualiAccountingDocumentFormBase
251         * @param item A PurApItem
252         * @param line A PurApAccountingLine
253         */
254        protected void insertAccountingLine(KualiAccountingDocumentFormBase financialDocumentForm, PurApItem item, PurApAccountingLine line) {
255            // add it to the item
256            item.getSourceAccountingLines().add(line);
257        }
258    
259        /**
260         * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#deleteSourceLine(org.apache.struts.action.ActionMapping,
261         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
262         */
263        @Override
264        public ActionForward deleteSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
265            PurchasingAccountsPayableFormBase purapForm = (PurchasingAccountsPayableFormBase) form;
266    
267            String[] indexes = getSelectedLineForAccounts(request);
268            int itemIndex = Integer.parseInt(indexes[0]);
269            int accountIndex = Integer.parseInt(indexes[1]);
270    
271            PurApItem item = (PurApItem) ((PurchasingAccountsPayableDocument) purapForm.getDocument()).getItem((itemIndex));
272            item.getSourceAccountingLines().remove(accountIndex);
273    
274            // remove the decorator
275            // financialDocumentForm.getSourceLineDecorators().remove(decorator);
276    
277            return mapping.findForward(KFSConstants.MAPPING_BASIC);
278        }
279    
280        /**
281         * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#getSourceAccountingLine(org.apache.struts.action.ActionForm,
282         *      javax.servlet.http.HttpServletRequest)
283         */
284        @Override
285        public SourceAccountingLine getSourceAccountingLine(ActionForm form, HttpServletRequest request) {
286            String[] indexes = getSelectedLineForAccounts(request);
287            int itemIndex = Integer.parseInt(indexes[0]);
288            int accountIndex = Integer.parseInt(indexes[1]);
289            PurchasingAccountsPayableFormBase purchasingAccountsPayableForm = (PurchasingAccountsPayableFormBase) form;
290            SourceAccountingLine line;
291            if (itemIndex == -2) {
292                line = customAccountRetrieval(accountIndex, purchasingAccountsPayableForm);
293            }
294            else {
295                PurApItem item = (PurApItem) ((PurchasingAccountsPayableDocument) purchasingAccountsPayableForm.getDocument()).getItem((itemIndex));
296                line = (SourceAccountingLine) ObjectUtils.deepCopy(item.getSourceAccountingLines().get(accountIndex));
297            }
298            return line;
299        }
300    
301        /**
302         * Perform custom processing on accounting lines. See <code>getSelectedLineForAccounts</code>.
303         * 
304         * @param accountIndex The index of the account into the request parameter
305         * @param purchasingAccountsPayableForm A form which inherits from PurchasingAccountsPayableFormBase
306         * @return A SourceAccountingLine
307         */
308        protected SourceAccountingLine customAccountRetrieval(int accountIndex, PurchasingAccountsPayableFormBase purchasingAccountsPayableForm) {
309            // default impl returns null
310            return null;
311        }
312    
313        /**
314         * Will return an array of Strings containing 2 indexes, the first String is the item index and the second String is the account
315         * index. These are obtained by parsing the method to call parameter from the request, between the word ".line" and "." The
316         * indexes are separated by a semicolon (:)
317         * 
318         * @param request The HttpServletRequest
319         * @return An array of Strings containing pairs of two indices, an item index and a account index
320         */
321        protected String[] getSelectedLineForAccounts(HttpServletRequest request) {
322            String accountString = new String();
323            String parameterName = (String) request.getAttribute(KFSConstants.METHOD_TO_CALL_ATTRIBUTE);
324            if (StringUtils.isNotBlank(parameterName)) {
325                accountString = StringUtils.substringBetween(parameterName, ".line", ".");
326            }
327            String[] result = StringUtils.split(accountString, ":");
328    
329            return result;
330        }
331    
332        /**
333         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#downloadBOAttachment(org.apache.struts.action.ActionMapping,
334         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
335         */
336        @Override
337        public ActionForward downloadBOAttachment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
338            PurchasingAccountsPayableDocument document = (PurchasingAccountsPayableDocument) ((PurchasingAccountsPayableFormBase) form).getDocument();
339    
340            for (org.kuali.rice.kns.bo.Note note : (java.util.List<org.kuali.rice.kns.bo.Note>) document.getDocumentBusinessObject().getBoNotes()) {
341                note.refreshReferenceObject("attachment");
342            }
343    
344            return super.downloadBOAttachment(mapping, form, request, response);
345        }
346    
347        @Override
348        protected void processAccountingLineOverrides(List accountingLines) {
349            //do nothing purap handles these differently
350        }
351        
352        /**
353         * Perform calculation on item line.
354         * 
355         * @param mapping An ActionMapping
356         * @param form An ActionForm
357         * @param request The HttpServletRequest
358         * @param response The HttpServletResponse
359         * @return An ActionForward
360         */
361        public ActionForward calculate(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
362            return mapping.findForward(KFSConstants.MAPPING_BASIC);
363        }
364    
365        public ActionForward clearAllTaxes(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
366            return mapping.findForward(KFSConstants.MAPPING_BASIC);
367        }
368        
369        protected void customCalculate(PurchasingAccountsPayableDocument purapDoc) {
370            // do nothing by default
371        }
372        
373        /**
374         * Toggles all specific tabs to open
375         *
376         * @param mapping
377         * @param form
378         * @param request
379         * @param response
380         * @return
381         * @throws Exception
382         */
383        public ActionForward showAllAccounts(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
384            KualiForm kualiForm = (KualiForm) form;
385            String accountingLineTab = "AccountingLines";
386            String value = null;
387            
388            Map<String, String> tabStates = kualiForm.getTabStates();
389            Map<String, String> newTabStates = new HashMap<String, String>();
390            for (Entry<String, String> tabEntry: tabStates.entrySet()) {
391                if(tabEntry.getKey().startsWith(accountingLineTab)){
392                    newTabStates.put(tabEntry.getKey(), "OPEN");
393                }else{                        
394                    if (tabEntry.getValue() instanceof String) {
395                        value = tabEntry.getValue();
396                    }
397                    else {
398                        //This is the case where the value is an Array of String,
399                        //so we'll have to get the first element
400                        Object result = tabEntry.getValue();
401                        result.getClass();
402                        value = ((String[])result)[0];
403                    }
404                    newTabStates.put(tabEntry.getKey(), value);                
405                }
406            }
407            kualiForm.setTabStates(newTabStates);
408            return mapping.findForward(RiceConstants.MAPPING_BASIC);
409        }
410    
411        /**
412         * Toggles all specific tabs to closed
413         *
414         * @param mapping
415         * @param form
416         * @param request
417         * @param response
418         * @return
419         * @throws Exception
420         */
421        public ActionForward hideAllAccounts(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
422            KualiForm kualiForm = (KualiForm) form;
423            String accountingLineTab = "AccountingLines";
424            String value = null;
425            
426            Map<String, String> tabStates = kualiForm.getTabStates();
427            Map<String, String> newTabStates = new HashMap<String, String>();
428            for (Entry<String, String> tabEntry: tabStates.entrySet()) {
429                if(tabEntry.getKey().startsWith(accountingLineTab)){
430                    newTabStates.put(tabEntry.getKey(), "CLOSE");
431                }else{
432                    if (tabEntry.getValue() instanceof String) {
433                        value = tabEntry.getValue();
434                    }
435                    else {
436                        //This is the case where the value is an Array of String,
437                        //so we'll have to get the first element
438                        Object result = tabEntry.getValue();
439                        result.getClass();
440                        value = ((String[])result)[0];
441                    }
442                    newTabStates.put(tabEntry.getKey(), value);                
443                }
444            }
445            kualiForm.setTabStates(newTabStates);
446            return mapping.findForward(RiceConstants.MAPPING_BASIC);
447        }
448        
449        /**
450         * Override to verify the document has been saved before the note is inserted. This will assure the correct parent object id is
451         * associated with the note.
452         * 
453         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#insertBONote(org.apache.struts.action.ActionMapping,
454         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
455         */
456        @Override
457        public ActionForward insertBONote(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
458            PurchasingAccountsPayableDocument document = (PurchasingAccountsPayableDocument) ((PurchasingAccountsPayableFormBase) form).getDocument();
459            KualiWorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument();
460    
461            if (workflowDocument.stateIsInitiated()) {
462                SpringContext.getBean(DocumentService.class).saveDocument(document);
463            }
464    
465            return super.insertBONote(mapping, form, request, response);
466        }
467    
468    }