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 static org.kuali.kfs.sys.KFSConstants.VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME;
019    import static org.kuali.kfs.sys.KFSConstants.VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME;
020    
021    import java.sql.Date;
022    import java.util.ArrayList;
023    import java.util.List;
024    import java.util.Map;
025    
026    import javax.servlet.http.HttpServletRequest;
027    
028    import org.apache.commons.lang.StringUtils;
029    import org.kuali.kfs.coa.businessobject.AccountingPeriod;
030    import org.kuali.kfs.coa.businessobject.ObjectCode;
031    import org.kuali.kfs.coa.businessobject.SubObjectCode;
032    import org.kuali.kfs.coa.service.AccountingPeriodService;
033    import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelper;
034    import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelperBase;
035    import org.kuali.kfs.fp.document.VoucherDocument;
036    import org.kuali.kfs.sys.KFSConstants;
037    import org.kuali.kfs.sys.KFSKeyConstants;
038    import org.kuali.kfs.sys.KFSPropertyConstants;
039    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
040    import org.kuali.kfs.sys.context.SpringContext;
041    import org.kuali.kfs.sys.document.AmountTotaling;
042    import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase;
043    import org.kuali.rice.kns.service.DateTimeService;
044    import org.kuali.rice.kns.util.GlobalVariables;
045    import org.kuali.rice.kns.util.KualiDecimal;
046    import org.kuali.rice.kns.util.ObjectUtils;
047    import org.kuali.rice.kns.web.format.CurrencyFormatter;
048    
049    /**
050     * This class is the Struts specific form object that works in conjunction with the pojo utilities to build the UI for Voucher
051     * Document instances. This class is unique in that it leverages a helper data structure called the
052     * <code>{@link VoucherAccountingLineHelper}</code> because Voucher documents, under some/none conditions, presents the user with
053     * a debit and credit column for amount entry. New accounting lines use specific credit and debit amount fields b/c the new line is
054     * explicitly known; however, already existing accounting lines need to exist within a list with ordering that matches the
055     * accounting lines source list.
056     */
057    public class VoucherForm extends KualiAccountingDocumentFormBase {
058        protected List accountingPeriods;
059        protected KualiDecimal newSourceLineDebit;
060        protected KualiDecimal newSourceLineCredit;
061        protected List voucherLineHelpers;
062        protected String selectedAccountingPeriod;
063    
064        /**
065         * Supplements a constructor for this voucher class
066         */
067        public VoucherForm() {
068            populateDefaultSelectedAccountingPeriod();
069            setNewSourceLineCredit(KualiDecimal.ZERO);
070            setNewSourceLineDebit(KualiDecimal.ZERO);
071            setVoucherLineHelpers(new ArrayList());
072        }
073    
074        /**
075         * sets initial selected accounting period to current period
076         */
077        public void populateDefaultSelectedAccountingPeriod() {
078            Date date = SpringContext.getBean(DateTimeService.class).getCurrentSqlDate();
079            AccountingPeriod accountingPeriod = SpringContext.getBean(AccountingPeriodService.class).getByDate(date);
080    
081            StringBuffer sb = new StringBuffer();
082            sb.append(accountingPeriod.getUniversityFiscalPeriodCode());
083            sb.append(accountingPeriod.getUniversityFiscalYear());
084    
085            setSelectedAccountingPeriod(sb.toString());
086        }
087    
088        /**
089         * Overrides the parent to call super.populate and then to call the two methods that are specific to loading the two select
090         * lists on the page. In addition, this also makes sure that the credit and debit amounts are filled in for situations where
091         * validation errors occur and the page reposts.
092         * 
093         * @see org.kuali.rice.kns.web.struts.pojo.PojoForm#populate(javax.servlet.http.HttpServletRequest)
094         */
095        @Override
096        public void populate(HttpServletRequest request) {
097            super.populate(request);
098    
099            // populate the drop downs
100            if (KFSConstants.RETURN_METHOD_TO_CALL.equals(getMethodToCall())) {
101                String selectedPeriod = (StringUtils.defaultString(request.getParameter(KFSPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE)) + StringUtils.defaultString(request.getParameter(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR)));
102                if (StringUtils.isNotBlank(selectedPeriod)) {
103                    setSelectedAccountingPeriod(selectedPeriod);
104                }
105            }
106            populateAccountingPeriodListForRendering();
107    
108            // we don't want to do this if we are just reloading the document
109            if (StringUtils.isBlank(getMethodToCall()) || !getMethodToCall().equals(KFSConstants.RELOAD_METHOD_TO_CALL)) {
110                // make sure the amount fields are populated appropriately when in debit/credit amount mode
111                populateCreditAndDebitAmounts();
112            }
113        }
114    
115        /**
116         * util method to get postingYear out of selectedAccountingPeriod
117         * 
118         * @return Integer
119         */
120    
121        protected Integer getSelectedPostingYear() {
122            Integer postingYear = null;
123            if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
124                postingYear = new Integer(StringUtils.right(getSelectedAccountingPeriod(), 4));
125            }
126            return postingYear;
127        }
128    
129        /**
130         * util method to get posting period code out of selectedAccountingPeriod
131         * 
132         * @return String
133         */
134        protected String getSelectedPostingPeriodCode() {
135            String periodCode = null;
136            String selectedPeriod = getSelectedAccountingPeriod();
137            if (StringUtils.isNotBlank(selectedPeriod)) {
138                periodCode = StringUtils.left(selectedPeriod, 2);
139            }
140            return periodCode;
141        }
142    
143        /**
144         * Helper method to make casting easier
145         * 
146         * @return VoucherDocument
147         */
148        public VoucherDocument getVoucherDocument() {
149            return (VoucherDocument) getDocument();
150        }
151    
152        /**
153         * Override the parent, to push the chosen accounting period and balance type down into the source accounting line object. In
154         * addition, check the balance type to see if it's the "External Encumbrance" balance and alter the encumbrance update code on
155         * the accounting line appropriately.
156         * 
157         * @see org.kuali.rice.kns.web.struts.form.KualiTransactionalDocumentFormBase#populateSourceAccountingLine(org.kuali.rice.kns.bo.SourceAccountingLine)
158         */
159        @Override
160        public void populateSourceAccountingLine(SourceAccountingLine sourceLine, String accountingLinePropertyName, Map parameterMap) {
161            super.populateSourceAccountingLine(sourceLine, accountingLinePropertyName, parameterMap);
162    
163            // set the chosen accounting period into the line
164            String selectedAccountingPeriod = getSelectedAccountingPeriod();
165    
166            if (StringUtils.isNotBlank(selectedAccountingPeriod)) {
167                Integer postingYear = getSelectedPostingYear();
168                sourceLine.setPostingYear(postingYear);
169    
170                if (ObjectUtils.isNull(sourceLine.getObjectCode())) {
171                    sourceLine.setObjectCode(new ObjectCode());
172                }
173                sourceLine.getObjectCode().setUniversityFiscalYear(postingYear);
174    
175                if (ObjectUtils.isNull(sourceLine.getSubObjectCode())) {
176                    sourceLine.setSubObjectCode(new SubObjectCode());
177                }
178                sourceLine.getSubObjectCode().setUniversityFiscalYear(postingYear);
179            }
180    
181        }
182    
183        /**
184         * This method retrieves the list of valid accounting periods to display.
185         * 
186         * @return List
187         */
188        public List getAccountingPeriods() {
189            return accountingPeriods;
190        }
191    
192        /**
193         * This method sets the list of valid accounting periods to display.
194         * 
195         * @param accountingPeriods
196         */
197        public void setAccountingPeriods(List accountingPeriods) {
198            this.accountingPeriods = accountingPeriods;
199        }
200    
201        /**
202         * This method returns the reversal date in the format MMM d, yyyy.
203         * 
204         * @return String
205         */
206        public String getFormattedReversalDate() {
207            return formatReversalDate(getVoucherDocument().getReversalDate());
208        }
209    
210        /**
211         * This method retrieves the selectedAccountingPeriod.
212         * 
213         * @return String
214         */
215        public String getSelectedAccountingPeriod() {
216            return selectedAccountingPeriod;
217        }
218    
219        /**
220         * @return AccountingPeriod associated with the currently selected period
221         */
222        public AccountingPeriod getAccountingPeriod() {
223            AccountingPeriod period = null;
224    
225            if (!StringUtils.isBlank(getSelectedAccountingPeriod())) {
226                period = SpringContext.getBean(AccountingPeriodService.class).getByPeriod(getSelectedPostingPeriodCode(), getSelectedPostingYear());
227            }
228    
229            return period;
230        }
231    
232        /**
233         * This method sets the selectedAccountingPeriod.
234         * 
235         * @param selectedAccountingPeriod
236         */
237        public void setSelectedAccountingPeriod(String selectedAccountingPeriod) {
238            this.selectedAccountingPeriod = selectedAccountingPeriod;
239        }
240    
241        /**
242         * Accessor to the list of <code>{@link VoucherAccountingLineHelper}</code> instances. This method retrieves the list of
243         * helper line objects for the form.
244         * 
245         * @return List
246         */
247        public List getVoucherLineHelpers() {
248            return voucherLineHelpers;
249        }
250    
251        /**
252         * This method retrieves the proper voucher helper line data structure at the passed in list index so that it matches up with
253         * the correct accounting line at that index.
254         * 
255         * @param index
256         * @return VoucherAccountingLineHelper
257         */
258        public VoucherAccountingLineHelper getVoucherLineHelper(int index) {
259            while (getVoucherLineHelpers().size() <= index) {
260                getVoucherLineHelpers().add(new VoucherAccountingLineHelperBase());
261            }
262            return (VoucherAccountingLineHelper) getVoucherLineHelpers().get(index);
263        }
264    
265        /**
266         * This method sets the list of helper lines for the form.
267         * 
268         * @param voucherLineHelpers
269         */
270        public void setVoucherLineHelpers(List voucherLineHelpers) {
271            this.voucherLineHelpers = voucherLineHelpers;
272        }
273    
274        /**
275         * This method retrieves the credit amount of the new accounting line that was added.
276         * 
277         * @return KualiDecimal
278         */
279        public KualiDecimal getNewSourceLineCredit() {
280            return newSourceLineCredit;
281        }
282    
283        /**
284         * This method sets the credit amount of the new accounting line that was added.
285         * 
286         * @param newSourceLineCredit
287         */
288        public void setNewSourceLineCredit(KualiDecimal newSourceLineCredit) {
289            this.newSourceLineCredit = newSourceLineCredit;
290        }
291    
292        /**
293         * This method retrieves the debit amount of the new accounting line that was added.
294         * 
295         * @return KualiDecimal
296         */
297        public KualiDecimal getNewSourceLineDebit() {
298            return newSourceLineDebit;
299        }
300    
301        /**
302         * This method sets the debit amount of the new accounting line that was added.
303         * 
304         * @param newSourceLineDebit
305         */
306        public void setNewSourceLineDebit(KualiDecimal newSourceLineDebit) {
307            this.newSourceLineDebit = newSourceLineDebit;
308        }
309    
310        /**
311         * This method retrieves the voucher's debit total formatted as currency.
312         * 
313         * @return String
314         */
315        public String getCurrencyFormattedDebitTotal() {
316            return (String) new CurrencyFormatter().format(getVoucherDocument().getDebitTotal());
317        }
318    
319        /**
320         * This method retrieves the voucher's credit total formatted as currency.
321         * 
322         * @return String
323         */
324        public String getCurrencyFormattedCreditTotal() {
325            return (String) new CurrencyFormatter().format(getVoucherDocument().getCreditTotal());
326        }
327    
328        /**
329         * This method retrieves the voucher's total formatted as currency.
330         * 
331         * @return String
332         */
333        public String getCurrencyFormattedTotal() {
334            return (String) new CurrencyFormatter().format(((AmountTotaling) getVoucherDocument()).getTotalDollarAmount());
335        }
336    
337        /**
338         * This method retrieves all of the "open for posting" accounting periods and prepares them to be rendered in a dropdown UI
339         * component.
340         */
341        public void populateAccountingPeriodListForRendering() {
342            // grab the list of valid accounting periods
343            ArrayList accountingPeriods = new ArrayList(SpringContext.getBean(AccountingPeriodService.class).getOpenAccountingPeriods());
344            // set into the form for rendering
345            setAccountingPeriods(accountingPeriods);
346            // set the chosen accounting period into the form
347            populateSelectedVoucherAccountingPeriod();
348        }
349    
350    
351        /**
352         * This method parses the accounting period value from the form and builds a basic AccountingPeriod object so that the voucher
353         * is properly persisted with the accounting period set for it.
354         */
355        protected void populateSelectedVoucherAccountingPeriod() {
356            if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
357                AccountingPeriod ap = new AccountingPeriod();
358                ap.setUniversityFiscalPeriodCode(getSelectedPostingPeriodCode());
359                ap.setUniversityFiscalYear(getSelectedPostingYear());
360                getFinancialDocument().setAccountingPeriod(ap);
361            }
362        }
363    
364        /**
365         * If the balance type is an offset generation balance type, then the user is able to enter the amount as either a debit or a
366         * credit, otherwise, they only need to deal with the amount field in this case we always need to update the underlying bo so
367         * that the debit/credit code along with the amount, is properly set.
368         */
369        protected void populateCreditAndDebitAmounts() {
370            processDebitAndCreditForNewSourceLine();
371            processDebitAndCreditForAllSourceLines();
372        }
373    
374        /**
375         * This method uses the newly entered debit and credit amounts to populate the new source line that is to be added to the
376         * voucher document.
377         * 
378         * @return boolean True if the processing was successful, false otherwise.
379         */
380        protected boolean processDebitAndCreditForNewSourceLine() {
381            // using debits and credits supplied, populate the new source accounting line's amount and debit/credit code appropriately
382            boolean passed = processDebitAndCreditForSourceLine(getNewSourceLine(), newSourceLineDebit, newSourceLineCredit, KFSConstants.NEGATIVE_ONE);
383    
384            return passed;
385        }
386    
387        /**
388         * This method iterates through all of the source accounting lines associated with the voucher doc and accounts for any changes
389         * to the credit and debit amounts, populate the source lines' amount and debit/credit code fields appropriately, so that they
390         * can be persisted accurately. This accounts for the fact that users may change the amounts and/or flip-flop the credit debit
391         * amounts on any accounting line after the initial add of the accounting line.
392         * 
393         * @return boolean
394         */
395        protected boolean processDebitAndCreditForAllSourceLines() {
396            VoucherDocument vDoc = getVoucherDocument();
397    
398            // iterate through all of the source accounting lines
399            boolean validProcessing = true;
400            for (int i = 0; i < vDoc.getSourceAccountingLines().size(); i++) {
401                // retrieve the proper business objects from the form
402                SourceAccountingLine sourceLine = vDoc.getSourceAccountingLine(i);
403                VoucherAccountingLineHelper voucherLineHelper = getVoucherLineHelper(i);
404    
405                // now process the amounts and the line
406                // we want to process all lines, some may be invalid b/c of dual amount values, but this method will handle
407                // only processing the valid ones, that way we are guaranteed that values in the valid lines carry over through the
408                // post and invalid ones do not alter the underlying business object
409                validProcessing &= processDebitAndCreditForSourceLine(sourceLine, voucherLineHelper.getDebit(), voucherLineHelper.getCredit(), i);
410            }
411            return validProcessing;
412        }
413    
414        /**
415         * This method checks the debit and credit attributes passed in, figures out which one has a value, and sets the source
416         * accounting line's amount and debit/credit attribute appropriately. It assumes that if it finds something in the debit field,
417         * it's a debit entry, otherwise it's a credit entry. If a user enters a value into both fields, it will assume the debit value,
418         * then when the br eval framework applies the "add" rule, it will bomb out. If first checks to make sure that there isn't a
419         * value in both the credit and debit columns.
420         * 
421         * @param sourceLine
422         * @param debitAmount
423         * @param creditAmount
424         * @param index if -1, then its a new line, if not -1 then it's an existing line
425         * @return boolean True if the processing was successful, false otherwise.
426         */
427        protected boolean processDebitAndCreditForSourceLine(SourceAccountingLine sourceLine, KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
428            // check to make sure that the
429            if (!validateCreditAndDebitAmounts(debitAmount, creditAmount, index)) {
430                return false;
431            }
432    
433            // check to see which amount field has a value - credit or debit field?
434            // and set the values of the appropriate fields
435            if (debitAmount != null && debitAmount.isNonZero()) { // a value entered into the debit field? if so it's a debit
436                // create a new instance w/out reference
437                KualiDecimal tmpDebitAmount = new KualiDecimal(debitAmount.toString());
438                sourceLine.setDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
439                sourceLine.setAmount(tmpDebitAmount);
440            }
441            else if (creditAmount != null && creditAmount.isNonZero()) { // assume credit, if both are set the br eval framework will
442                // catch it
443                KualiDecimal tmpCreditAmount = new KualiDecimal(creditAmount.toString());
444                sourceLine.setDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
445                sourceLine.setAmount(tmpCreditAmount);
446            }
447            else { // default to DEBIT, note the br eval framework will still pick it up
448                sourceLine.setDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
449                sourceLine.setAmount(KualiDecimal.ZERO);
450            }
451    
452            return true;
453        }
454    
455        /**
456         * This method checks to make sure that there isn't a value in both the credit and debit columns for a given accounting line.
457         * 
458         * @param creditAmount
459         * @param debitAmount
460         * @param index if -1, it's a new line, if not -1, then its an existing line
461         * @return boolean False if both the credit and debit fields have a value, true otherwise.
462         */
463        protected boolean validateCreditAndDebitAmounts(KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
464            boolean valid = false;
465            if (null != creditAmount && null != debitAmount) {
466                if (creditAmount.isNonZero() && debitAmount.isNonZero()) {
467                    // there's a value in both fields
468                    if (KFSConstants.NEGATIVE_ONE == index) { // it's a new line
469                        GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSConstants.DEBIT_AMOUNT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
470                        GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSConstants.CREDIT_AMOUNT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
471                    }
472                    else {
473                        String errorKeyPath = KFSConstants.JOURNAL_LINE_HELPER_PROPERTY_NAME + KFSConstants.SQUARE_BRACKET_LEFT + Integer.toString(index) + KFSConstants.SQUARE_BRACKET_RIGHT;
474                        GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
475                        GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
476                    }
477                }
478                else {
479                    valid = true;
480                }
481            }
482            else {
483                valid = true;
484            }
485            return valid;
486        }
487    }