View Javadoc
1   /*
2    * Copyright 2006 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.fp.document.web.struts;
17  
18  import static org.kuali.ole.sys.OLEConstants.VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME;
19  import static org.kuali.ole.sys.OLEConstants.VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME;
20  
21  import java.sql.Date;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Map;
25  
26  import javax.servlet.http.HttpServletRequest;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.kuali.ole.coa.businessobject.AccountingPeriod;
30  import org.kuali.ole.coa.businessobject.ObjectCode;
31  import org.kuali.ole.coa.businessobject.SubObjectCode;
32  import org.kuali.ole.coa.service.AccountingPeriodService;
33  import org.kuali.ole.fp.businessobject.VoucherAccountingLineHelper;
34  import org.kuali.ole.fp.businessobject.VoucherAccountingLineHelperBase;
35  import org.kuali.ole.fp.document.VoucherDocument;
36  import org.kuali.ole.sys.OLEConstants;
37  import org.kuali.ole.sys.OLEKeyConstants;
38  import org.kuali.ole.sys.OLEPropertyConstants;
39  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
40  import org.kuali.ole.sys.context.SpringContext;
41  import org.kuali.ole.sys.document.AmountTotaling;
42  import org.kuali.ole.sys.web.struts.KualiAccountingDocumentFormBase;
43  import org.kuali.rice.core.api.datetime.DateTimeService;
44  import org.kuali.rice.core.api.util.type.KualiDecimal;
45  import org.kuali.rice.core.web.format.CurrencyFormatter;
46  import org.kuali.rice.krad.util.GlobalVariables;
47  import org.kuali.rice.krad.util.ObjectUtils;
48  
49  /**
50   * This class is the Struts specific form object that works in conjunction with the pojo utilities to build the UI for Voucher
51   * Document instances. This class is unique in that it leverages a helper data structure called the
52   * <code>{@link VoucherAccountingLineHelper}</code> because Voucher documents, under some/none conditions, presents the user with
53   * a debit and credit column for amount entry. New accounting lines use specific credit and debit amount fields b/c the new line is
54   * explicitly known; however, already existing accounting lines need to exist within a list with ordering that matches the
55   * accounting lines source list.
56   */
57  public class VoucherForm extends KualiAccountingDocumentFormBase {
58      protected List accountingPeriods;
59      protected KualiDecimal newSourceLineDebit;
60      protected KualiDecimal newSourceLineCredit;
61      protected List voucherLineHelpers;
62      protected String selectedAccountingPeriod;
63  
64      /**
65       * Supplements a constructor for this voucher class
66       */
67      public VoucherForm() {
68          populateDefaultSelectedAccountingPeriod();
69          setNewSourceLineCredit(KualiDecimal.ZERO);
70          setNewSourceLineDebit(KualiDecimal.ZERO);
71          setVoucherLineHelpers(new ArrayList());
72      }
73  
74      /**
75       * sets initial selected accounting period to current period
76       */
77      public void populateDefaultSelectedAccountingPeriod() {
78          Date date = SpringContext.getBean(DateTimeService.class).getCurrentSqlDate();
79          AccountingPeriod accountingPeriod = SpringContext.getBean(AccountingPeriodService.class).getByDate(date);
80  
81          StringBuffer sb = new StringBuffer();
82          sb.append(accountingPeriod.getUniversityFiscalPeriodCode());
83          sb.append(accountingPeriod.getUniversityFiscalYear());
84  
85          setSelectedAccountingPeriod(sb.toString());
86      }
87  
88      /**
89       * Overrides the parent to call super.populate and then to call the two methods that are specific to loading the two select
90       * lists on the page. In addition, this also makes sure that the credit and debit amounts are filled in for situations where
91       * validation errors occur and the page reposts.
92       * 
93       * @see org.kuali.rice.kns.web.struts.pojo.PojoForm#populate(javax.servlet.http.HttpServletRequest)
94       */
95      @Override
96      public void populate(HttpServletRequest request) {
97          super.populate(request);
98  
99          // populate the drop downs
100         if (OLEConstants.RETURN_METHOD_TO_CALL.equals(getMethodToCall())) {
101             String selectedPeriod = (StringUtils.defaultString(request.getParameter(OLEPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE)) + StringUtils.defaultString(request.getParameter(OLEPropertyConstants.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(OLEConstants.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.krad.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, OLEConstants.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(OLEConstants.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(OLEConstants.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(OLEConstants.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 (OLEConstants.NEGATIVE_ONE == index) { // it's a new line
469                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(OLEConstants.DEBIT_AMOUNT_PROPERTY_NAME, OLEKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
470                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(OLEConstants.CREDIT_AMOUNT_PROPERTY_NAME, OLEKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
471                 }
472                 else {
473                     String errorKeyPath = OLEConstants.JOURNAL_LINE_HELPER_PROPERTY_NAME + OLEConstants.SQUARE_BRACKET_LEFT + Integer.toString(index) + OLEConstants.SQUARE_BRACKET_RIGHT;
474                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME, OLEKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
475                     GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME, OLEKeyConstants.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 }