/*
* 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.validation.impl;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.fp.businessobject.DisbursementVoucherNonEmployeeTravel;
import org.kuali.kfs.fp.document.DisbursementVoucherConstants;
import org.kuali.kfs.fp.document.DisbursementVoucherDocument;
import org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService;
import org.kuali.kfs.fp.document.service.DisbursementVoucherTravelService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.validation.GenericValidation;
import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
import org.kuali.rice.core.api.parameter.ParameterEvaluator;
import org.kuali.rice.core.api.parameter.ParameterEvaluatorService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kns.service.DictionaryValidationService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.krad.util.ObjectUtils;
public class DisbursementVoucherNonEmployeeTravelValidation extends GenericValidation {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisbursementVoucherNonEmployeeTravelValidation.class);
private ParameterService parameterService;
private DisbursementVoucherTaxService disbursementVoucherTaxService;
private DisbursementVoucherTravelService disbursementVoucherTravelService;
private DictionaryValidationService dictionaryValidationService;
private AccountingDocument accountingDocumentForValidation;
/**
* @see org.kuali.kfs.sys.document.validation.Validation#validate(org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent)
*/
public boolean validate(AttributedDocumentEvent event) {
LOG.debug("validate start");
boolean isValid = true;
DisbursementVoucherDocument document = (DisbursementVoucherDocument) accountingDocumentForValidation;
DisbursementVoucherNonEmployeeTravel nonEmployeeTravel = document.getDvNonEmployeeTravel();
// skip the validation if the payment reason is not noneployee travel or the payee is an employee
if (!isTravelNonEmplPaymentReason(document) || document.getDvPayeeDetail().isEmployee()) {
return true;
}
MessageMap errors = GlobalVariables.getMessageMap();
errors.addToErrorPath(KFSPropertyConstants.DOCUMENT);
errors.addToErrorPath(KFSPropertyConstants.DV_NON_EMPLOYEE_TRAVEL);
getDictionaryValidationService().validateBusinessObjectsRecursively(document.getDvNonEmployeeTravel(), 1);
/* travel from and to state required if country is us */
if (KFSConstants.COUNTRY_CODE_UNITED_STATES.equals(nonEmployeeTravel.getDvTravelFromCountryCode()) && StringUtils.isBlank(nonEmployeeTravel.getDisbVchrTravelFromStateCode())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_TRAVEL_FROM_STATE_CODE, KFSKeyConstants.ERROR_DV_TRAVEL_FROM_STATE);
isValid = false;
}
if (KFSConstants.COUNTRY_CODE_UNITED_STATES.equals(nonEmployeeTravel.getDisbVchrTravelToCountryCode()) && StringUtils.isBlank(nonEmployeeTravel.getDisbVchrTravelToStateCode())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_TRAVEL_TO_STATE_CODE, KFSKeyConstants.ERROR_DV_TRAVEL_TO_STATE);
isValid = false;
}
if (!isValid) {
errors.removeFromErrorPath(KFSPropertyConstants.DV_NON_EMPLOYEE_TRAVEL);
errors.removeFromErrorPath(KFSPropertyConstants.DOCUMENT);
return false;
}
/* must fill in all required per diem fields if any field is filled in */
boolean perDiemSectionComplete = validatePerDiemSection(document, errors);
/* must fill in all required personal vehicle fields if any field is filled in */
boolean personalVehicleSectionComplete = validatePersonalVehicleSection(document, errors);
/* must have per diem change message if actual amount is different from calculated amount */
if (perDiemSectionComplete) { // Only validate if per diem section is filled in
if (nonEmployeeTravel.getDisbVchrPerdiemCalculatedAmt().compareTo(nonEmployeeTravel.getDisbVchrPerdiemActualAmount()) != 0 && StringUtils.isBlank(nonEmployeeTravel.getDvPerdiemChangeReasonText())) {
errors.putError(KFSPropertyConstants.DV_PERDIEM_CHANGE_REASON_TEXT, KFSKeyConstants.ERROR_DV_PERDIEM_CHANGE_REQUIRED);
isValid = false;
}
}
/* make sure per diem fields have not changed since the per diem amount calculation */
if (perDiemSectionComplete) { // Only validate if per diem section is filled in
KualiDecimal calculatedPerDiem = getDisbursementVoucherTravelService().calculatePerDiemAmount(nonEmployeeTravel.getDvPerdiemStartDttmStamp(), nonEmployeeTravel.getDvPerdiemEndDttmStamp(), nonEmployeeTravel.getDisbVchrPerdiemRate());
if (calculatedPerDiem.compareTo(nonEmployeeTravel.getDisbVchrPerdiemCalculatedAmt()) != 0) {
errors.putErrorWithoutFullErrorPath(KFSConstants.GENERAL_NONEMPLOYEE_TAB_ERRORS, KFSKeyConstants.ERROR_DV_PER_DIEM_CALC_CHANGE);
isValid = false;
}
}
// validate the tax amount
isValid &= validateTravelAmount(document);
/* make sure mileage fields have not changed since the mileage amount calculation */
if (personalVehicleSectionComplete) {
KualiDecimal currentCalcAmt = document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt();
KualiDecimal currentActualAmt = document.getDvNonEmployeeTravel().getDisbVchrPersonalCarAmount();
if (ObjectUtils.isNotNull(currentCalcAmt) && ObjectUtils.isNotNull(currentActualAmt)) {
KualiDecimal calculatedMileageAmount = getDisbursementVoucherTravelService().calculateMileageAmount(document.getDvNonEmployeeTravel().getDvPersonalCarMileageAmount(), document.getDvNonEmployeeTravel().getDvPerdiemStartDttmStamp());
if (calculatedMileageAmount.compareTo(document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt()) != 0) {
errors.putErrorWithoutFullErrorPath(KFSConstants.GENERAL_NONEMPLOYEE_TAB_ERRORS, KFSKeyConstants.ERROR_DV_MILEAGE_CALC_CHANGE);
isValid = false;
}
// determine if the rule is flagged off in the parm setting
boolean performTravelMileageLimitInd = parameterService.getParameterValueAsBoolean(DisbursementVoucherDocument.class, DisbursementVoucherConstants.NONEMPLOYEE_TRAVEL_ACTUAL_MILEAGE_LIMIT_PARM_NM);
if (performTravelMileageLimitInd) {
// if actual amount is greater than calculated amount
if (currentCalcAmt.subtract(currentActualAmt).isNegative()) {
errors.putError(KFSPropertyConstants.DV_PERSONAL_CAR_AMOUNT, KFSKeyConstants.ERROR_DV_ACTUAL_MILEAGE_TOO_HIGH);
isValid = false;
}
}
}
}
errors.removeFromErrorPath(KFSPropertyConstants.DV_NON_EMPLOYEE_TRAVEL);
errors.removeFromErrorPath(KFSPropertyConstants.DOCUMENT);
return isValid;
}
/**
* Determines if the given document has an income for tax
* @param document document to check
* @return true if it does have non-reportable income, false otherwise
*/
protected boolean hasIncomeClassCode(DisbursementVoucherDocument document) {
return StringUtils.isNotBlank(document.getDvNonResidentAlienTax().getIncomeClassCode());
}
/**
* Determines if the tax on the document was gross up
* @param document the document to check
* @return true if the tax was gross up, false otherwise
*/
protected boolean isGrossUp(DisbursementVoucherDocument document) {
return document.getDvNonResidentAlienTax().isIncomeTaxGrossUpCode();
}
/**
* Determines if tax should be taken into consideration when checking the total travel amount, and validates that it matches the paid amount
* @param document the document to validate the non-employee total travel amount of
* @return true if the document validated perfectly, false otherwise
*/
protected boolean validateTravelAmount(DisbursementVoucherDocument document) {
/* total on non-employee travel must equal Check Total */
KualiDecimal paidAmount = document.getDisbVchrCheckTotalAmount();
final boolean incomeClassCoded = hasIncomeClassCode(document);
final boolean grossUp = isGrossUp(document);
final KualiDecimal travelAmount = document.getDvNonEmployeeTravel().getTotalTravelAmount();
if (incomeClassCoded && !grossUp) { // we're adding tax and not grossing up; we need to add the tax amount to the paid amount
paidAmount = paidAmount.add(getDisbursementVoucherTaxService().getNonResidentAlienTaxAmount(document));
}
if (paidAmount.compareTo(travelAmount) != 0) {
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSConstants.DV_CHECK_TRAVEL_TOTAL_ERROR, KFSKeyConstants.ERROR_DV_TRAVEL_CHECK_TOTAL);
return false;
}
return true;
}
/**
* This method checks to see if the per diem section of the non employee travel tab contains any values. If this section
* contains any values, the section is validated to ensure that all the required fields for this section are populated.
*
* @param document submitted disbursement voucher document
* @param errors map containing any generated errors
* @return true if per diem section is used by user and that all fields contain values.
*/
private boolean validatePerDiemSection(DisbursementVoucherDocument document, MessageMap errors) {
boolean perDiemSectionComplete = true;
// Checks to see if any per diem fields are filled in
boolean perDiemUsed = StringUtils.isNotBlank(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCategoryName()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemRate()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCalculatedAmt()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemActualAmount());
// If any per diem fields contain data, validates that all required per diem fields are filled in
if (perDiemUsed) {
if (StringUtils.isBlank(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCategoryName())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_PERDIEM_CATEGORY_NAME, KFSKeyConstants.ERROR_DV_PER_DIEM_CATEGORY);
perDiemSectionComplete = false;
}
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemRate())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_PERDIEM_RATE, KFSKeyConstants.ERROR_DV_PER_DIEM_RATE);
perDiemSectionComplete = false;
}
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCalculatedAmt())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_PERDIEM_CALCULATED_AMT, KFSKeyConstants.ERROR_DV_PER_DIEM_CALC_AMT);
perDiemSectionComplete = false;
}
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemActualAmount())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_PERDIEM_ACTUAL_AMOUNT, KFSKeyConstants.ERROR_DV_PER_DIEM_ACTUAL_AMT);
perDiemSectionComplete = false;
}
}
perDiemSectionComplete = perDiemSectionComplete && perDiemUsed;
return perDiemSectionComplete;
}
/**
* This method checks to see if the per diem section of the non employee travel tab contains any values. If this section
* contains any values, the section is validated to ensure that all the required fields for this section are populated.
*
* @param document submitted disbursement voucher document
* @param errors map containing any generated errors
* @return true if per diem section is used by user and that all fields contain values.
*/
private boolean validatePersonalVehicleSection(DisbursementVoucherDocument document, MessageMap errors) {
boolean personalVehicleSectionComplete = true;
// Checks to see if any per diem fields are filled in
boolean personalVehilcleUsed = ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromCityName()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromStateCode()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToCityName()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToStateCode()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDvPersonalCarMileageAmount()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPersonalCarAmount());
// If any per diem fields contain data, validates that all required per diem fields are filled in
if (personalVehilcleUsed) {
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromCityName())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_AUTO_FROM_CITY_NAME, KFSKeyConstants.ERROR_DV_AUTO_FROM_CITY);
personalVehicleSectionComplete = false;
}
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToCityName())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_AUTO_TO_CITY_NAME, KFSKeyConstants.ERROR_DV_AUTO_TO_CITY);
personalVehicleSectionComplete = false;
}
// are state fields required always or only for US travel?
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromStateCode())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_AUTO_FROM_STATE_CODE, KFSKeyConstants.ERROR_DV_AUTO_FROM_STATE);
personalVehicleSectionComplete = false;
}
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToStateCode())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_AUTO_TO_STATE_CODE, KFSKeyConstants.ERROR_DV_AUTO_TO_STATE);
personalVehicleSectionComplete = false;
}
// end state field validation
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDvPersonalCarMileageAmount())) {
errors.putError(KFSPropertyConstants.DV_PERSONAL_CAR_MILEAGE_AMOUNT, KFSKeyConstants.ERROR_DV_MILEAGE_AMT);
personalVehicleSectionComplete = false;
}
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_MILEAGE_CALCULATED_AMT, KFSKeyConstants.ERROR_DV_MILEAGE_CALC_AMT);
personalVehicleSectionComplete = false;
}
if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPersonalCarAmount())) {
errors.putError(KFSPropertyConstants.DISB_VCHR_PERSONAL_CAR_AMOUNT, KFSKeyConstants.ERROR_DV_MILEAGE_ACTUAL_AMT);
personalVehicleSectionComplete = false;
}
}
personalVehicleSectionComplete = personalVehicleSectionComplete && personalVehilcleUsed;
return personalVehicleSectionComplete;
}
/**
* Returns whether the document's payment reason is for travel by a non-employee
*
* @param disbursementVoucherDocument submitted disbursement voucher document
* @return true if payment reason is travel by a non-employee
*
*/
private boolean isTravelNonEmplPaymentReason(DisbursementVoucherDocument disbursementVoucherDocument) {
ParameterEvaluator travelNonEmplPaymentReasonEvaluator = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(DisbursementVoucherDocument.class, DisbursementVoucherConstants.NONEMPLOYEE_TRAVEL_PAY_REASONS_PARM_NM, disbursementVoucherDocument.getDvPayeeDetail().getDisbVchrPaymentReasonCode());
return travelNonEmplPaymentReasonEvaluator.evaluationSucceeds();
}
/**
* Sets the accountingDocumentForValidation attribute value.
*
* @param accountingDocumentForValidation The accountingDocumentForValidation to set.
*/
public void setAccountingDocumentForValidation(AccountingDocument accountingDocumentForValidation) {
this.accountingDocumentForValidation = accountingDocumentForValidation;
}
/**
* Sets the parameterService attribute value.
* @param parameterService The parameterService to set.
*/
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
/**
* Gets the accountingDocumentForValidation attribute.
* @return Returns the accountingDocumentForValidation.
*/
public AccountingDocument getAccountingDocumentForValidation() {
return accountingDocumentForValidation;
}
/**
* Gets the disbursementVoucherTaxService attribute.
* @return Returns the disbursementVoucherTaxService.
*/
public DisbursementVoucherTaxService getDisbursementVoucherTaxService() {
return disbursementVoucherTaxService;
}
/**
* Sets the disbursementVoucherTaxService attribute value.
* @param disbursementVoucherTaxService The disbursementVoucherTaxService to set.
*/
public void setDisbursementVoucherTaxService(DisbursementVoucherTaxService disbursementVoucherTaxService) {
this.disbursementVoucherTaxService = disbursementVoucherTaxService;
}
/**
* Gets the disbursementVoucherTravelService attribute.
* @return Returns the disbursementVoucherTravelService.
*/
public DisbursementVoucherTravelService getDisbursementVoucherTravelService() {
return disbursementVoucherTravelService;
}
/**
* Sets the disbursementVoucherTravelService attribute value.
* @param disbursementVoucherTravelService The disbursementVoucherTravelService to set.
*/
public void setDisbursementVoucherTravelService(DisbursementVoucherTravelService disbursementVoucherTravelService) {
this.disbursementVoucherTravelService = disbursementVoucherTravelService;
}
/**
* Gets the dictionaryValidationService attribute.
* @return Returns the dictionaryValidationService.
*/
public DictionaryValidationService getDictionaryValidationService() {
return dictionaryValidationService;
}
/**
* Sets the dictionaryValidationService attribute value.
* @param dictionaryValidationService The dictionaryValidationService to set.
*/
public void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) {
this.dictionaryValidationService = dictionaryValidationService;
}
}