/* * 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.tem.document.validation.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.fp.businessobject.TravelCompanyCode; import org.kuali.kfs.module.tem.TemConstants; import org.kuali.kfs.module.tem.TemKeyConstants; import org.kuali.kfs.module.tem.TemPropertyConstants; import org.kuali.kfs.module.tem.TemPropertyConstants.TravelAuthorizationFields; import org.kuali.kfs.module.tem.TemWorkflowConstants; import org.kuali.kfs.module.tem.businessobject.AccountingDistribution; import org.kuali.kfs.module.tem.businessobject.ActualExpense; import org.kuali.kfs.module.tem.businessobject.TemExpense; import org.kuali.kfs.module.tem.businessobject.TemSourceAccountingLine; import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument; import org.kuali.kfs.module.tem.document.TravelDocument; import org.kuali.kfs.module.tem.document.service.TravelDocumentService; import org.kuali.kfs.module.tem.document.web.bean.AccountingLineDistributionKey; import org.kuali.kfs.module.tem.service.AccountingDistributionService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.document.validation.GenericValidation; import org.kuali.kfs.sys.document.validation.event.AccountingLineEvent; import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent; import org.kuali.kfs.sys.document.validation.event.UpdateAccountingLineEvent; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.krad.bo.Note; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADPropertyConstants; import org.kuali.rice.krad.util.ObjectUtils; public class TemAccountingLineAllowedObjectCodeValidation extends GenericValidation { protected TravelDocumentService travelDocumentService; protected AccountingDistributionService accountingDistributionService; protected ParameterService parameterService; protected BusinessObjectService businessObjectService; /** * @see org.kuali.kfs.sys.document.validation.Validation#validate(org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent) */ @SuppressWarnings("rawtypes") @Override public boolean validate(AttributedDocumentEvent event) { final Person currentUser = GlobalVariables.getUserSession().getPerson(); TemSourceAccountingLine line = null; if (event instanceof UpdateAccountingLineEvent){ line = (TemSourceAccountingLine) ((UpdateAccountingLineEvent)event).getUpdatedAccountingLine(); } else{ line = (TemSourceAccountingLine) ((AccountingLineEvent)event).getAccountingLine(); } List<String> holdErrors = new ArrayList<String>(); holdErrors.addAll(GlobalVariables.getMessageMap().getErrorPath()); GlobalVariables.getMessageMap().clearErrorPath(); TravelDocument travelDocument = (TravelDocument) event.getDocument(); final boolean canUpdate = isAtTravelNode(event.getDocument().getDocumentHeader().getWorkflowDocument()) || isAdvancePaymentMethodException(event.getDocument(), line); // Are we at the travel node? If so, there's a chance that accounting lines changed; if they did, that // was a permission granted to the travel manager so we should allow it. Also, if we're at PaymentMethod and the line is an advance accounting line, that's allowed to boolean valid = true; String errorPath = TemPropertyConstants.NEW_SOURCE_ACCTG_LINE; for (TemSourceAccountingLine sourceLine : (List<TemSourceAccountingLine>)travelDocument.getSourceAccountingLines()){ if (line.equals(sourceLine)){ errorPath = "document." + TemPropertyConstants.SOURCE_ACCOUNTING_LINE + "[" + travelDocument.getSourceAccountingLines().indexOf(line) + "]"; break; } } // Test added accounting lines for null values and if there is an access change. valid = getTravelDocumentService().validateSourceAccountingLines(travelDocument, false); if ((!travelDocument.getAppDocStatus().equalsIgnoreCase(TemConstants.TRAVEL_DOC_APP_DOC_STATUS_INIT)) && (!travelDocument.getAppDocStatus().equalsIgnoreCase(TemConstants.TravelAuthorizationStatusCodeKeys.IN_PROCESS)) && (!travelDocument.getAppDocStatus().equalsIgnoreCase(TemConstants.TravelAuthorizationStatusCodeKeys.CHANGE_IN_PROCESS))) { if (!line.getAccount().getAccountFiscalOfficerUser().getPrincipalId().equals(currentUser.getPrincipalId()) && !canUpdate) { GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, TemKeyConstants.ERROR_TA_FISCAL_OFFICER_ACCOUNT, line.getAccountNumber()); return false; } } GlobalVariables.getMessageMap().addToErrorPath(errorPath); // skip accounting line validation for TA if (!(event.getDocument() instanceof TravelAuthorizationDocument)) { if (ObjectUtils.isNotNull(line.getObjectTypeCode())) { // check to make sure they're the same List<AccountingDistribution> list = getAccountingDistributionService().buildDistributionFrom(travelDocument); List<AccountingLineDistributionKey> distributionList = new ArrayList<AccountingLineDistributionKey>(); List<String> expectedObjectCodes = new ArrayList<String>(); for (AccountingDistribution dist : list) { distributionList.add(new AccountingLineDistributionKey(dist.getObjectCode(), dist.getCardType())); expectedObjectCodes.add(dist.getObjectCode()); } final String expectedObjectCodesString = StringUtils.join(expectedObjectCodes, ", "); if (!distributionList.contains(new AccountingLineDistributionKey(line.getFinancialObjectCode(), line.getCardType()))) { GlobalVariables.getMessageMap().putError(TravelAuthorizationFields.FIN_OBJ_CD, TemKeyConstants.ERROR_TEM_ACCOUNTING_LINES_OBJECT_CODE_CARD_TYPE, line.getFinancialObjectCode(), line.getCardType(), expectedObjectCodesString); valid &= false; } } } if (line.getAmount().isLessEqual(KualiDecimal.ZERO) && !travelDocument.getBlanketTravel()) { GlobalVariables.getMessageMap().putError(KFSPropertyConstants.AMOUNT, KFSKeyConstants.ERROR_CUSTOM, "Amount must be greater than zero."); valid &= false; } if (valid){ //Fly America validation TravelDocument document = (TravelDocument) event.getDocument(); List<TemExpense> allExpenses = new ArrayList<TemExpense>(); allExpenses.addAll(document.getImportedExpenses()); allExpenses.addAll(document.getActualExpenses()); if (allExpenses.size() > 0){ boolean hasAttachment = false; boolean showFlyAmerica = false; for (Note note : document.getNotes()){ if (note.getAttachment() != null){ hasAttachment = true; break; } } boolean isCGEnabled = getParameterService().getParameterValueAsBoolean(KFSConstants.CoreModuleNamespaces.CHART, KFSConstants.RouteLevelNames.ACCOUNT, KFSConstants.ChartApcParms.ACCOUNT_FUND_GROUP_DENOTES_CG); if (isCGEnabled){ for (TemExpense expense : allExpenses){ if (expense.getExpenseTypeCode().equals(TemConstants.ExpenseTypes.AIRFARE)){ Map<String,Object> fieldValues = new HashMap<String, Object>(); fieldValues.put(KRADPropertyConstants.CODE,TemConstants.ExpenseTypes.AIRFARE); fieldValues.put(KRADPropertyConstants.NAME,expense.getTravelCompanyCodeName()); TravelCompanyCode travelCompany = getBusinessObjectService().findByPrimaryKey(TravelCompanyCode.class, fieldValues); if (travelCompany != null && travelCompany.isForeignCompany()){ String financialObjectCode = expense.getExpenseTypeObjectCode() != null ? expense.getExpenseTypeObjectCode().getFinancialObjectCode() : null; if (travelDocument instanceof TravelAuthorizationDocument && expense instanceof ActualExpense){ if (document.getTripType() != null) { financialObjectCode = document.getTripType().getEncumbranceObjCode(); } } if (financialObjectCode != null && financialObjectCode.equals(line.getFinancialObjectCode())){ String cg = getParameterService().getParameterValueAsString(KFSConstants.CoreModuleNamespaces.CHART, KFSConstants.RouteLevelNames.ACCOUNT, KFSConstants.ChartApcParms.ACCOUNT_CG_DENOTING_VALUE); if (line.getAccount() == null){ line.refreshReferenceObject(KFSPropertyConstants.ACCOUNT); } if (line.getAccount().getSubFundGroup() == null){ line.refreshReferenceObject(KFSPropertyConstants.SUB_FUND_GROUP); } if (line.getAccount().getSubFundGroup().getFundGroupCode().equals(cg)){ showFlyAmerica = true; } } } } } } //Fly America error has been triggered, determine what accounting line to show it on. if (showFlyAmerica && !hasAttachment){ boolean newLine = true; for (TemSourceAccountingLine sourceLine : (List<TemSourceAccountingLine>)travelDocument.getSourceAccountingLines()){ if (line.equals(sourceLine)){ newLine = false; } } //if line is a new accounting line or a current one being saved/submitted in the document. //figure out where the new accounting line will be added and set the error to that line # if (newLine) { GlobalVariables.getMessageMap().clearErrorPath(); int newIndex = document.getSourceAccountingLine(document.getSourceAccountingLines().size() - 1).getSequenceNumber() + 1; errorPath = "document." + TemPropertyConstants.SOURCE_ACCOUNTING_LINE + "[" + newIndex + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); } GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, TemKeyConstants.ERROR_ACCOUNTING_LINE_CG); } } } GlobalVariables.getMessageMap().clearErrorPath(); GlobalVariables.getMessageMap().getErrorPath().addAll(holdErrors); return valid; } /** * Check if workflow is at the specific node * * @param workflowDocument * @param nodeName * @return */ protected boolean isAtTravelNode(WorkflowDocument workflowDocument) { Set<String> nodeNames = workflowDocument.getNodeNames(); for (String nodeNamesNode : nodeNames) { if (TemWorkflowConstants.RouteNodeNames.AP_TRAVEL.equals(nodeNamesNode)) { return true; } } return false; } /** * Determines if a) the document is at the PaymentMethod route node; and b) the accountingLine is for a travel advance * @param document a travel document * @param accountingLine the accounting line to validate * @return true if the advance payment/payment method is correct and accessibility should not be checked; false otherwise */ protected boolean isAdvancePaymentMethodException(Document document, TemSourceAccountingLine accountingLine) { return StringUtils.equals(TemConstants.TRAVEL_ADVANCE_ACCOUNTING_LINE_TYPE_CODE, accountingLine.getFinancialDocumentLineTypeCode()) && document.getDocumentHeader().getWorkflowDocument().getCurrentNodeNames().contains(KFSConstants.RouteLevelNames.PAYMENT_METHOD); } public TravelDocumentService getTravelDocumentService() { return travelDocumentService; } public void setTravelDocumentService(TravelDocumentService travelDocumentService) { this.travelDocumentService = travelDocumentService; } public AccountingDistributionService getAccountingDistributionService() { return accountingDistributionService; } public void setAccountingDistributionService(AccountingDistributionService accountingDistributionService) { this.accountingDistributionService = accountingDistributionService; } public ParameterService getParameterService() { return parameterService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } public BusinessObjectService getBusinessObjectService() { return businessObjectService; } public void setBusinessObjectService(BusinessObjectService businessObjectService) { this.businessObjectService = businessObjectService; } }