View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.fp.document.web.struts;
20  
21  import static org.kuali.kfs.sys.KFSConstants.VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME;
22  import static org.kuali.kfs.sys.KFSConstants.VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME;
23  
24  import java.sql.Date;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Map;
28  
29  import javax.servlet.http.HttpServletRequest;
30  
31  import org.apache.commons.lang.StringUtils;
32  import org.kuali.kfs.coa.businessobject.AccountingPeriod;
33  import org.kuali.kfs.coa.businessobject.ObjectCode;
34  import org.kuali.kfs.coa.businessobject.SubObjectCode;
35  import org.kuali.kfs.coa.service.AccountingPeriodService;
36  import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelper;
37  import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelperBase;
38  import org.kuali.kfs.fp.document.VoucherDocument;
39  import org.kuali.kfs.sys.KFSConstants;
40  import org.kuali.kfs.sys.KFSKeyConstants;
41  import org.kuali.kfs.sys.KFSPropertyConstants;
42  import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
43  import org.kuali.kfs.sys.context.SpringContext;
44  import org.kuali.kfs.sys.document.AmountTotaling;
45  import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase;
46  import org.kuali.rice.core.api.datetime.DateTimeService;
47  import org.kuali.rice.core.api.util.type.KualiDecimal;
48  import org.kuali.rice.core.web.format.CurrencyFormatter;
49  import org.kuali.rice.krad.util.GlobalVariables;
50  import org.kuali.rice.krad.util.ObjectUtils;
51  
52  /**
53   * This class is the Struts specific form object that works in conjunction with the pojo utilities to build the UI for Voucher
54   * Document instances. This class is unique in that it leverages a helper data structure called the
55   * <code>{@link VoucherAccountingLineHelper}</code> because Voucher documents, under some/none conditions, presents the user with
56   * a debit and credit column for amount entry. New accounting lines use specific credit and debit amount fields b/c the new line is
57   * explicitly known; however, already existing accounting lines need to exist within a list with ordering that matches the
58   * accounting lines source list.
59   */
60  public class VoucherForm extends KualiAccountingDocumentFormBase {
61      protected List accountingPeriods;
62      protected KualiDecimal newSourceLineDebit;
63      protected KualiDecimal newSourceLineCredit;
64      protected List voucherLineHelpers;
65      protected String selectedAccountingPeriod;
66  
67      /**
68       * Supplements a constructor for this voucher class
69       */
70      public VoucherForm() {
71          populateDefaultSelectedAccountingPeriod();
72          setNewSourceLineCredit(KualiDecimal.ZERO);
73          setNewSourceLineDebit(KualiDecimal.ZERO);
74          setVoucherLineHelpers(new ArrayList());
75      }
76  
77      /**
78       * sets initial selected accounting period to current period
79       */
80      public void populateDefaultSelectedAccountingPeriod() {
81          Date date = SpringContext.getBean(DateTimeService.class).getCurrentSqlDate();
82          AccountingPeriod accountingPeriod = SpringContext.getBean(AccountingPeriodService.class).getByDate(date);
83  
84          StringBuffer sb = new StringBuffer();
85          sb.append(accountingPeriod.getUniversityFiscalPeriodCode());
86          sb.append(accountingPeriod.getUniversityFiscalYear());
87  
88          setSelectedAccountingPeriod(sb.toString());
89      }
90  
91      /**
92       * Overrides the parent to call super.populate and then to call the two methods that are specific to loading the two select
93       * lists on the page. In addition, this also makes sure that the credit and debit amounts are filled in for situations where
94       * validation errors occur and the page reposts.
95       * 
96       * @see org.kuali.rice.kns.web.struts.pojo.PojoForm#populate(javax.servlet.http.HttpServletRequest)
97       */
98      @Override
99      public void populate(HttpServletRequest request) {
100         super.populate(request);
101 
102         // populate the drop downs
103         if (KFSConstants.RETURN_METHOD_TO_CALL.equals(getMethodToCall())) {
104             String selectedPeriod = (StringUtils.defaultString(request.getParameter(KFSPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE)) + StringUtils.defaultString(request.getParameter(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR)));
105             if (StringUtils.isNotBlank(selectedPeriod)) {
106                 setSelectedAccountingPeriod(selectedPeriod);
107             }
108         }
109         populateAccountingPeriodListForRendering();
110 
111         // we don't want to do this if we are just reloading the document
112         if (StringUtils.isBlank(getMethodToCall()) || !getMethodToCall().equals(KFSConstants.RELOAD_METHOD_TO_CALL)) {
113             // make sure the amount fields are populated appropriately when in debit/credit amount mode
114             populateCreditAndDebitAmounts();
115         }
116     }
117 
118     /**
119      * util method to get postingYear out of selectedAccountingPeriod
120      * 
121      * @return Integer
122      */
123 
124     protected Integer getSelectedPostingYear() {
125         Integer postingYear = null;
126         if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
127             postingYear = new Integer(StringUtils.right(getSelectedAccountingPeriod(), 4));
128         }
129         return postingYear;
130     }
131 
132     /**
133      * util method to get posting period code out of selectedAccountingPeriod
134      * 
135      * @return String
136      */
137     protected String getSelectedPostingPeriodCode() {
138         String periodCode = null;
139         String selectedPeriod = getSelectedAccountingPeriod();
140         if (StringUtils.isNotBlank(selectedPeriod)) {
141             periodCode = StringUtils.left(selectedPeriod, 2);
142         }
143         return periodCode;
144     }
145 
146     /**
147      * Helper method to make casting easier
148      * 
149      * @return VoucherDocument
150      */
151     public VoucherDocument getVoucherDocument() {
152         return (VoucherDocument) getDocument();
153     }
154 
155     /**
156      * Override the parent, to push the chosen accounting period and balance type down into the source accounting line object. In
157      * addition, check the balance type to see if it's the "External Encumbrance" balance and alter the encumbrance update code on
158      * the accounting line appropriately.
159      * 
160      * @see org.kuali.rice.kns.web.struts.form.KualiTransactionalDocumentFormBase#populateSourceAccountingLine(org.kuali.rice.krad.bo.SourceAccountingLine)
161      */
162     @Override
163     public void populateSourceAccountingLine(SourceAccountingLine sourceLine, String accountingLinePropertyName, Map parameterMap) {
164         super.populateSourceAccountingLine(sourceLine, accountingLinePropertyName, parameterMap);
165 
166         // set the chosen accounting period into the line
167         String selectedAccountingPeriod = getSelectedAccountingPeriod();
168 
169         if (StringUtils.isNotBlank(selectedAccountingPeriod)) {
170             Integer postingYear = getSelectedPostingYear();
171             sourceLine.setPostingYear(postingYear);
172 
173             if (ObjectUtils.isNull(sourceLine.getObjectCode())) {
174                 sourceLine.setObjectCode(new ObjectCode());
175             }
176             sourceLine.getObjectCode().setUniversityFiscalYear(postingYear);
177 
178             if (ObjectUtils.isNull(sourceLine.getSubObjectCode())) {
179                 sourceLine.setSubObjectCode(new SubObjectCode());
180             }
181             sourceLine.getSubObjectCode().setUniversityFiscalYear(postingYear);
182         }
183 
184     }
185 
186     /**
187      * This method retrieves the list of valid accounting periods to display.
188      * 
189      * @return List
190      */
191     public List getAccountingPeriods() {
192         return accountingPeriods;
193     }
194 
195     /**
196      * This method sets the list of valid accounting periods to display.
197      * 
198      * @param accountingPeriods
199      */
200     public void setAccountingPeriods(List accountingPeriods) {
201         this.accountingPeriods = accountingPeriods;
202     }
203 
204     /**
205      * This method returns the reversal date in the format MMM d, yyyy.
206      * 
207      * @return String
208      */
209     public String getFormattedReversalDate() {
210         return formatReversalDate(getVoucherDocument().getReversalDate());
211     }
212 
213     /**
214      * This method retrieves the selectedAccountingPeriod.
215      * 
216      * @return String
217      */
218     public String getSelectedAccountingPeriod() {
219         return selectedAccountingPeriod;
220     }
221 
222     /**
223      * @return AccountingPeriod associated with the currently selected period
224      */
225     public AccountingPeriod getAccountingPeriod() {
226         AccountingPeriod period = null;
227 
228         if (!StringUtils.isBlank(getSelectedAccountingPeriod())) {
229             period = SpringContext.getBean(AccountingPeriodService.class).getByPeriod(getSelectedPostingPeriodCode(), getSelectedPostingYear());
230         }
231 
232         return period;
233     }
234 
235     /**
236      * This method sets the selectedAccountingPeriod.
237      * 
238      * @param selectedAccountingPeriod
239      */
240     public void setSelectedAccountingPeriod(String selectedAccountingPeriod) {
241         this.selectedAccountingPeriod = selectedAccountingPeriod;
242     }
243 
244     /**
245      * Accessor to the list of <code>{@link VoucherAccountingLineHelper}</code> instances. This method retrieves the list of
246      * helper line objects for the form.
247      * 
248      * @return List
249      */
250     public List getVoucherLineHelpers() {
251         return voucherLineHelpers;
252     }
253 
254     /**
255      * This method retrieves the proper voucher helper line data structure at the passed in list index so that it matches up with
256      * the correct accounting line at that index.
257      * 
258      * @param index
259      * @return VoucherAccountingLineHelper
260      */
261     public VoucherAccountingLineHelper getVoucherLineHelper(int index) {
262         while (getVoucherLineHelpers().size() <= index) {
263             getVoucherLineHelpers().add(new VoucherAccountingLineHelperBase());
264         }
265         return (VoucherAccountingLineHelper) getVoucherLineHelpers().get(index);
266     }
267 
268     /**
269      * This method sets the list of helper lines for the form.
270      * 
271      * @param voucherLineHelpers
272      */
273     public void setVoucherLineHelpers(List voucherLineHelpers) {
274         this.voucherLineHelpers = voucherLineHelpers;
275     }
276 
277     /**
278      * This method retrieves the credit amount of the new accounting line that was added.
279      * 
280      * @return KualiDecimal
281      */
282     public KualiDecimal getNewSourceLineCredit() {
283         return newSourceLineCredit;
284     }
285 
286     /**
287      * This method sets the credit amount of the new accounting line that was added.
288      * 
289      * @param newSourceLineCredit
290      */
291     public void setNewSourceLineCredit(KualiDecimal newSourceLineCredit) {
292         this.newSourceLineCredit = newSourceLineCredit;
293     }
294 
295     /**
296      * This method retrieves the debit amount of the new accounting line that was added.
297      * 
298      * @return KualiDecimal
299      */
300     public KualiDecimal getNewSourceLineDebit() {
301         return newSourceLineDebit;
302     }
303 
304     /**
305      * This method sets the debit amount of the new accounting line that was added.
306      * 
307      * @param newSourceLineDebit
308      */
309     public void setNewSourceLineDebit(KualiDecimal newSourceLineDebit) {
310         this.newSourceLineDebit = newSourceLineDebit;
311     }
312 
313     /**
314      * This method retrieves the voucher's debit total formatted as currency.
315      * 
316      * @return String
317      */
318     public String getCurrencyFormattedDebitTotal() {
319         return (String) new CurrencyFormatter().format(getVoucherDocument().getDebitTotal());
320     }
321 
322     /**
323      * This method retrieves the voucher's credit total formatted as currency.
324      * 
325      * @return String
326      */
327     public String getCurrencyFormattedCreditTotal() {
328         return (String) new CurrencyFormatter().format(getVoucherDocument().getCreditTotal());
329     }
330 
331     /**
332      * This method retrieves the voucher's total formatted as currency.
333      * 
334      * @return String
335      */
336     public String getCurrencyFormattedTotal() {
337         return (String) new CurrencyFormatter().format(((AmountTotaling) getVoucherDocument()).getTotalDollarAmount());
338     }
339 
340     /**
341      * This method retrieves all of the "open for posting" accounting periods and prepares them to be rendered in a dropdown UI
342      * component.
343      */
344     public void populateAccountingPeriodListForRendering() {
345         // grab the list of valid accounting periods
346         ArrayList accountingPeriods = new ArrayList(SpringContext.getBean(AccountingPeriodService.class).getOpenAccountingPeriods());
347         // set into the form for rendering
348         setAccountingPeriods(accountingPeriods);
349         // set the chosen accounting period into the form
350         populateSelectedVoucherAccountingPeriod();
351     }
352 
353 
354     /**
355      * This method parses the accounting period value from the form and builds a basic AccountingPeriod object so that the voucher
356      * is properly persisted with the accounting period set for it.
357      */
358     protected void populateSelectedVoucherAccountingPeriod() {
359         if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
360             AccountingPeriod ap = new AccountingPeriod();
361             ap.setUniversityFiscalPeriodCode(getSelectedPostingPeriodCode());
362             ap.setUniversityFiscalYear(getSelectedPostingYear());
363             getFinancialDocument().setAccountingPeriod(ap);
364         }
365     }
366 
367     /**
368      * 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
369      * credit, otherwise, they only need to deal with the amount field in this case we always need to update the underlying bo so
370      * that the debit/credit code along with the amount, is properly set.
371      */
372     protected void populateCreditAndDebitAmounts() {
373         processDebitAndCreditForNewSourceLine();
374         processDebitAndCreditForAllSourceLines();
375     }
376 
377     /**
378      * This method uses the newly entered debit and credit amounts to populate the new source line that is to be added to the
379      * voucher document.
380      * 
381      * @return boolean True if the processing was successful, false otherwise.
382      */
383     protected boolean processDebitAndCreditForNewSourceLine() {
384         // using debits and credits supplied, populate the new source accounting line's amount and debit/credit code appropriately
385         boolean passed = processDebitAndCreditForSourceLine(getNewSourceLine(), newSourceLineDebit, newSourceLineCredit, KFSConstants.NEGATIVE_ONE);
386 
387         return passed;
388     }
389 
390     /**
391      * This method iterates through all of the source accounting lines associated with the voucher doc and accounts for any changes
392      * to the credit and debit amounts, populate the source lines' amount and debit/credit code fields appropriately, so that they
393      * can be persisted accurately. This accounts for the fact that users may change the amounts and/or flip-flop the credit debit
394      * amounts on any accounting line after the initial add of the accounting line.
395      * 
396      * @return boolean
397      */
398     protected boolean processDebitAndCreditForAllSourceLines() {
399         VoucherDocument vDoc = getVoucherDocument();
400 
401         // iterate through all of the source accounting lines
402         boolean validProcessing = true;
403         for (int i = 0; i < vDoc.getSourceAccountingLines().size(); i++) {
404             // retrieve the proper business objects from the form
405             SourceAccountingLine sourceLine = vDoc.getSourceAccountingLine(i);
406             VoucherAccountingLineHelper voucherLineHelper = getVoucherLineHelper(i);
407 
408             // now process the amounts and the line
409             // we want to process all lines, some may be invalid b/c of dual amount values, but this method will handle
410             // only processing the valid ones, that way we are guaranteed that values in the valid lines carry over through the
411             // post and invalid ones do not alter the underlying business object
412             validProcessing &= processDebitAndCreditForSourceLine(sourceLine, voucherLineHelper.getDebit(), voucherLineHelper.getCredit(), i);
413         }
414         return validProcessing;
415     }
416 
417     /**
418      * This method checks the debit and credit attributes passed in, figures out which one has a value, and sets the source
419      * accounting line's amount and debit/credit attribute appropriately. It assumes that if it finds something in the debit field,
420      * 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,
421      * 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
422      * value in both the credit and debit columns.
423      * 
424      * @param sourceLine
425      * @param debitAmount
426      * @param creditAmount
427      * @param index if -1, then its a new line, if not -1 then it's an existing line
428      * @return boolean True if the processing was successful, false otherwise.
429      */
430     protected boolean processDebitAndCreditForSourceLine(SourceAccountingLine sourceLine, KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
431         // check to make sure that the
432         if (!validateCreditAndDebitAmounts(debitAmount, creditAmount, index)) {
433             return false;
434         }
435 
436         // check to see which amount field has a value - credit or debit field?
437         // and set the values of the appropriate fields
438         if (debitAmount != null && debitAmount.isNonZero()) { // a value entered into the debit field? if so it's a debit
439             // create a new instance w/out reference
440             KualiDecimal tmpDebitAmount = new KualiDecimal(debitAmount.toString());
441             sourceLine.setDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
442             sourceLine.setAmount(tmpDebitAmount);
443         }
444         else if (creditAmount != null && creditAmount.isNonZero()) { // assume credit, if both are set the br eval framework will
445             // catch it
446             KualiDecimal tmpCreditAmount = new KualiDecimal(creditAmount.toString());
447             sourceLine.setDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
448             sourceLine.setAmount(tmpCreditAmount);
449         }
450         else { // default to DEBIT, note the br eval framework will still pick it up
451             sourceLine.setDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
452             sourceLine.setAmount(KualiDecimal.ZERO);
453         }
454 
455         return true;
456     }
457 
458     /**
459      * This method checks to make sure that there isn't a value in both the credit and debit columns for a given accounting line.
460      * 
461      * @param creditAmount
462      * @param debitAmount
463      * @param index if -1, it's a new line, if not -1, then its an existing line
464      * @return boolean False if both the credit and debit fields have a value, true otherwise.
465      */
466     protected boolean validateCreditAndDebitAmounts(KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
467         boolean valid = false;
468         if (null != creditAmount && null != debitAmount) {
469             if (creditAmount.isNonZero() && debitAmount.isNonZero()) {
470                 // there's a value in both fields
471                 if (KFSConstants.NEGATIVE_ONE == index) { // it's a new line
472                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSConstants.DEBIT_AMOUNT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
473                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSConstants.CREDIT_AMOUNT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
474                 }
475                 else {
476                     String errorKeyPath = KFSConstants.JOURNAL_LINE_HELPER_PROPERTY_NAME + KFSConstants.SQUARE_BRACKET_LEFT + Integer.toString(index) + KFSConstants.SQUARE_BRACKET_RIGHT;
477                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
478                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
479                 }
480             }
481             else {
482                 valid = true;
483             }
484         }
485         else {
486             valid = true;
487         }
488         return valid;
489     }
490 }