/*
* 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.sys.web.struts;
import static org.kuali.kfs.sys.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_INVALID_ACCOUNT;
import static org.kuali.kfs.sys.KFSKeyConstants.ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_REQUIRED;
import static org.kuali.kfs.sys.KFSKeyConstants.ERROR_REQUIRED;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.upload.FormFile;
import org.kuali.kfs.fp.businessobject.SalesTax;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.AccountingLineOverride;
import org.kuali.kfs.sys.businessobject.AccountingLineParser;
import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.AmountTotaling;
import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent;
import org.kuali.kfs.sys.document.validation.event.DeleteAccountingLineEvent;
import org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBaseConstants.APPLICATION_PARAMETER;
import org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase;
import org.kuali.kfs.sys.exception.AccountingLineParserException;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.parameter.ParameterEvaluator;
import org.kuali.rice.core.api.parameter.ParameterEvaluatorService;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.kns.service.DictionaryValidationService;
import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.KualiRuleService;
import org.kuali.rice.krad.service.PersistenceService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADConstants;
import org.kuali.rice.krad.util.ObjectUtils;
import org.kuali.rice.krad.util.UrlFactory;
/**
* This class handles UI actions for all shared methods of financial documents.
*/
public class KualiAccountingDocumentActionBase extends FinancialSystemTransactionalDocumentActionBase {
protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(KualiAccountingDocumentActionBase.class);
/**
* Adds check for accountingLine updates, generates and dispatches any events caused by such updates
*
* @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm,
* javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase transForm = (KualiAccountingDocumentFormBase) form;
// handle changes to accountingLines
if (transForm.hasDocumentId()) {
AccountingDocument financialDocument = (AccountingDocument) transForm.getDocument();
processAccountingLines(financialDocument, transForm, KFSConstants.SOURCE);
processAccountingLines(financialDocument, transForm, KFSConstants.TARGET);
}
// This is after a potential handleUpdate(), to display automatically cleared overrides following a route or save.
processAccountingLineOverrides(transForm);
// proceed as usual
ActionForward result = super.execute(mapping, form, request, response);
return result;
}
/**
* All document-load operations get routed through here
*
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
*/
@Override
protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
super.loadDocument(kualiDocumentFormBase);
KualiAccountingDocumentFormBase tform = (KualiAccountingDocumentFormBase) kualiDocumentFormBase;
// clear out the new accounting line holders
tform.setNewSourceLine(null);
tform.setNewTargetLine(null);
processAccountingLineOverrides(tform);
}
/**
* Needed to override this to keep from losing Sales Tax information
*
* @see org.kuali.rice.kns.web.struts.action.KualiAction#refresh(org.apache.struts.action.ActionMapping,
* org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
super.refresh(mapping, form, request, response);
refreshSalesTaxInfo(form);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Needed to override this to keep from losing Sales Tax information
*
* @see org.kuali.rice.kns.web.struts.action.KualiAction#toggleTab(org.apache.struts.action.ActionMapping,
* org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward toggleTab(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
super.toggleTab(mapping, form, request, response);
refreshSalesTaxInfo(form);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
// Set of actions for which updateEvents should be generated
protected static final Set UPDATE_EVENT_ACTIONS;
static {
String[] updateEventActions = { KFSConstants.SAVE_METHOD, KFSConstants.ROUTE_METHOD, KFSConstants.APPROVE_METHOD, KFSConstants.BLANKET_APPROVE_METHOD };
UPDATE_EVENT_ACTIONS = new HashSet();
for (int i = 0; i < updateEventActions.length; ++i) {
UPDATE_EVENT_ACTIONS.add(updateEventActions[i]);
}
}
/**
* @param transForm
*/
protected void processAccountingLineOverrides(KualiAccountingDocumentFormBase transForm) {
processAccountingLineOverrides(transForm.getNewSourceLine());
processAccountingLineOverrides(transForm.getNewTargetLine());
if (transForm.hasDocumentId()) {
AccountingDocument financialDocument = (AccountingDocument) transForm.getDocument();
processAccountingLineOverrides(financialDocument,financialDocument.getSourceAccountingLines());
processAccountingLineOverrides(financialDocument,financialDocument.getTargetAccountingLines());
}
}
/**
* @param line
*/
protected void processAccountingLineOverrides(AccountingLine line) {
processAccountingLineOverrides(Arrays.asList(new AccountingLine[] { line }));
}
protected void processAccountingLineOverrides(List accountingLines) {
processAccountingLineOverrides(null,accountingLines);
}
/**
* @param accountingLines
*/
protected void processAccountingLineOverrides(AccountingDocument financialDocument ,List accountingLines) {
if (!accountingLines.isEmpty()) {
for (Iterator i = accountingLines.iterator(); i.hasNext();) {
AccountingLine line = (AccountingLine) i.next();
// line.refreshReferenceObject("account");
SpringContext.getBean(PersistenceService.class).retrieveReferenceObjects(line, AccountingLineOverride.REFRESH_FIELDS);
AccountingLineOverride.processForOutput(financialDocument,line);
}
}
}
/**
* @param transDoc
* @param transForm
* @param lineSet
*/
protected void processAccountingLines(AccountingDocument transDoc, KualiAccountingDocumentFormBase transForm, String lineSet) {
// figure out which set of lines we're looking at
List formLines;
String pathPrefix;
boolean source;
if (lineSet.equals(KFSConstants.SOURCE)) {
formLines = transDoc.getSourceAccountingLines();
pathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_SOURCE_ACCT_LINE_PROPERTY_NAME;
source = true;
}
else {
formLines = transDoc.getTargetAccountingLines();
pathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_TARGET_ACCT_LINE_PROPERTY_NAME;
source = false;
}
// find and process corresponding form and baselines
int index = 0;
for (Iterator i = formLines.iterator(); i.hasNext(); index++) {
AccountingLine formLine = (AccountingLine) i.next();
// update sales tax required attribute for view
// handleSalesTaxRequired(transDoc, formLine, source, false, index);
checkSalesTax(transDoc, formLine, source, false, index);
}
}
/**
* This method will remove a TargetAccountingLine from a FinancialDocument. This assumes that the user presses the delete button
* for a specific accounting line on the document and that the document is represented by a FinancialDocumentFormBase.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward deleteTargetLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;
int deleteIndex = getLineToDelete(request);
String errorPath = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_TARGET_ACCT_LINE_PROPERTY_NAME + "[" + deleteIndex + "]";
boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new DeleteAccountingLineEvent(errorPath, financialDocumentForm.getDocument(), ((AccountingDocument) financialDocumentForm.getDocument()).getTargetAccountingLine(deleteIndex), false));
// if the rule evaluation passed, let's delete it
if (rulePassed) {
deleteAccountingLine(false, financialDocumentForm, deleteIndex);
}
else {
String[] errorParams = new String[] { "target", Integer.toString(deleteIndex + 1) };
GlobalVariables.getMessageMap().putError(errorPath, KFSKeyConstants.ERROR_ACCOUNTINGLINE_DELETERULE_INVALIDACCOUNT, errorParams);
}
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* This method will remove a SourceAccountingLine from a FinancialDocument. This assumes that the user presses the delete button
* for a specific accounting line on the document and that the document is represented by a FinancialDocumentFormBase.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward deleteSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;
int deleteIndex = getLineToDelete(request);
String errorPath = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.EXISTING_SOURCE_ACCT_LINE_PROPERTY_NAME + "[" + deleteIndex + "]";
boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(new DeleteAccountingLineEvent(errorPath, financialDocumentForm.getDocument(), ((AccountingDocument) financialDocumentForm.getDocument()).getSourceAccountingLine(deleteIndex), false));
// if the rule evaluation passed, let's delete it
if (rulePassed) {
deleteAccountingLine(true, financialDocumentForm, deleteIndex);
}
else {
String[] errorParams = new String[] { "source", Integer.toString(deleteIndex + 1) };
GlobalVariables.getMessageMap().putError(errorPath, KFSKeyConstants.ERROR_ACCOUNTINGLINE_DELETERULE_INVALIDACCOUNT, errorParams);
}
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Deletes the source or target accountingLine with the given index from the given form. Assumes that the rule- and form-
* validation have already occurred.
*
* @param isSource
* @param financialDocumentForm
* @param deleteIndex
*/
protected void deleteAccountingLine(boolean isSource, KualiAccountingDocumentFormBase financialDocumentForm, int deleteIndex) {
if (isSource) {
// remove from document
financialDocumentForm.getFinancialDocument().getSourceAccountingLines().remove(deleteIndex);
}
else {
// remove from document
financialDocumentForm.getFinancialDocument().getTargetAccountingLines().remove(deleteIndex);
}
// update the doc total
AccountingDocument tdoc = (AccountingDocument) financialDocumentForm.getDocument();
if (tdoc instanceof AmountTotaling) {
((FinancialSystemDocumentHeader) financialDocumentForm.getDocument().getDocumentHeader()).setFinancialDocumentTotalAmount(((AmountTotaling) tdoc).getTotalDollarAmount());
}
}
/**
* This action executes a call to upload CSV accounting line values as TargetAccountingLines for a given transactional document.
* The "uploadAccountingLines()" method handles the multi-part request.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward uploadTargetLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
// call method that sourceform and destination list
uploadAccountingLines(false, form);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* This action executes a call to upload CSV accounting line values as SourceAccountingLines for a given transactional document.
* The "uploadAccountingLines()" method handles the multi-part request.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws FileNotFoundException
* @throws IOException
*/
public ActionForward uploadSourceLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws FileNotFoundException, IOException {
LOG.info("Uploading source accounting lines");
// call method that sourceform and destination list
uploadAccountingLines(true, form);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* This method determines whether we are uploading source or target lines, and then calls uploadAccountingLines directly on the
* document object. This method handles retrieving the actual upload file as an input stream into the document.
*
* @param isSource
* @param form
* @throws FileNotFoundException
* @throws IOException
*/
protected void uploadAccountingLines(boolean isSource, ActionForm form) throws FileNotFoundException, IOException {
KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
List importedLines = null;
AccountingDocument financialDocument = tmpForm.getFinancialDocument();
AccountingLineParser accountingLineParser = financialDocument.getAccountingLineParser();
// import the lines
String errorPathPrefix = null;
try {
if (isSource) {
errorPathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS;
FormFile sourceFile = tmpForm.getSourceFile();
checkUploadFile(sourceFile);
importedLines = accountingLineParser.importSourceAccountingLines(sourceFile.getFileName(), sourceFile.getInputStream(), financialDocument);
}
else {
errorPathPrefix = KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS;
FormFile targetFile = tmpForm.getTargetFile();
checkUploadFile(targetFile);
importedLines = accountingLineParser.importTargetAccountingLines(targetFile.getFileName(), targetFile.getInputStream(), financialDocument);
}
}
catch (AccountingLineParserException e) {
GlobalVariables.getMessageMap().putError(errorPathPrefix, e.getErrorKey(), e.getErrorParameters());
}
// add line to list for those lines which were successfully imported
if (importedLines != null) {
for (Iterator i = importedLines.iterator(); i.hasNext();) {
AccountingLine importedLine = (AccountingLine) i.next();
insertAccountingLine(isSource, tmpForm, importedLine);
}
}
}
protected void checkUploadFile(FormFile file) {
if (file == null) {
throw new AccountingLineParserException("invalid (null) upload file", KFSKeyConstants.ERROR_UPLOADFILE_NULL);
}
}
/**
* This method will add a TargetAccountingLine to a FinancialDocument. This assumes that the user presses the add button for a
* specific accounting line on the document and that the document is represented by a FinancialDocumentFormBase. It first
* validates the line for data integrity and then checks appropriate business rules.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward insertTargetLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;
TargetAccountingLine line = financialDocumentForm.getNewTargetLine();
// populate chartOfAccountsCode from account number if accounts cant cross chart and Javascript is turned off
//SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(line);
boolean rulePassed = true;
// before we check the regular rules we need to check the sales tax rules
// TODO: Refactor rules so we no longer have to call this before a copy of the
// accountingLine
rulePassed &= checkSalesTax((AccountingDocument) financialDocumentForm.getDocument(), line, false, true, 0);
// check any business rules
rulePassed &= SpringContext.getBean(KualiRuleService.class).applyRules(new AddAccountingLineEvent(KFSConstants.NEW_TARGET_ACCT_LINE_PROPERTY_NAME, financialDocumentForm.getDocument(), line));
// if the rule evaluation passed, let's add it
if (rulePassed) {
// add accountingLine
SpringContext.getBean(PersistenceService.class).refreshAllNonUpdatingReferences(line);
insertAccountingLine(false, financialDocumentForm, line);
// clear the used newTargetLine
financialDocumentForm.setNewTargetLine(null);
}
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* This action executes an insert of a SourceAccountingLine into a document only after validating the accounting line and
* checking any appropriate business rules.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward insertSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase financialDocumentForm = (KualiAccountingDocumentFormBase) form;
SourceAccountingLine line = financialDocumentForm.getNewSourceLine();
// populate chartOfAccountsCode from account number if accounts cant cross chart and Javascript is turned off
//SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(line);
boolean rulePassed = true;
// before we check the regular rules we need to check the sales tax rules
// TODO: Refactor rules so we no longer have to call this before a copy of the
// accountingLine
rulePassed &= checkSalesTax((AccountingDocument) financialDocumentForm.getDocument(), line, true, true, 0);
// check any business rules
rulePassed &= SpringContext.getBean(KualiRuleService.class).applyRules(new AddAccountingLineEvent(KFSConstants.NEW_SOURCE_ACCT_LINE_PROPERTY_NAME, financialDocumentForm.getDocument(), line));
if (rulePassed) {
// add accountingLine
SpringContext.getBean(PersistenceService.class).refreshAllNonUpdatingReferences(line);
insertAccountingLine(true, financialDocumentForm, line);
// clear the used newTargetLine
financialDocumentForm.setNewSourceLine(null);
}
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Adds the given accountingLine to the appropriate form-related data structures.
*
* @param isSource
* @param financialDocumentForm
* @param line
*/
protected void insertAccountingLine(boolean isSource, KualiAccountingDocumentFormBase financialDocumentForm, AccountingLine line) {
AccountingDocument tdoc = financialDocumentForm.getFinancialDocument();
if (isSource) {
// add it to the document
tdoc.addSourceAccountingLine((SourceAccountingLine) line);
// add PK fields to sales tax if needed
if (line.isSalesTaxRequired()) {
populateSalesTax(line);
}
// Update the doc total
if (tdoc instanceof AmountTotaling)
((FinancialSystemDocumentHeader) financialDocumentForm.getDocument().getDocumentHeader()).setFinancialDocumentTotalAmount(((AmountTotaling) tdoc).getTotalDollarAmount());
}
else {
// add it to the document
tdoc.addTargetAccountingLine((TargetAccountingLine) line);
// add PK fields to sales tax if needed
if (line.isSalesTaxRequired()) {
populateSalesTax(line);
}
}
}
/**
* TODO: remove this method once baseline accounting lines has been removed
*/
protected List deepCopyAccountingLinesList(List originals) {
if (originals == null) {
return null;
}
List copiedLines = new ArrayList();
for (int i = 0; i < originals.size(); i++) {
copiedLines.add(ObjectUtils.deepCopy((AccountingLine) originals.get(i)));
}
return copiedLines;
}
/**
* This action changes the value of the hide field in the user interface so that when the page is rendered, the UI knows to show
* all of the labels for each of the accounting line values.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward showDetails(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
tmpForm.setHideDetails(false);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* This method is triggered when the user toggles the show/hide button to "hide" thus making the UI render without any of the
* accounting line labels/descriptions showing up underneath the values in the UI.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward hideDetails(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
tmpForm.setHideDetails(true);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Takes care of storing the action form in the User session and forwarding to the balance inquiry report menu action for a
* source accounting line.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward performBalanceInquiryForSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
SourceAccountingLine line = this.getSourceAccountingLine(form, request);
return performBalanceInquiryForAccountingLine(mapping, form, request, line);
}
/**
* Takes care of storing the action form in the User session and forwarding to the balance inquiry report menu action for a
* target accounting line.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward performBalanceInquiryForTargetLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
int lineIndex = getSelectedLine(request);
TargetAccountingLine line = this.getTargetAccountingLine(form, request);
return performBalanceInquiryForAccountingLine(mapping, form, request, line);
}
/**
* This method is a helper method that will return a source accounting line. The reason we're making it protected in here is so
* that we can override this method in some of the modules. PurchasingActionBase is one of the subclasses that will be
* overriding this, because in PurchasingActionBase, we'll need to get the source accounting line using both an item index and
* an account index.
*
* @param form
* @param request
* @param isSource
* @return
*/
protected SourceAccountingLine getSourceAccountingLine(ActionForm form, HttpServletRequest request) {
int lineIndex = getSelectedLine(request);
SourceAccountingLine line = (SourceAccountingLine) ObjectUtils.deepCopy(((KualiAccountingDocumentFormBase) form).getFinancialDocument().getSourceAccountingLine(lineIndex));
return line;
}
protected TargetAccountingLine getTargetAccountingLine(ActionForm form, HttpServletRequest request) {
int lineIndex = getSelectedLine(request);
TargetAccountingLine line = (TargetAccountingLine) ((KualiAccountingDocumentFormBase) form).getFinancialDocument().getTargetAccountingLine(lineIndex);
return line;
}
/**
* This method handles preparing all of the accounting line data so that it can be pushed up to the balance inquiries for
* populating the search criteria of each.
*
* @param mapping
* @param form
* @param request
* @param line
* @return ActionForward
*/
protected ActionForward performBalanceInquiryForAccountingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, AccountingLine line) {
// build out base path for return location
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
// build out the actual form key that will be used to retrieve the form on refresh
String callerDocFormKey = GlobalVariables.getUserSession().addObjectWithGeneratedKey(form);
// now add required parameters
Properties parameters = new Properties();
parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, KFSConstants.START_METHOD);
// need this next param b/c the lookup's return back will overwrite
// the original doc form key
parameters.put(KFSConstants.BALANCE_INQUIRY_REPORT_MENU_CALLER_DOC_FORM_KEY, callerDocFormKey);
parameters.put(KFSConstants.DOC_FORM_KEY, callerDocFormKey);
parameters.put(KFSConstants.BACK_LOCATION, basePath + mapping.getPath() + ".do");
if (line.getPostingYear() != null) {
parameters.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, line.getPostingYear().toString());
}
if (StringUtils.isNotBlank(line.getReferenceOriginCode())) {
parameters.put("referenceOriginCode", line.getReferenceOriginCode());
}
if (StringUtils.isNotBlank(line.getReferenceNumber())) {
parameters.put("referenceNumber", line.getReferenceNumber());
}
if (StringUtils.isNotBlank(line.getReferenceTypeCode())) {
parameters.put("referenceTypeCode", line.getReferenceTypeCode());
}
if (StringUtils.isNotBlank(line.getDebitCreditCode())) {
parameters.put("debitCreditCode", line.getDebitCreditCode());
}
if (StringUtils.isNotBlank(line.getChartOfAccountsCode())) {
parameters.put("chartOfAccountsCode", line.getChartOfAccountsCode());
}
if (StringUtils.isNotBlank(line.getAccountNumber())) {
parameters.put("accountNumber", line.getAccountNumber());
}
if (StringUtils.isNotBlank(line.getFinancialObjectCode())) {
parameters.put("financialObjectCode", line.getFinancialObjectCode());
}
if (StringUtils.isNotBlank(line.getSubAccountNumber())) {
parameters.put("subAccountNumber", line.getSubAccountNumber());
}
if (StringUtils.isNotBlank(line.getFinancialSubObjectCode())) {
parameters.put("financialSubObjectCode", line.getFinancialSubObjectCode());
}
if (StringUtils.isNotBlank(line.getProjectCode())) {
parameters.put("projectCode", line.getProjectCode());
}
if (StringUtils.isNotBlank(getObjectTypeCodeFromLine(line))) {
if (!StringUtils.isBlank(line.getObjectTypeCode())) {
parameters.put("objectTypeCode", line.getObjectTypeCode());
}
else {
line.refreshReferenceObject("objectCode");
parameters.put("objectTypeCode", line.getObjectCode().getFinancialObjectTypeCode());
}
}
String lookupUrl = UrlFactory.parameterizeUrl(basePath + "/" + KFSConstants.BALANCE_INQUIRY_REPORT_MENU_ACTION, parameters);
// register that we're going to come back w/ to this form w/ a refresh methodToCall
((KualiAccountingDocumentFormBase) form).registerEditableProperty(KRADConstants.DISPATCH_REQUEST_PARAMETER);
return new ActionForward(lookupUrl, true);
}
/**
* A hook so that most accounting lines - which don't have object types - can have their object type codes used in balance
* inquiries
*
* @param line the line to get the object type code from
* @return the object type code the line would use
*/
protected String getObjectTypeCodeFromLine(AccountingLine line) {
line.refreshReferenceObject("objectCode");
return line.getObjectCode().getFinancialObjectTypeCode();
}
@Override
public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
ActionForward forward = super.save(mapping, form, request, response);
// need to check on sales tax for all the accounting lines
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getSourceAccountingLines());
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getTargetAccountingLines());
return forward;
}
@Override
public ActionForward approve(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
ActionForward forward = super.approve(mapping, form, request, response);
// need to check on sales tax for all the accounting lines
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getSourceAccountingLines());
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getTargetAccountingLines());
return forward;
}
@Override
public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
// this.applyCapitalAssetInformation(tmpForm);
ActionForward forward = super.route(mapping, form, request, response);
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getSourceAccountingLines());
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getTargetAccountingLines());
return forward;
}
@Override
public ActionForward blanketApprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
KualiAccountingDocumentFormBase tmpForm = (KualiAccountingDocumentFormBase) form;
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getSourceAccountingLines());
checkSalesTaxRequiredAllLines(tmpForm, tmpForm.getFinancialDocument().getTargetAccountingLines());
ActionForward forward = super.blanketApprove(mapping, form, request, response);
return forward;
}
/**
* Encapsulate the rule check so we can call it from multiple places
*
* @param document
* @param line
* @return true if sales is either not required or it contains sales tax
*/
protected boolean checkSalesTax(AccountingDocument document, AccountingLine line, boolean source, boolean newLine, int index) {
boolean passed = true;
if (isSalesTaxRequired(document, line)) {
// then set the salesTaxRequired on the accountingLine
line.setSalesTaxRequired(true);
populateSalesTax(line);
// check to see if the sales tax info has been put in
passed &= isValidSalesTaxEntered(line, source, newLine, index);
} else {
//we do not need the saleTax bo for the line otherwise validations will fail.
line.setSalesTax(null);
}
return passed;
}
/**
* This method checks to see if this doctype needs sales tax If it does then it checks to see if the account and object code
* require sales tax If it does then it returns true. Note - this is hackish as we shouldn't have to call rules directly from
* the action class But we need to in this instance because we are copying the lines before calling rules and need a way to
* modify them before they go on
*
* @param accountingLine
* @return true if sales tax check is needed, false otherwise
*/
protected boolean isSalesTaxRequired(AccountingDocument financialDocument, AccountingLine accountingLine) {
boolean required = false;
String docType = SpringContext.getBean(DataDictionaryService.class).getDocumentTypeNameByClass(financialDocument.getClass());
// first we need to check just the doctype to see if it needs the sales tax check
ParameterService parameterService = SpringContext.getBean(ParameterService.class);
// apply the rule, see if it fails
ParameterEvaluator docTypeSalesTaxCheckEvaluator = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(KfsParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class, APPLICATION_PARAMETER.DOCTYPE_SALES_TAX_CHECK, docType);
if (docTypeSalesTaxCheckEvaluator.evaluationSucceeds()) {
required = true;
}
// second we need to check the account and object code combination to see if it needs sales tax
if (required) {
// get the object code and account
String objCd = accountingLine.getFinancialObjectCode();
String account = accountingLine.getAccountNumber();
if (!StringUtils.isEmpty(objCd) && !StringUtils.isEmpty(account)) {
String compare = account + ":" + objCd;
ParameterEvaluator salesTaxApplicableAcctAndObjectEvaluator = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(KfsParameterConstants.FINANCIAL_PROCESSING_DOCUMENT.class, APPLICATION_PARAMETER.SALES_TAX_APPLICABLE_ACCOUNTS_AND_OBJECT_CODES, compare);
if (!salesTaxApplicableAcctAndObjectEvaluator.evaluationSucceeds()) {
required = false;
}
}
else {
// the two fields are currently empty and we don't need to check yet
required = false;
}
}
return required;
}
/**
* This method checks to see if the sales tax information was put into the accounting line
*
* @param accountingLine
* @return true if entered correctly, false otherwise
*/
protected boolean isValidSalesTaxEntered(AccountingLine accountingLine, boolean source, boolean newLine, int index) {
boolean valid = true;
DictionaryValidationService dictionaryValidationService = SpringContext.getBean(DictionaryValidationService.class);
BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);
String objCd = accountingLine.getFinancialObjectCode();
String account = accountingLine.getAccountNumber();
SalesTax salesTax = accountingLine.getSalesTax();
String pathPrefix = "";
if (source && !newLine) {
pathPrefix = "document." + KFSConstants.EXISTING_SOURCE_ACCT_LINE_PROPERTY_NAME + "[" + index + "]";
}
else if (!source && !newLine) {
pathPrefix = "document." + KFSConstants.EXISTING_TARGET_ACCT_LINE_PROPERTY_NAME + "[" + index + "]";
}
else if (source && newLine) {
pathPrefix = KFSConstants.NEW_SOURCE_ACCT_LINE_PROPERTY_NAME;
}
else if (!source && newLine) {
pathPrefix = KFSConstants.NEW_TARGET_ACCT_LINE_PROPERTY_NAME;
}
GlobalVariables.getMessageMap().addToErrorPath(pathPrefix);
if (ObjectUtils.isNull(salesTax)) {
valid &= false;
GlobalVariables.getMessageMap().putError("salesTax.chartOfAccountsCode", ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_REQUIRED, account, objCd);
}
else {
if (StringUtils.isBlank(salesTax.getChartOfAccountsCode())) {
valid &= false;
GlobalVariables.getMessageMap().putError("salesTax.chartOfAccountsCode", ERROR_REQUIRED, "Chart of Accounts");
}
if (StringUtils.isBlank(salesTax.getAccountNumber())) {
valid &= false;
GlobalVariables.getMessageMap().putError("salesTax.accountNumber", ERROR_REQUIRED, "Account Number");
}
if (salesTax.getFinancialDocumentGrossSalesAmount() == null) {
valid &= false;
GlobalVariables.getMessageMap().putError("salesTax.financialDocumentGrossSalesAmount", ERROR_REQUIRED, "Gross Sales Amount");
}
if (salesTax.getFinancialDocumentTaxableSalesAmount() == null) {
valid &= false;
GlobalVariables.getMessageMap().putError("salesTax.financialDocumentTaxableSalesAmount", ERROR_REQUIRED, "Taxable Sales Amount");
}
if (salesTax.getFinancialDocumentSaleDate() == null) {
valid &= false;
GlobalVariables.getMessageMap().putError("salesTax.financialDocumentSaleDate", ERROR_REQUIRED, "Sale Date");
}
if (StringUtils.isNotBlank(salesTax.getChartOfAccountsCode()) && StringUtils.isNotBlank(salesTax.getAccountNumber())) {
if (boService.getReferenceIfExists(salesTax, "account") == null) {
valid &= false;
GlobalVariables.getMessageMap().putError("salesTax.accountNumber", ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_INVALID_ACCOUNT, salesTax.getChartOfAccountsCode(), salesTax.getAccountNumber());
}
}
if (!valid) {
GlobalVariables.getMessageMap().putError("salesTax.chartOfAccountsCode", ERROR_DOCUMENT_ACCOUNTING_LINE_SALES_TAX_REQUIRED, account, objCd);
}
}
GlobalVariables.getMessageMap().removeFromErrorPath(pathPrefix);
return valid;
}
/**
* This method removes the sales tax information from a line that no longer requires it
*
* @param accountingLine
*/
protected void removeSalesTax(AccountingLine accountingLine) {
SalesTax salesTax = accountingLine.getSalesTax();
if (ObjectUtils.isNotNull(salesTax)) {
accountingLine.setSalesTax(null);
}
}
/**
* This method checks to see if the given accounting needs sales tax and if it does it sets the salesTaxRequired variable on the
* line If it doesn't and it has it then it removes the sales tax information from the line This method is called from the
* execute() on all accounting lines that have been edited or lines that have already been added to the document, not on new
* lines
*
* @param transDoc
* @param formLine
* @param baseLine
*/
protected void handleSalesTaxRequired(AccountingDocument transDoc, AccountingLine formLine, boolean source, boolean newLine, int index) {
boolean salesTaxRequired = isSalesTaxRequired(transDoc, formLine);
if (salesTaxRequired) {
formLine.setSalesTaxRequired(true);
populateSalesTax(formLine);
}
else if (!salesTaxRequired && hasSalesTaxBeenEntered(formLine, source, newLine, index)) {
// remove it if it has been added but is no longer required
removeSalesTax(formLine);
formLine.setSalesTax(null);
}
if (!salesTaxRequired) {
formLine.setSalesTax(null);
}
}
protected boolean hasSalesTaxBeenEntered(AccountingLine accountingLine, boolean source, boolean newLine, int index) {
boolean entered = true;
String objCd = accountingLine.getFinancialObjectCode();
String account = accountingLine.getAccountNumber();
SalesTax salesTax = accountingLine.getSalesTax();
if (ObjectUtils.isNull(salesTax)) {
return false;
}
if (StringUtils.isBlank(salesTax.getChartOfAccountsCode())) {
entered &= false;
}
if (StringUtils.isBlank(salesTax.getAccountNumber())) {
entered &= false;
}
if (salesTax.getFinancialDocumentGrossSalesAmount() == null) {
entered &= false;
}
if (salesTax.getFinancialDocumentTaxableSalesAmount() == null) {
entered &= false;
}
if (salesTax.getFinancialDocumentSaleDate() == null) {
entered &= false;
}
return entered;
}
/**
* This method is called from the createDocument and processes through all the accouting lines and checks to see if they need
* sales tax fields
*
* @param kualiDocumentFormBase
* @param baselineSourceLines
*/
protected void handleSalesTaxRequiredAllLines(KualiDocumentFormBase kualiDocumentFormBase, List<AccountingLine> baselineAcctingLines) {
AccountingDocument accoutingDocument = (AccountingDocument) kualiDocumentFormBase.getDocument();
int index = 0;
for (AccountingLine accountingLine : baselineAcctingLines) {
boolean source = false;
if (accountingLine.isSourceAccountingLine()) {
source = true;
}
handleSalesTaxRequired(accoutingDocument, accountingLine, source, false, index);
index++;
}
}
protected boolean checkSalesTaxRequiredAllLines(KualiDocumentFormBase kualiDocumentFormBase, List<AccountingLine> baselineAcctingLines) {
AccountingDocument accoutingDocument = (AccountingDocument) kualiDocumentFormBase.getDocument();
boolean passed = true;
int index = 0;
for (AccountingLine accountingLine : baselineAcctingLines) {
boolean source = false;
if (accountingLine.isSourceAccountingLine()) {
source = true;
}
passed &= checkSalesTax(accoutingDocument, accountingLine, source, false, index);
index++;
}
return passed;
}
/**
* This method refreshes the sales tax fields on a refresh or tab toggle so that all the information that was there before is
* still there after a state change
*
* @param form
*/
protected void refreshSalesTaxInfo(ActionForm form) {
KualiAccountingDocumentFormBase accountingForm = (KualiAccountingDocumentFormBase) form;
AccountingDocument document = (AccountingDocument) accountingForm.getDocument();
List sourceLines = document.getSourceAccountingLines();
List targetLines = document.getTargetAccountingLines();
handleSalesTaxRequiredAllLines(accountingForm, sourceLines);
handleSalesTaxRequiredAllLines(accountingForm, targetLines);
AccountingLine newTargetLine = accountingForm.getNewTargetLine();
AccountingLine newSourceLine = accountingForm.getNewSourceLine();
if (newTargetLine != null) {
handleSalesTaxRequired(document, newTargetLine, false, true, 0);
}
if (newSourceLine != null) {
handleSalesTaxRequired(document, newSourceLine, true, true, 0);
}
}
/**
* This method populates the sales tax for a given accounting line with the appropriate primary key fields from the accounting
* line since OJB won't do it automatically for us
*
* @param line
*/
protected void populateSalesTax(AccountingLine line) {
SalesTax salesTax = line.getSalesTax();
if (ObjectUtils.isNotNull(salesTax)) {
salesTax.setDocumentNumber(line.getDocumentNumber());
salesTax.setFinancialDocumentLineTypeCode(line.getFinancialDocumentLineTypeCode());
salesTax.setFinancialDocumentLineNumber(line.getSequenceNumber());
}
}
@Override
public ActionForward performLookup(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
// parse out the business object name from our methodToCall parameter
String fullParameter = (String) request.getAttribute(KFSConstants.METHOD_TO_CALL_ATTRIBUTE);
String boClassName = StringUtils.substringBetween(fullParameter, KFSConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, KFSConstants.METHOD_TO_CALL_BOPARM_RIGHT_DEL);
if (!StringUtils.equals(boClassName, GeneralLedgerPendingEntry.class.getName())) {
return super.performLookup(mapping, form, request, response);
}
String path = super.performLookup(mapping, form, request, response).getPath();
path = path.replaceFirst(KFSConstants.LOOKUP_ACTION, KFSConstants.GL_MODIFIED_INQUIRY_ACTION);
return new ActionForward(path, true);
}
}