001/* 002 * Copyright 2006 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 */ 016package org.kuali.ole.fp.document.web.struts; 017 018import static org.kuali.ole.sys.OLEConstants.VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME; 019import static org.kuali.ole.sys.OLEConstants.VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME; 020 021import java.sql.Date; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Map; 025 026import javax.servlet.http.HttpServletRequest; 027 028import org.apache.commons.lang.StringUtils; 029import org.kuali.ole.coa.businessobject.AccountingPeriod; 030import org.kuali.ole.coa.businessobject.ObjectCode; 031import org.kuali.ole.coa.businessobject.SubObjectCode; 032import org.kuali.ole.coa.service.AccountingPeriodService; 033import org.kuali.ole.fp.businessobject.VoucherAccountingLineHelper; 034import org.kuali.ole.fp.businessobject.VoucherAccountingLineHelperBase; 035import org.kuali.ole.fp.document.VoucherDocument; 036import org.kuali.ole.sys.OLEConstants; 037import org.kuali.ole.sys.OLEKeyConstants; 038import org.kuali.ole.sys.OLEPropertyConstants; 039import org.kuali.ole.sys.businessobject.SourceAccountingLine; 040import org.kuali.ole.sys.context.SpringContext; 041import org.kuali.ole.sys.document.AmountTotaling; 042import org.kuali.ole.sys.web.struts.KualiAccountingDocumentFormBase; 043import org.kuali.rice.core.api.datetime.DateTimeService; 044import org.kuali.rice.core.api.util.type.KualiDecimal; 045import org.kuali.rice.core.web.format.CurrencyFormatter; 046import org.kuali.rice.krad.util.GlobalVariables; 047import org.kuali.rice.krad.util.ObjectUtils; 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 */ 057public 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 (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}