/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.fp.document.web.struts;
import static org.kuali.kfs.sys.KFSConstants.VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME;
import static org.kuali.kfs.sys.KFSConstants.VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.AccountingPeriod;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.coa.businessobject.SubObjectCode;
import org.kuali.kfs.coa.service.AccountingPeriodService;
import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelper;
import org.kuali.kfs.fp.businessobject.VoucherAccountingLineHelperBase;
import org.kuali.kfs.fp.document.VoucherDocument;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AmountTotaling;
import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentFormBase;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.web.format.CurrencyFormatter;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* This class is the Struts specific form object that works in conjunction with the pojo utilities to build the UI for Voucher
* Document instances. This class is unique in that it leverages a helper data structure called the
* <code>{@link VoucherAccountingLineHelper}</code> because Voucher documents, under some/none conditions, presents the user with
* a debit and credit column for amount entry. New accounting lines use specific credit and debit amount fields b/c the new line is
* explicitly known; however, already existing accounting lines need to exist within a list with ordering that matches the
* accounting lines source list.
*/
public class VoucherForm extends KualiAccountingDocumentFormBase {
protected List accountingPeriods;
protected KualiDecimal newSourceLineDebit;
protected KualiDecimal newSourceLineCredit;
protected List voucherLineHelpers;
protected String selectedAccountingPeriod;
/**
* Supplements a constructor for this voucher class
*/
public VoucherForm() {
populateDefaultSelectedAccountingPeriod();
setNewSourceLineCredit(KualiDecimal.ZERO);
setNewSourceLineDebit(KualiDecimal.ZERO);
setVoucherLineHelpers(new ArrayList());
}
/**
* sets initial selected accounting period to current period
*/
public void populateDefaultSelectedAccountingPeriod() {
Date date = SpringContext.getBean(DateTimeService.class).getCurrentSqlDate();
AccountingPeriod accountingPeriod = SpringContext.getBean(AccountingPeriodService.class).getByDate(date);
StringBuffer sb = new StringBuffer();
sb.append(accountingPeriod.getUniversityFiscalPeriodCode());
sb.append(accountingPeriod.getUniversityFiscalYear());
setSelectedAccountingPeriod(sb.toString());
}
/**
* Overrides the parent to call super.populate and then to call the two methods that are specific to loading the two select
* lists on the page. In addition, this also makes sure that the credit and debit amounts are filled in for situations where
* validation errors occur and the page reposts.
*
* @see org.kuali.rice.kns.web.struts.pojo.PojoForm#populate(javax.servlet.http.HttpServletRequest)
*/
@Override
public void populate(HttpServletRequest request) {
super.populate(request);
// populate the drop downs
if (KFSConstants.RETURN_METHOD_TO_CALL.equals(getMethodToCall())) {
String selectedPeriod = (StringUtils.defaultString(request.getParameter(KFSPropertyConstants.UNIVERSITY_FISCAL_PERIOD_CODE)) + StringUtils.defaultString(request.getParameter(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR)));
if (StringUtils.isNotBlank(selectedPeriod)) {
setSelectedAccountingPeriod(selectedPeriod);
}
}
populateAccountingPeriodListForRendering();
// we don't want to do this if we are just reloading the document
if (StringUtils.isBlank(getMethodToCall()) || !getMethodToCall().equals(KFSConstants.RELOAD_METHOD_TO_CALL)) {
// make sure the amount fields are populated appropriately when in debit/credit amount mode
populateCreditAndDebitAmounts();
}
}
/**
* util method to get postingYear out of selectedAccountingPeriod
*
* @return Integer
*/
protected Integer getSelectedPostingYear() {
Integer postingYear = null;
if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
postingYear = new Integer(StringUtils.right(getSelectedAccountingPeriod(), 4));
}
return postingYear;
}
/**
* util method to get posting period code out of selectedAccountingPeriod
*
* @return String
*/
protected String getSelectedPostingPeriodCode() {
String periodCode = null;
String selectedPeriod = getSelectedAccountingPeriod();
if (StringUtils.isNotBlank(selectedPeriod)) {
periodCode = StringUtils.left(selectedPeriod, 2);
}
return periodCode;
}
/**
* Helper method to make casting easier
*
* @return VoucherDocument
*/
public VoucherDocument getVoucherDocument() {
return (VoucherDocument) getDocument();
}
/**
* Override the parent, to push the chosen accounting period and balance type down into the source accounting line object. In
* addition, check the balance type to see if it's the "External Encumbrance" balance and alter the encumbrance update code on
* the accounting line appropriately.
*
* @see org.kuali.rice.kns.web.struts.form.KualiTransactionalDocumentFormBase#populateSourceAccountingLine(org.kuali.rice.krad.bo.SourceAccountingLine)
*/
@Override
public void populateSourceAccountingLine(SourceAccountingLine sourceLine, String accountingLinePropertyName, Map parameterMap) {
super.populateSourceAccountingLine(sourceLine, accountingLinePropertyName, parameterMap);
// set the chosen accounting period into the line
String selectedAccountingPeriod = getSelectedAccountingPeriod();
if (StringUtils.isNotBlank(selectedAccountingPeriod)) {
Integer postingYear = getSelectedPostingYear();
sourceLine.setPostingYear(postingYear);
if (ObjectUtils.isNull(sourceLine.getObjectCode())) {
sourceLine.setObjectCode(new ObjectCode());
}
sourceLine.getObjectCode().setUniversityFiscalYear(postingYear);
if (ObjectUtils.isNull(sourceLine.getSubObjectCode())) {
sourceLine.setSubObjectCode(new SubObjectCode());
}
sourceLine.getSubObjectCode().setUniversityFiscalYear(postingYear);
}
}
/**
* This method retrieves the list of valid accounting periods to display.
*
* @return List
*/
public List getAccountingPeriods() {
return accountingPeriods;
}
/**
* This method sets the list of valid accounting periods to display.
*
* @param accountingPeriods
*/
public void setAccountingPeriods(List accountingPeriods) {
this.accountingPeriods = accountingPeriods;
}
/**
* This method returns the reversal date in the format MMM d, yyyy.
*
* @return String
*/
public String getFormattedReversalDate() {
return formatReversalDate(getVoucherDocument().getReversalDate());
}
/**
* This method retrieves the selectedAccountingPeriod.
*
* @return String
*/
public String getSelectedAccountingPeriod() {
return selectedAccountingPeriod;
}
/**
* @return AccountingPeriod associated with the currently selected period
*/
public AccountingPeriod getAccountingPeriod() {
AccountingPeriod period = null;
if (!StringUtils.isBlank(getSelectedAccountingPeriod())) {
period = SpringContext.getBean(AccountingPeriodService.class).getByPeriod(getSelectedPostingPeriodCode(), getSelectedPostingYear());
}
return period;
}
/**
* This method sets the selectedAccountingPeriod.
*
* @param selectedAccountingPeriod
*/
public void setSelectedAccountingPeriod(String selectedAccountingPeriod) {
this.selectedAccountingPeriod = selectedAccountingPeriod;
}
/**
* Accessor to the list of <code>{@link VoucherAccountingLineHelper}</code> instances. This method retrieves the list of
* helper line objects for the form.
*
* @return List
*/
public List getVoucherLineHelpers() {
return voucherLineHelpers;
}
/**
* This method retrieves the proper voucher helper line data structure at the passed in list index so that it matches up with
* the correct accounting line at that index.
*
* @param index
* @return VoucherAccountingLineHelper
*/
public VoucherAccountingLineHelper getVoucherLineHelper(int index) {
while (getVoucherLineHelpers().size() <= index) {
getVoucherLineHelpers().add(new VoucherAccountingLineHelperBase());
}
return (VoucherAccountingLineHelper) getVoucherLineHelpers().get(index);
}
/**
* This method sets the list of helper lines for the form.
*
* @param voucherLineHelpers
*/
public void setVoucherLineHelpers(List voucherLineHelpers) {
this.voucherLineHelpers = voucherLineHelpers;
}
/**
* This method retrieves the credit amount of the new accounting line that was added.
*
* @return KualiDecimal
*/
public KualiDecimal getNewSourceLineCredit() {
return newSourceLineCredit;
}
/**
* This method sets the credit amount of the new accounting line that was added.
*
* @param newSourceLineCredit
*/
public void setNewSourceLineCredit(KualiDecimal newSourceLineCredit) {
this.newSourceLineCredit = newSourceLineCredit;
}
/**
* This method retrieves the debit amount of the new accounting line that was added.
*
* @return KualiDecimal
*/
public KualiDecimal getNewSourceLineDebit() {
return newSourceLineDebit;
}
/**
* This method sets the debit amount of the new accounting line that was added.
*
* @param newSourceLineDebit
*/
public void setNewSourceLineDebit(KualiDecimal newSourceLineDebit) {
this.newSourceLineDebit = newSourceLineDebit;
}
/**
* This method retrieves the voucher's debit total formatted as currency.
*
* @return String
*/
public String getCurrencyFormattedDebitTotal() {
return (String) new CurrencyFormatter().format(getVoucherDocument().getDebitTotal());
}
/**
* This method retrieves the voucher's credit total formatted as currency.
*
* @return String
*/
public String getCurrencyFormattedCreditTotal() {
return (String) new CurrencyFormatter().format(getVoucherDocument().getCreditTotal());
}
/**
* This method retrieves the voucher's total formatted as currency.
*
* @return String
*/
public String getCurrencyFormattedTotal() {
return (String) new CurrencyFormatter().format(((AmountTotaling) getVoucherDocument()).getTotalDollarAmount());
}
/**
* This method retrieves all of the "open for posting" accounting periods and prepares them to be rendered in a dropdown UI
* component.
*/
public void populateAccountingPeriodListForRendering() {
// grab the list of valid accounting periods
ArrayList accountingPeriods = new ArrayList(SpringContext.getBean(AccountingPeriodService.class).getOpenAccountingPeriods());
// set into the form for rendering
setAccountingPeriods(accountingPeriods);
// set the chosen accounting period into the form
populateSelectedVoucherAccountingPeriod();
}
/**
* This method parses the accounting period value from the form and builds a basic AccountingPeriod object so that the voucher
* is properly persisted with the accounting period set for it.
*/
protected void populateSelectedVoucherAccountingPeriod() {
if (StringUtils.isNotBlank(getSelectedAccountingPeriod())) {
AccountingPeriod ap = new AccountingPeriod();
ap.setUniversityFiscalPeriodCode(getSelectedPostingPeriodCode());
ap.setUniversityFiscalYear(getSelectedPostingYear());
getFinancialDocument().setAccountingPeriod(ap);
}
}
/**
* 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
* credit, otherwise, they only need to deal with the amount field in this case we always need to update the underlying bo so
* that the debit/credit code along with the amount, is properly set.
*/
protected void populateCreditAndDebitAmounts() {
processDebitAndCreditForNewSourceLine();
processDebitAndCreditForAllSourceLines();
}
/**
* This method uses the newly entered debit and credit amounts to populate the new source line that is to be added to the
* voucher document.
*
* @return boolean True if the processing was successful, false otherwise.
*/
protected boolean processDebitAndCreditForNewSourceLine() {
// using debits and credits supplied, populate the new source accounting line's amount and debit/credit code appropriately
boolean passed = processDebitAndCreditForSourceLine(getNewSourceLine(), newSourceLineDebit, newSourceLineCredit, KFSConstants.NEGATIVE_ONE);
return passed;
}
/**
* This method iterates through all of the source accounting lines associated with the voucher doc and accounts for any changes
* to the credit and debit amounts, populate the source lines' amount and debit/credit code fields appropriately, so that they
* can be persisted accurately. This accounts for the fact that users may change the amounts and/or flip-flop the credit debit
* amounts on any accounting line after the initial add of the accounting line.
*
* @return boolean
*/
protected boolean processDebitAndCreditForAllSourceLines() {
VoucherDocument vDoc = getVoucherDocument();
// iterate through all of the source accounting lines
boolean validProcessing = true;
for (int i = 0; i < vDoc.getSourceAccountingLines().size(); i++) {
// retrieve the proper business objects from the form
SourceAccountingLine sourceLine = vDoc.getSourceAccountingLine(i);
VoucherAccountingLineHelper voucherLineHelper = getVoucherLineHelper(i);
// now process the amounts and the line
// we want to process all lines, some may be invalid b/c of dual amount values, but this method will handle
// only processing the valid ones, that way we are guaranteed that values in the valid lines carry over through the
// post and invalid ones do not alter the underlying business object
validProcessing &= processDebitAndCreditForSourceLine(sourceLine, voucherLineHelper.getDebit(), voucherLineHelper.getCredit(), i);
}
return validProcessing;
}
/**
* This method checks the debit and credit attributes passed in, figures out which one has a value, and sets the source
* accounting line's amount and debit/credit attribute appropriately. It assumes that if it finds something in the debit field,
* 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,
* 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
* value in both the credit and debit columns.
*
* @param sourceLine
* @param debitAmount
* @param creditAmount
* @param index if -1, then its a new line, if not -1 then it's an existing line
* @return boolean True if the processing was successful, false otherwise.
*/
protected boolean processDebitAndCreditForSourceLine(SourceAccountingLine sourceLine, KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
// check to make sure that the
if (!validateCreditAndDebitAmounts(debitAmount, creditAmount, index)) {
return false;
}
// check to see which amount field has a value - credit or debit field?
// and set the values of the appropriate fields
if (debitAmount != null && debitAmount.isNonZero()) { // a value entered into the debit field? if so it's a debit
// create a new instance w/out reference
KualiDecimal tmpDebitAmount = new KualiDecimal(debitAmount.toString());
sourceLine.setDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
sourceLine.setAmount(tmpDebitAmount);
}
else if (creditAmount != null && creditAmount.isNonZero()) { // assume credit, if both are set the br eval framework will
// catch it
KualiDecimal tmpCreditAmount = new KualiDecimal(creditAmount.toString());
sourceLine.setDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
sourceLine.setAmount(tmpCreditAmount);
}
else { // default to DEBIT, note the br eval framework will still pick it up
sourceLine.setDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
sourceLine.setAmount(KualiDecimal.ZERO);
}
return true;
}
/**
* This method checks to make sure that there isn't a value in both the credit and debit columns for a given accounting line.
*
* @param creditAmount
* @param debitAmount
* @param index if -1, it's a new line, if not -1, then its an existing line
* @return boolean False if both the credit and debit fields have a value, true otherwise.
*/
protected boolean validateCreditAndDebitAmounts(KualiDecimal debitAmount, KualiDecimal creditAmount, int index) {
boolean valid = false;
if (null != creditAmount && null != debitAmount) {
if (creditAmount.isNonZero() && debitAmount.isNonZero()) {
// there's a value in both fields
if (KFSConstants.NEGATIVE_ONE == index) { // it's a new line
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSConstants.DEBIT_AMOUNT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSConstants.CREDIT_AMOUNT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
}
else {
String errorKeyPath = KFSConstants.JOURNAL_LINE_HELPER_PROPERTY_NAME + KFSConstants.SQUARE_BRACKET_LEFT + Integer.toString(index) + KFSConstants.SQUARE_BRACKET_RIGHT;
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_DEBIT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorKeyPath + VOUCHER_LINE_HELPER_CREDIT_PROPERTY_NAME, KFSKeyConstants.ERROR_DOCUMENT_JV_AMOUNTS_IN_CREDIT_AND_DEBIT_FIELDS);
}
}
else {
valid = true;
}
}
else {
valid = true;
}
return valid;
}
}