/*
* 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.module.ld.document.web.struts;
import static org.kuali.kfs.sys.KFSKeyConstants.ERROR_ZERO_AMOUNT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.kuali.kfs.module.ld.LaborConstants;
import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine;
import org.kuali.kfs.module.ld.businessobject.LaborAccountingLineOverride;
import org.kuali.kfs.module.ld.businessobject.LedgerBalance;
import org.kuali.kfs.module.ld.document.LaborExpenseTransferDocumentBase;
import org.kuali.kfs.sys.KFSConstants;
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.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent;
import org.kuali.kfs.sys.service.SegmentedLookupResultsService;
import org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
import org.kuali.rice.kns.web.struts.form.KualiForm;
import org.kuali.rice.krad.bo.PersistableBusinessObject;
import org.kuali.rice.krad.document.TransactionalDocument;
import org.kuali.rice.krad.rules.rule.event.KualiDocumentEventBase;
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.UrlFactory;
/**
* Base Struts Action class for Benefit Expense Transfer Document.
*/
public class ExpenseTransferDocumentActionBase extends KualiAccountingDocumentActionBase {
protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ExpenseTransferDocumentActionBase.class);
/**
* @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#performBalanceInquiryForSourceLine(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward performBalanceInquiryForSourceLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ExpenseTransferAccountingLine line = (ExpenseTransferAccountingLine)this.getSourceAccountingLine(form, request);
line.setPostingYear(line.getPayrollEndDateFiscalYear());
return performBalanceInquiryForAccountingLine(mapping, form, request, line);
}
/**
* @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#performBalanceInquiryForTargetLine(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward performBalanceInquiryForTargetLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ExpenseTransferAccountingLine line = (ExpenseTransferAccountingLine)this.getTargetAccountingLine(form, request);
line.setPostingYear(line.getPayrollEndDateFiscalYear());
return performBalanceInquiryForAccountingLine(mapping, form, request, line);
}
/**
* Takes care of storing the action form in the user session and forwarding to the balance inquiry lookup action.
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
* @throws Exception
*/
public ActionForward performBalanceInquiryLookup(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ExpenseTransferDocumentFormBase financialDocumentForm = (ExpenseTransferDocumentFormBase) form;
// when we return from the lookup, our next request's method to call is going to be refresh
financialDocumentForm.registerEditableProperty(KRADConstants.DISPATCH_REQUEST_PARAMETER);
TransactionalDocument document = financialDocumentForm.getTransactionalDocument();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
// parse out the important strings from our methodToCall parameter
String fullParameter = (String) request.getAttribute(KFSConstants.METHOD_TO_CALL_ATTRIBUTE);
// parse out business object class name for lookup
String boClassName = StringUtils.substringBetween(fullParameter, KFSConstants.METHOD_TO_CALL_BOPARM_LEFT_DEL, KFSConstants.METHOD_TO_CALL_BOPARM_RIGHT_DEL);
if (StringUtils.isBlank(boClassName)) {
throw new RuntimeException("Illegal call to perform lookup, no business object class name specified.");
}
// build the parameters for the lookup url
Properties parameters = new Properties();
String conversionFields = StringUtils.substringBetween(fullParameter, KFSConstants.METHOD_TO_CALL_PARM1_LEFT_DEL, KFSConstants.METHOD_TO_CALL_PARM1_RIGHT_DEL);
if (StringUtils.isNotBlank(conversionFields)) {
parameters.put(KFSConstants.CONVERSION_FIELDS_PARAMETER, conversionFields);
}
// pass values from form that should be pre-populated on lookup search
String parameterFields = StringUtils.substringBetween(fullParameter, KFSConstants.METHOD_TO_CALL_PARM2_LEFT_DEL, KFSConstants.METHOD_TO_CALL_PARM2_RIGHT_DEL);
if (StringUtils.isNotBlank(parameterFields)) {
String[] lookupParams = parameterFields.split(KFSConstants.FIELD_CONVERSIONS_SEPERATOR);
for (int i = 0; i < lookupParams.length; i++) {
String[] keyValue = lookupParams[i].split(KFSConstants.FIELD_CONVERSION_PAIR_SEPERATOR);
// hard-coded passed value
if (StringUtils.contains(keyValue[0], "'")) {
parameters.put(keyValue[1], StringUtils.replace(keyValue[0], "'", ""));
}
// passed value should come from property
else if (StringUtils.isNotBlank(request.getParameter(keyValue[0]))) {
parameters.put(keyValue[1], request.getParameter(keyValue[0]));
}
}
}
// grab whether or not the "return value" link should be hidden or not
String hideReturnLink = StringUtils.substringBetween(fullParameter, KFSConstants.METHOD_TO_CALL_PARM3_LEFT_DEL, KFSConstants.METHOD_TO_CALL_PARM3_RIGHT_DEL);
if (StringUtils.isNotBlank(hideReturnLink)) {
parameters.put(KFSConstants.HIDE_LOOKUP_RETURN_LINK, hideReturnLink);
}
// anchor, if it exists
if (form instanceof KualiForm && StringUtils.isNotEmpty(((KualiForm) form).getAnchor())) {
parameters.put(KFSConstants.LOOKUP_ANCHOR, ((KualiForm) form).getAnchor());
}
// determine what the action path is
String actionPath = StringUtils.substringBetween(fullParameter, KFSConstants.METHOD_TO_CALL_PARM4_LEFT_DEL, KFSConstants.METHOD_TO_CALL_PARM4_RIGHT_DEL);
if (StringUtils.isBlank(actionPath)) {
throw new IllegalStateException("The \"actionPath\" attribute is an expected parameter for the <kul:balanceInquiryLookup> tag - it " + "should never be blank.");
}
// now add required parameters
parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, "search");
parameters.put(KFSConstants.DOC_FORM_KEY, GlobalVariables.getUserSession().addObjectWithGeneratedKey(form));
parameters.put(KFSConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, boClassName);
parameters.put(KFSConstants.RETURN_LOCATION_PARAMETER, basePath + mapping.getPath() + ".do");
//parameters.put(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE.toString());
String lookupUrl = UrlFactory.parameterizeUrl(basePath + "/" + actionPath, parameters);
return new ActionForward(lookupUrl, true);
}
/**
* Populates the lines of the ST or BT document from a balance lookup. First, the data must be retrieved based on the selected
* ids persisted from the framework. The basic steps are: 1) Retrieve selected (row) ids that were persisted 2) Each id has
* form: {db object id}.{period name}.{line amount} 3) Retrieve the balance records associated with the object ids 4)Build an
* accounting line from the retrieved balance record, using parsed period name as the pay period, and parsed amount as the new
* line amount. 5) Call insertAccountingLine
*
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#refresh(ActionMapping, ActionForm, HttpServletRequest,
* HttpServletResponse)
*/
@Override
public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
super.refresh(mapping, form, request, response);
ExpenseTransferDocumentFormBase expenseTransferDocumentForm = (ExpenseTransferDocumentFormBase) form;
Collection<PersistableBusinessObject> rawValues = null;
Map<String, Set<String>> segmentedSelection = new HashMap<String, Set<String>>();
if (StringUtils.equals(KFSConstants.MULTIPLE_VALUE, expenseTransferDocumentForm.getRefreshCaller())) {
String lookupResultsSequenceNumber = expenseTransferDocumentForm.getLookupResultsSequenceNumber();
if (StringUtils.isNotBlank(lookupResultsSequenceNumber)) {
// actually returning from a multiple value lookup
Set<String> selectedIds = getSegmentedLookupResultsService().retrieveSetOfSelectedObjectIds(lookupResultsSequenceNumber, GlobalVariables.getUserSession().getPerson().getPrincipalId());
for (String selectedId : selectedIds) {
String selectedObjId = StringUtils.substringBefore(selectedId, ".");
String selectedMonthData = StringUtils.substringAfter(selectedId, ".");
if (!segmentedSelection.containsKey(selectedObjId)) {
segmentedSelection.put(selectedObjId, new HashSet<String>());
}
segmentedSelection.get(selectedObjId).add(selectedMonthData);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Asking segmentation service for object ids " + segmentedSelection.keySet());
}
rawValues = getSegmentedLookupResultsService().retrieveSelectedResultBOs(lookupResultsSequenceNumber, segmentedSelection.keySet(), LedgerBalance.class, GlobalVariables.getUserSession().getPerson().getPrincipalId());
}
if (rawValues != null) {
boolean isFirstBalance = true;
for (PersistableBusinessObject bo : rawValues) {
// reset the form with the first leadge balance
if (isFirstBalance) {
resetLookupFields(expenseTransferDocumentForm, (LedgerBalance) bo);
isFirstBalance = false;
}
for (String selectedMonthData : segmentedSelection.get(bo.getObjectId())) {
String selectedPeriodName = StringUtils.substringBefore(selectedMonthData, ".");
String selectedPeriodAmount = StringUtils.substringAfter(selectedMonthData, ".");
if (LaborConstants.periodCodeMapping.containsKey(selectedPeriodName)) {
String periodCode = LaborConstants.periodCodeMapping.get(selectedPeriodName);
ExpenseTransferAccountingLine line = (ExpenseTransferAccountingLine) expenseTransferDocumentForm.getFinancialDocument().getSourceAccountingLineClass().newInstance();
LaborExpenseTransferDocumentBase financialDocument = (LaborExpenseTransferDocumentBase) expenseTransferDocumentForm.getDocument();
try {
KualiDecimal lineAmount = (new KualiDecimal(selectedPeriodAmount)).divide(new KualiDecimal(100));
// Notice that user tried to import an accounting line which has Zero amount
if (KualiDecimal.ZERO.compareTo(lineAmount) == 0) {
GlobalVariables.getMessageMap().putError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES, ERROR_ZERO_AMOUNT, "an accounting line");
}
else {
buildAccountingLineFromLedgerBalance((LedgerBalance) bo, line, lineAmount, periodCode);
// SpringContext.getBean(KualiRuleService.class).applyRules(new
// AddAccountingLineEvent(KFSConstants.NEW_SOURCE_ACCT_LINE_PROPERTY_NAME, financialDocument,
// line));
SpringContext.getBean(PersistenceService.class).retrieveNonKeyFields(line);
insertAccountingLine(true, expenseTransferDocumentForm, line);
updateAccountOverrideCode(financialDocument, line);
processAccountingLineOverrides(line);
}
}
catch (Exception e) {
// No way to recover gracefully, so throw it back as a RuntimeException
throw new RuntimeException(e);
}
}
}
}
Collections.sort((List<Comparable>) expenseTransferDocumentForm.getFinancialDocument().getSourceAccountingLines());
}
}
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Overload the method in order to have balance importing section be populated with the last search criteria
*
* @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
*/
@Override
protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
super.loadDocument(kualiDocumentFormBase);
ExpenseTransferDocumentFormBase expenseTransferDocumentForm = (ExpenseTransferDocumentFormBase) kualiDocumentFormBase;
expenseTransferDocumentForm.populateSearchFields();
}
/**
* This method copies all accounting lines from financial document form if they pass validation rules
*
* @param mapping
* @param form
* @param request
* @param response
* @return
* @throws Exception
*/
public ActionForward copyAllAccountingLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ExpenseTransferDocumentFormBase financialDocumentForm = (ExpenseTransferDocumentFormBase) form;
for (Object line : financialDocumentForm.getFinancialDocument().getSourceAccountingLines()) {
ExpenseTransferAccountingLine to = (ExpenseTransferAccountingLine) financialDocumentForm.getFinancialDocument().getTargetAccountingLineClass().newInstance();
copyAccountingLine((ExpenseTransferAccountingLine) line, to);
boolean rulePassed = runRule(new AddAccountingLineEvent(KFSConstants.NEW_TARGET_ACCT_LINE_PROPERTY_NAME, financialDocumentForm.getDocument(), to));
// if the rule evaluation passed, let's add it
if (rulePassed) {
// add accountingLine
SpringContext.getBean(PersistenceService.class).retrieveNonKeyFields(line);
insertAccountingLine(false, financialDocumentForm, to);
}
processAccountingLineOverrides(to);
}
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Delete all source accounting lines
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionMapping
* @throws Exception
*/
public ActionForward deleteAllSourceAccountingLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ExpenseTransferDocumentFormBase financialDocumentForm = (ExpenseTransferDocumentFormBase) form;
financialDocumentForm.getFinancialDocument().setSourceAccountingLines(new ArrayList());
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Delete all target accounting lines
*
* @param mapping
* @param form
* @param request
* @param response
* @return ActionMapping
* @throws Exception
*/
public ActionForward deleteAllTargetAccountingLines(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ExpenseTransferDocumentFormBase financialDocumentForm = (ExpenseTransferDocumentFormBase) form;
financialDocumentForm.getFinancialDocument().setTargetAccountingLines(new ArrayList());
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Copy a single accounting line
*
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#copyAccountingLine(ActionMapping, ActionForm,
* HttpServletRequest, HttpServletResponse)
*/
public ActionForward copyAccountingLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
ExpenseTransferDocumentFormBase financialDocumentForm = (ExpenseTransferDocumentFormBase) form;
LaborExpenseTransferDocumentBase financialDocument = (LaborExpenseTransferDocumentBase) financialDocumentForm.getDocument();
int index = getSelectedLine(request);
ExpenseTransferAccountingLine line = (ExpenseTransferAccountingLine) financialDocumentForm.getFinancialDocument().getTargetAccountingLineClass().newInstance();
copyAccountingLine((ExpenseTransferAccountingLine) financialDocument.getSourceAccountingLine(index), line);
boolean rulePassed = runRule(new AddAccountingLineEvent(KFSConstants.NEW_TARGET_ACCT_LINE_PROPERTY_NAME, financialDocumentForm.getDocument(), line));
// if the rule evaluation passed, let's add it
// KFSMI-9133 : allowing the line to insert even on a rule failure since the user has
// no ability to make changes since the source is read only
// This will then depend on document final edits catching any problems.
//if (rulePassed) {
// add accountingLine
SpringContext.getBean(PersistenceService.class).retrieveNonKeyFields(line);
insertAccountingLine(false, financialDocumentForm, line);
//}
processAccountingLineOverrides(line);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Reset the lookup fields in the given expense transfer form with the given ledger balance
*
* @param expenseTransferDocumentForm the given expense transfer form
* @param the given ledger balance
*/
protected void resetLookupFields(ExpenseTransferDocumentFormBase expenseTransferDocumentForm, LedgerBalance balance) {
expenseTransferDocumentForm.setUniversityFiscalYear(balance.getUniversityFiscalYear());
}
/**
* Copies content from one accounting line to the other. Ignores Source or Target information.
*
* @param source line to copy from
* @param target new line to copy data to
*/
protected void copyAccountingLine(ExpenseTransferAccountingLine source, ExpenseTransferAccountingLine target) {
target.setChartOfAccountsCode(source.getChartOfAccountsCode());
target.setAccountNumber(source.getAccountNumber());
target.setSubAccountNumber(source.getSubAccountNumber());
target.setPostingYear(source.getPostingYear());
target.setPayrollEndDateFiscalYear(source.getPayrollEndDateFiscalYear());
target.setFinancialObjectCode(source.getFinancialObjectCode());
target.setFinancialSubObjectCode(source.getFinancialSubObjectCode());
target.setBalanceTypeCode(source.getBalanceTypeCode());
target.setPositionNumber(source.getPositionNumber());
target.setAmount(source.getAmount());
target.setEmplid(source.getEmplid());
target.setPayrollEndDateFiscalPeriodCode(source.getPayrollEndDateFiscalPeriodCode());
// KFSMI-9133 : we should not be copying the override codes between source and target lines
//target.setOverrideCode(source.getOverrideCode());
target.setPayrollTotalHours(source.getPayrollTotalHours());
}
/**
* Translates <code>{@link LedgerBalance}</code> data into an <code>{@link ExpenseTransferAccountingLine}</code>
*
* @param bo <code>{@link LedgerBalance}</code> instance
* @param line <code>{@link ExpenseTransferAccountingLine}</code> to copy data to
*/
protected void buildAccountingLineFromLedgerBalance(LedgerBalance ledgerBalance, ExpenseTransferAccountingLine line, KualiDecimal amount, String periodCode) {
line.setChartOfAccountsCode(ledgerBalance.getChartOfAccountsCode());
line.setAccountNumber(ledgerBalance.getAccountNumber());
if (!KFSConstants.getDashSubAccountNumber().equals(ledgerBalance.getSubAccountNumber())) {
line.setSubAccountNumber(ledgerBalance.getSubAccountNumber());
}
line.setPostingYear(ledgerBalance.getUniversityFiscalYear());
line.setPayrollEndDateFiscalYear(ledgerBalance.getUniversityFiscalYear());
line.setFinancialObjectCode(ledgerBalance.getFinancialObjectCode());
if (!KFSConstants.getDashFinancialSubObjectCode().equals(ledgerBalance.getFinancialSubObjectCode())) {
line.setFinancialSubObjectCode(ledgerBalance.getFinancialSubObjectCode());
}
line.setBalanceTypeCode(ledgerBalance.getFinancialBalanceTypeCode());
line.setPositionNumber(ledgerBalance.getPositionNumber());
line.setAmount(amount);
line.setEmplid(ledgerBalance.getEmplid());
line.setPayrollEndDateFiscalPeriodCode(periodCode);
}
/**
* Processes accounting line overrides for output to JSP
*
* @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#processAccountingLineOverrides(java.util.List)
*/
@Override
protected void processAccountingLineOverrides(List accountingLines) {
processAccountingLineOverrides(null,accountingLines);
}
/**
*
* @see org.kuali.kfs.sys.web.struts.KualiAccountingDocumentActionBase#processAccountingLineOverrides(org.kuali.kfs.sys.document.AccountingDocument, java.util.List)
*/
@Override
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);
LaborAccountingLineOverride.processForOutput(financialDocument, line);
}
}
}
/**
* For given accounting line, set the corresponding override code
*
* @param line accounting line
*/
protected void updateAccountOverrideCode(AccountingDocument accountingDocument, ExpenseTransferAccountingLine line) {
AccountingLineOverride override = LaborAccountingLineOverride.determineNeededOverrides(accountingDocument, line);
line.setOverrideCode(override.getCode());
}
/**
* Executes for the given event. This is more of a convenience method.
*
* @param event to run the rules for
* @return true if rule passes
*/
protected boolean runRule(KualiDocumentEventBase event) {
// check any business rules
boolean rulePassed = SpringContext.getBean(KualiRuleService.class).applyRules(event);
return rulePassed;
}
/**
* Get the BO class name of the set of lookup results
*
* @param expenseTransferDocumentForm the Struts form for expense transfer document
* @return the BO class name of the set of lookup results
*/
protected String getLookupResultsBOClassName(ExpenseTransferDocumentFormBase expenseTransferDocumentForm) {
return expenseTransferDocumentForm.getLookupResultsBOClassName();
}
/**
* @return SegmentedLookupResultsService
*/
protected SegmentedLookupResultsService getSegmentedLookupResultsService() {
return SpringContext.getBean(SegmentedLookupResultsService.class);
}
}