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 }