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.fp.document.web.struts;
017    
018    import java.io.FileNotFoundException;
019    import java.io.IOException;
020    import java.util.ArrayList;
021    
022    import javax.servlet.http.HttpServletRequest;
023    import javax.servlet.http.HttpServletResponse;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.apache.struts.action.ActionForm;
027    import org.apache.struts.action.ActionForward;
028    import org.apache.struts.action.ActionMapping;
029    import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelper;
030    import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelperBase;
031    import org.kuali.kfs.fp.document.VoucherDocument;
032    import org.kuali.kfs.sys.KFSConstants;
033    import org.kuali.kfs.sys.KFSKeyConstants;
034    import org.kuali.kfs.sys.businessobject.AccountingLine;
035    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
036    import org.kuali.kfs.sys.context.SpringContext;
037    import org.kuali.kfs.sys.document.AmountTotaling;
038    import org.kuali.kfs.sys.service.UniversityDateService;
039    import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase;
040    import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase;
041    import org.kuali.rice.kew.exception.WorkflowException;
042    import org.kuali.rice.kns.question.ConfirmationQuestion;
043    import org.kuali.rice.kns.service.KualiConfigurationService;
044    import org.kuali.rice.kns.util.GlobalVariables;
045    import org.kuali.rice.kns.util.KualiDecimal;
046    import org.kuali.rice.kns.web.format.CurrencyFormatter;
047    import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
048    
049    /**
050     * This class piggy backs on all of the functionality in the FinancialSystemTransactionalDocumentActionBase but is necessary for this document
051     * type. Vouchers are unique in that they define several fields that aren't typically used by the other financial transaction
052     * processing eDocs (i.e. external system fields, object type override, credit and debit amounts).
053     */
054    public class VoucherAction extends KualiAccountingDocumentActionBase {
055        // used to determine which way the change balance type action is switching
056        // these are local constants only used within this action class
057        // these should not be used outside of this class
058    
059        /**
060         * Overrides to call super, and then to repopulate the credit/debit amounts b/c the credit/debit code might change during a
061         * voucher error correction.
062         * 
063         * @see org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase#correct(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
064         */
065        @Override
066        public ActionForward correct(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
067            ActionForward actionForward = super.correct(mapping, form, request, response);
068    
069            VoucherForm vForm = (VoucherForm) form;
070    
071            // now make sure to repopulate credit/debit amounts
072            populateAllVoucherAccountingLineHelpers(vForm);
073    
074            return actionForward;
075        }
076    
077        /**
078         * Overrides parent to first populate the new source line with the correct debit or credit value, then it calls the parent's
079         * implementation.
080         * 
081         * @see org.kuali.module.financial.web.struts.action.KualiFinancialDocumentActionBase#insertSourceLine(org.apache.struts.action.ActionMapping,
082         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
083         */
084        @Override
085        public ActionForward insertSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
086            // cast the form to the right pojo
087            VoucherForm voucherForm = (VoucherForm) form;
088    
089            // call the super's method
090            ActionForward actionForward = super.insertSourceLine(mapping, form, request, response);
091    
092            if (GlobalVariables.getMessageMap().getErrorCount() == 0) {
093                // since no exceptions were thrown, the add succeeded, so we have to re-init the new credit and debit
094                // attributes, and add a new instance of a helperLine to the helperLines list
095                VoucherAccountingLineHelper helperLine = populateNewVoucherAccountingLineHelper(voucherForm);
096                voucherForm.getVoucherLineHelpers().add(helperLine);
097    
098                // now reset the debit and credit fields for adds
099                voucherForm.setNewSourceLineDebit(KualiDecimal.ZERO);
100                voucherForm.setNewSourceLineCredit(KualiDecimal.ZERO);
101            }
102    
103            return actionForward;
104        }
105    
106        /**
107         * Overrides parent to remove the associated helper line also, and then it call the parent's implementation.
108         * 
109         * @see org.kuali.module.financial.web.struts.action.KualiFinancialDocumentActionBase#deleteSourceLine(org.apache.struts.action.ActionMapping,
110         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
111         */
112        @Override
113        public ActionForward deleteSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
114            // cast the form to the right pojo
115            VoucherForm voucherForm = (VoucherForm) form;
116    
117            // call the super's method
118            ActionForward actionForward = super.deleteSourceLine(mapping, voucherForm, request, response);
119    
120            // now remove the associated helper line
121            int index = getLineToDelete(request);
122            if (voucherForm.getVoucherLineHelpers() != null && voucherForm.getVoucherLineHelpers().size() > index) {
123                voucherForm.getVoucherLineHelpers().remove(getLineToDelete(request));
124            }
125    
126            return actionForward;
127        }
128    
129        /**
130         * Overrides the parent to make sure that the AV specific accounting line helper forms are properly populated when the document
131         * is first loaded. This first calls super, then populates the helper objects.
132         * 
133         * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
134         */
135        @Override
136        protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
137            super.loadDocument(kualiDocumentFormBase);
138            VoucherForm voucherForm = (VoucherForm) kualiDocumentFormBase;
139    
140            populateAllVoucherAccountingLineHelpers(voucherForm);
141            voucherForm.setNewSourceLineCredit(KualiDecimal.ZERO);
142            voucherForm.setNewSourceLineDebit(KualiDecimal.ZERO);
143    
144            // always wipe out the new source line
145            voucherForm.setNewSourceLine(null);
146    
147            // reload the accounting period selections since now we have data in the document bo
148            populateSelectedAccountingPeriod(voucherForm.getVoucherDocument(), voucherForm);
149        }
150    
151        /**
152         * This method parses the accounting period value from the bo and builds the right string to pass to the form object as the
153         * selected value.
154         * 
155         * @param voucherDocument
156         * @param voucherForm
157         */
158        protected void populateSelectedAccountingPeriod(VoucherDocument voucherDocument, VoucherForm voucherForm) {
159            if (StringUtils.isNotBlank(voucherDocument.getPostingPeriodCode())) {
160                String selectedAccountingPeriod = voucherDocument.getPostingPeriodCode();
161                if (null != voucherDocument.getPostingYear()) {
162                    selectedAccountingPeriod += voucherDocument.getPostingYear().toString();
163                }
164                else {
165                    selectedAccountingPeriod += SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear().toString();
166                }
167                voucherForm.setSelectedAccountingPeriod(selectedAccountingPeriod);
168            }
169        }
170    
171        /**
172         * This populates a new helperLine instance with the one that was just added so that the new instance can be added to the
173         * helperLines list.
174         * 
175         * @param voucherForm
176         * @return VoucherAccountingLineHelper
177         */
178        protected VoucherAccountingLineHelper populateVoucherAccountingLineHelper(VoucherForm voucherForm) {
179            VoucherAccountingLineHelper helperLine = new VoucherAccountingLineHelperBase();
180    
181            KualiDecimal debitAmount = voucherForm.getNewSourceLineDebit();
182            if (debitAmount != null && StringUtils.isNotBlank(debitAmount.toString())) {
183                helperLine.setDebit(debitAmount);
184            }
185    
186            KualiDecimal creditAmount = voucherForm.getNewSourceLineCredit();
187            if (creditAmount != null && StringUtils.isNotBlank(creditAmount.toString())) {
188                helperLine.setCredit(creditAmount);
189            }
190    
191            return helperLine;
192        }
193    
194        /**
195         * This method builds the corresponding list of voucher acounting line helper objects so that a user can differentiate between
196         * credit and debit fields. It does this by iterating over each source accounting line (what the voucher uses) looking at the
197         * debit/credit code and then populateingLineHelpers a corresponding helper form instance with the amount in the appropriate
198         * amount field - credit or debit.
199         * 
200         * @param voucherForm
201         */
202        protected void populateAllVoucherAccountingLineHelpers(VoucherForm voucherForm) {
203            // make sure the journal voucher accounting line helper form list is populated properly
204            ArrayList voucherLineHelpers = (ArrayList) voucherForm.getVoucherLineHelpers();
205    
206            // make sure the helper list is the right size
207            VoucherDocument vDoc = (VoucherDocument) voucherForm.getTransactionalDocument();
208            int size = vDoc.getSourceAccountingLines().size();
209            voucherLineHelpers.ensureCapacity(size);
210    
211            // iterate through each source accounting line and initialize the helper form lines appropriately
212            for (int i = 0; i < size; i++) {
213                // get the bo's accounting line at the right index
214                SourceAccountingLine sourceAccountingLine = vDoc.getSourceAccountingLine(i);
215    
216                // instantiate a new helper form to use for populating the helper form list
217                VoucherAccountingLineHelper avAcctLineHelperForm = voucherForm.getVoucherLineHelper(i);
218    
219                // figure whether we need to set the credit amount or the debit amount
220                if (StringUtils.isNotBlank(sourceAccountingLine.getDebitCreditCode())) {
221                    if (sourceAccountingLine.getDebitCreditCode().equals(KFSConstants.GL_DEBIT_CODE)) {
222                        avAcctLineHelperForm.setDebit(sourceAccountingLine.getAmount());
223                        avAcctLineHelperForm.setCredit(KualiDecimal.ZERO);
224                    }
225                    else if (sourceAccountingLine.getDebitCreditCode().equals(KFSConstants.GL_CREDIT_CODE)) {
226                        avAcctLineHelperForm.setCredit(sourceAccountingLine.getAmount());
227                        avAcctLineHelperForm.setDebit(KualiDecimal.ZERO);
228                    }
229                }
230            }
231        }
232    
233    
234        /**
235         * This helper method determines from the request object instance whether or not the user has been prompted about the journal
236         * being out of balance. If they haven't, then the method will build the appropriate message given the state of the document and
237         * return control to the question component so that the user receives the "yes"/"no" prompt. If the question has been asked, the
238         * we evaluate the user's answer and direct the flow appropriately. If they answer with a "No", then we build out a message
239         * stating that they chose that value and return an ActionForward of a MAPPING_BASIC which keeps them at the same page that they
240         * were on. If they choose "Yes", then we return a null ActionForward, which the calling action method recognizes as a "Yes" and
241         * continues on processing the "Route."
242         * 
243         * @param mapping
244         * @param form
245         * @param request
246         * @param response
247         * @return ActionForward
248         * @throws Exception
249         */
250        protected ActionForward processRouteOutOfBalanceDocumentConfirmationQuestion(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
251            VoucherForm vForm = (VoucherForm) form;
252            VoucherDocument avDoc = vForm.getVoucherDocument();
253    
254            String question = request.getParameter(KFSConstants.QUESTION_INST_ATTRIBUTE_NAME);
255            KualiConfigurationService kualiConfiguration = SpringContext.getBean(KualiConfigurationService.class);
256    
257            if (question == null) { // question hasn't been asked
258                String currencyFormattedDebitTotal = (String) new CurrencyFormatter().format(avDoc.getDebitTotal());
259                String currencyFormattedCreditTotal = (String) new CurrencyFormatter().format(avDoc.getCreditTotal());
260                String currencyFormattedTotal = (String) new CurrencyFormatter().format(((AmountTotaling) avDoc).getTotalDollarAmount());
261                String message = "";
262                message = StringUtils.replace(kualiConfiguration.getPropertyString(KFSKeyConstants.QUESTION_ROUTE_OUT_OF_BALANCE_JV_DOC), "{0}", currencyFormattedDebitTotal);
263                message = StringUtils.replace(message, "{1}", currencyFormattedCreditTotal);
264    
265                // now transfer control over to the question component
266                return this.performQuestionWithoutInput(mapping, form, request, response, KFSConstants.JOURNAL_VOUCHER_ROUTE_OUT_OF_BALANCE_DOCUMENT_QUESTION, message, KFSConstants.CONFIRMATION_QUESTION, KFSConstants.ROUTE_METHOD, "");
267            }
268            else {
269                String buttonClicked = request.getParameter(KFSConstants.QUESTION_CLICKED_BUTTON);
270                if ((KFSConstants.JOURNAL_VOUCHER_ROUTE_OUT_OF_BALANCE_DOCUMENT_QUESTION.equals(question)) && ConfirmationQuestion.NO.equals(buttonClicked)) {
271                    GlobalVariables.getMessageList().add(KFSKeyConstants.MESSAGE_JV_CANCELLED_ROUTE);
272                    return mapping.findForward(KFSConstants.MAPPING_BASIC);
273                }
274            }
275            return null;
276        }
277    
278        /**
279         * This populates a new helperLine instance with the one that was just added so that the new instance can be added to the
280         * helperLines list.
281         * 
282         * @param voucherForm
283         * @return voucherAccountingLineHelper
284         */
285        protected VoucherAccountingLineHelper populateNewVoucherAccountingLineHelper(VoucherForm voucherForm) {
286            VoucherAccountingLineHelper helperLine = new VoucherAccountingLineHelperBase();
287    
288            KualiDecimal debitAmount = voucherForm.getNewSourceLineDebit();
289            if (debitAmount != null && StringUtils.isNotBlank(debitAmount.toString())) {
290                helperLine.setDebit(debitAmount);
291            }
292    
293            KualiDecimal creditAmount = voucherForm.getNewSourceLineCredit();
294            if (creditAmount != null && StringUtils.isNotBlank(creditAmount.toString())) {
295                helperLine.setCredit(creditAmount);
296            }
297    
298            return helperLine;
299        }
300    
301        /**
302         * This action executes a call to upload CSV accounting line values as SourceAccountingLines for a given transactional document.
303         * The "uploadAccountingLines()" method handles the multi-part request.
304         * 
305         * @param mapping
306         * @param form
307         * @param request
308         * @param response
309         * @return ActionForward
310         * @throws FileNotFoundException
311         * @throws IOException
312         */
313        @Override
314        public ActionForward uploadSourceLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws FileNotFoundException, IOException {
315            // call method that sourceform and destination list
316            uploadAccountingLines(true, form);
317    
318            return mapping.findForward(KFSConstants.MAPPING_BASIC);
319        }
320    
321        /**
322         * This method determines whether we are uploading source or target lines, and then calls uploadAccountingLines directly on the
323         * document object. This method handles retrieving the actual upload file as an input stream into the document.
324         * 
325         * @param isSource
326         * @param form
327         * @throws FileNotFoundException
328         * @throws IOException
329         */
330        @Override
331        protected void uploadAccountingLines(boolean isSource, ActionForm form) throws FileNotFoundException, IOException {
332            super.uploadAccountingLines(isSource, form);
333    
334            populateAllVoucherAccountingLineHelpers((VoucherForm) form);
335        }
336    
337        /**
338         * Overridden to reset the available and selected accounting periods on the form, so that copies are moved forward to the current accounting period correctly
339         * @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#copy(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
340         */
341        @Override
342        public ActionForward copy(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
343            ActionForward forward = super.copy(mapping, form, request, response);
344            VoucherForm voucherForm = (VoucherForm)form;
345            voucherForm.populateAccountingPeriodListForRendering();
346            voucherForm.populateDefaultSelectedAccountingPeriod();
347            return forward;
348        }
349    }