/* * 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 java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.fp.businessobject.DisbursementVoucherNonEmployeeTravel; import org.kuali.kfs.fp.businessobject.options.PaymentReasonValuesFinder; import org.kuali.kfs.fp.document.DisbursementVoucherConstants; import org.kuali.kfs.fp.document.DisbursementVoucherDocument; import org.kuali.kfs.fp.service.AccountingDocumentPreRuleService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.Bank; import org.kuali.kfs.sys.businessobject.PaymentSourceWireTransfer; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.AccountingDocumentBase; import org.kuali.kfs.sys.service.BankService; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.core.api.util.KeyValue; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kns.rules.PromptBeforeValidationBase; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.util.ObjectUtils; /** * Checks warnings and prompt conditions for dv document. */ public class DisbursementVoucherDocumentPreRules extends PromptBeforeValidationBase implements DisbursementVoucherConstants { /** * Executes pre-rules for Disbursement Voucher Document * * @param document submitted document * @return true if pre-rules execute successfully * @see org.kuali.rice.kns.rules.PromptBeforeValidationBase#doRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override public boolean doPrompts(Document document) { boolean preRulesOK = true; DisbursementVoucherDocument dvDocument = (DisbursementVoucherDocument) document; checkSpecialHandlingIndicator(dvDocument); preRulesOK &= checkNonEmployeeTravelTabState(dvDocument); preRulesOK &= checkWireTransferTabState(dvDocument); preRulesOK &= checkForeignDraftTabState(dvDocument); preRulesOK &= checkBankCodeActive(dvDocument); preRulesOK &= SpringContext.getBean(AccountingDocumentPreRuleService.class).expiredAccountOverrideQuestion((AccountingDocumentBase) document, this, this.event); return preRulesOK; } /** * If the special handling name and address 1 fields have value, this will mark the special handling indicator for the user. * * @param dvDocument submitted disbursement voucher document */ protected void checkSpecialHandlingIndicator(DisbursementVoucherDocument dvDocument) { if (StringUtils.isNotBlank(dvDocument.getDvPayeeDetail().getDisbVchrSpecialHandlingPersonName()) && StringUtils.isNotBlank(dvDocument.getDvPayeeDetail().getDisbVchrSpecialHandlingLine1Addr()) && allowTurningOnOfSpecialHandling(dvDocument)) { dvDocument.setDisbVchrSpecialHandlingCode(true); } } /** * Allows the automatic turning on of special handling indicator - which will not be allowed at the Campus route level * @param dvDocument the document to allow turning on of special handling for * @return true if special handling can be automatically turned on, false otherwise */ protected boolean allowTurningOnOfSpecialHandling(DisbursementVoucherDocument dvDocument) { Set<String> currentNodes = dvDocument.getDocumentHeader().getWorkflowDocument().getCurrentNodeNames(); return CollectionUtils.isNotEmpty(currentNodes) && !(currentNodes.contains(DisbursementVoucherConstants.RouteLevelNames.CAMPUS)); } /** * This method checks non-employee travel tab state is valid * * @param dvDocument submitted disbursement voucher document * @return true if the state of all the tabs is valid, false otherwise. */ protected boolean checkNonEmployeeTravelTabState(DisbursementVoucherDocument dvDocument) { boolean tabStatesOK = true; DisbursementVoucherNonEmployeeTravel dvNonEmplTrav = dvDocument.getDvNonEmployeeTravel(); if (!hasNonEmployeeTravelValues(dvNonEmplTrav)) { return true; } String paymentReasonCode = dvDocument.getDvPayeeDetail().getDisbVchrPaymentReasonCode(); List<String> nonEmpltravelPaymentReasonCodes = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(DisbursementVoucherDocument.class, NONEMPLOYEE_TRAVEL_PAY_REASONS_PARM_NM) ); if (nonEmpltravelPaymentReasonCodes == null || !nonEmpltravelPaymentReasonCodes.contains(paymentReasonCode) || dvDocument.getDvPayeeDetail().isEmployee()) { String nonEmplTravReasonStr = getValidPaymentReasonsAsString(nonEmpltravelPaymentReasonCodes); String paymentReasonName = dvDocument.getDvPayeeDetail().getDisbVchrPaymentReasonName(); Object[] args = { "payment reason", "'" + paymentReasonName + "'", "Non-Employee Travel", nonEmplTravReasonStr }; String questionText = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(KFSKeyConstants.QUESTION_CLEAR_UNNEEDED_TAB); questionText = MessageFormat.format(questionText, args); boolean clearTab = super.askOrAnalyzeYesNoQuestion(KFSConstants.DisbursementVoucherDocumentConstants.CLEAR_NON_EMPLOYEE_TAB_QUESTION_ID, questionText); if (clearTab) { DisbursementVoucherNonEmployeeTravel blankDvNonEmplTrav = new DisbursementVoucherNonEmployeeTravel(); blankDvNonEmplTrav.setDocumentNumber(dvNonEmplTrav.getDocumentNumber()); blankDvNonEmplTrav.setVersionNumber(dvNonEmplTrav.getVersionNumber()); dvDocument.setDvNonEmployeeTravel(blankDvNonEmplTrav); } else { // return to document if the user doesn't want to clear the Non Employee Travel tab super.event.setActionForwardName(KFSConstants.MAPPING_BASIC); tabStatesOK = false; } } return tabStatesOK; } /** * Returns true if non-employee travel tab contains any data in any of its fields * * @param dvNonEmplTrav disbursement voucher non employee travel object * @return True if non employee travel tab contains any data in any fields. */ protected boolean hasNonEmployeeTravelValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) { boolean hasValues = false; // Checks each explicit field in the tab for user entered values hasValues = hasNonEmployeeTravelGeneralValues(dvNonEmplTrav); // Checks per diem section for values if (!hasValues) { hasValues = hasNonEmployeeTravelPerDiemValues(dvNonEmplTrav); } if (!hasValues) { hasValues = hasNonEmployeeTravelPersonalVehicleValues(dvNonEmplTrav); } if (!hasValues) { hasValues = dvNonEmplTrav.getDvNonEmployeeExpenses().size() > 0; } if (!hasValues) { hasValues = dvNonEmplTrav.getDvPrePaidEmployeeExpenses().size() > 0; } return hasValues; } /** * Returns true if any values are not blank on non employee travel tab * * @param dvNonEmplTrav disbursement voucher non employee travel object * @return True if any values are found in the non employee travel tab */ protected boolean hasNonEmployeeTravelGeneralValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) { return StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrNonEmpTravelerName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrServicePerformedDesc()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvServicePerformedLocName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvServiceRegularEmprName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelFromCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelFromStateCode()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvTravelFromCountryCode()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDvPerdiemStartDttmStamp()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelToCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelToStateCode()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelToCountryCode()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDvPerdiemEndDttmStamp()); } /** * Returns true if non employee travel tab contains data in any of the fields in the per diem section * * @param dvNonEmplTrav disbursement voucher non employee travel object * @return True if non employee travel tab contains data in any of the fields in the per diem section */ protected boolean hasNonEmployeeTravelPerDiemValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) { return StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrPerdiemCategoryName()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPerdiemRate()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPerdiemCalculatedAmt()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPerdiemActualAmount()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvPerdiemChangeReasonText()); } /** * Returns true if non employee travel tab contains data in any of the fields in the personal vehicle section * * @param dvNonEmplTrav disbursement voucher non employee travel object * @return True if non employee travel tab contains data in any of the fields in the personal vehicle section */ protected boolean hasNonEmployeeTravelPersonalVehicleValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) { return StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoFromCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoFromStateCode()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoToCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoToStateCode()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrMileageCalculatedAmt()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPersonalCarAmount()); } /** * Returns true if the state of all the tabs is valid, false otherwise. * * @param dvDocument submitted disbursemtn voucher document * @return true if the state of all the tabs is valid, false otherwise. */ protected boolean checkForeignDraftTabState(DisbursementVoucherDocument dvDocument) { boolean tabStatesOK = true; PaymentSourceWireTransfer dvForeignDraft = dvDocument.getWireTransfer(); // if payment method is CHECK and wire tab contains data, ask user to clear tab if ((StringUtils.equals(KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_CHECK, dvDocument.getDisbVchrPaymentMethodCode()) || StringUtils.equals(KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_WIRE, dvDocument.getDisbVchrPaymentMethodCode())) && hasForeignDraftValues(dvForeignDraft)) { String questionText = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(KFSKeyConstants.QUESTION_CLEAR_UNNEEDED_TAB); Object[] args = { "payment method", dvDocument.getDisbVchrPaymentMethodCode(), "Foreign Draft", KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_DRAFT }; questionText = MessageFormat.format(questionText, args); boolean clearTab = super.askOrAnalyzeYesNoQuestion(KFSConstants.DisbursementVoucherDocumentConstants.CLEAR_FOREIGN_DRAFT_TAB_QUESTION_ID, questionText); if (clearTab) { // NOTE: Can't replace with new instance because Wire Transfer uses same object clearForeignDraftValues(dvForeignDraft); } else { // return to document if the user doesn't want to clear the Wire Transfer tab super.event.setActionForwardName(KFSConstants.MAPPING_BASIC); tabStatesOK = false; } } return tabStatesOK; } /** * Returns true if foreign draft tab contains any data in any fields. NOTE: Currently does not validate based on only required * fields. Checks all fields within tab for data. * * @param dvForeignDraft disbursement foreign draft object * @return True if foreign draft tab contains any data in any fields. */ protected boolean hasForeignDraftValues(PaymentSourceWireTransfer dvForeignDraft) { boolean hasValues = false; // Checks each explicit field in the tab for user entered values hasValues |= StringUtils.isNotBlank(dvForeignDraft.getForeignCurrencyTypeCode()); hasValues |= StringUtils.isNotBlank(dvForeignDraft.getForeignCurrencyTypeName()); return hasValues; } /** * This method sets foreign currency type code and name to null for passed in disbursement foreign draft object * * @param dvForeignDraft disbursement foreign draft object */ protected void clearForeignDraftValues(PaymentSourceWireTransfer dvForeignDraft) { dvForeignDraft.setForeignCurrencyTypeCode(null); dvForeignDraft.setForeignCurrencyTypeName(null); } /** * This method returns true if the state of all the tabs is valid, false otherwise. * * @param dvDocument submitted disbursement voucher document * @return Returns true if the state of all the tabs is valid, false otherwise. */ protected boolean checkWireTransferTabState(DisbursementVoucherDocument dvDocument) { boolean tabStatesOK = true; PaymentSourceWireTransfer dvWireTransfer = dvDocument.getWireTransfer(); // if payment method is CHECK and wire tab contains data, ask user to clear tab if ((StringUtils.equals(KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_CHECK, dvDocument.getDisbVchrPaymentMethodCode()) || StringUtils.equals(KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_DRAFT, dvDocument.getDisbVchrPaymentMethodCode())) && hasWireTransferValues(dvWireTransfer)) { String questionText = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(KFSKeyConstants.QUESTION_CLEAR_UNNEEDED_TAB); Object[] args = { "payment method", dvDocument.getDisbVchrPaymentMethodCode(), "Wire Transfer", KFSConstants.PaymentSourceConstants.PAYMENT_METHOD_WIRE }; questionText = MessageFormat.format(questionText, args); boolean clearTab = super.askOrAnalyzeYesNoQuestion(KFSConstants.DisbursementVoucherDocumentConstants.CLEAR_WIRE_TRANSFER_TAB_QUESTION_ID, questionText); if (clearTab) { // NOTE: Can't replace with new instance because Foreign Draft uses same object clearWireTransferValues(dvWireTransfer); } else { // return to document if the user doesn't want to clear the Wire Transfer tab super.event.setActionForwardName(KFSConstants.MAPPING_BASIC); tabStatesOK = false; } } return tabStatesOK; } /** * If bank specification is enabled, prompts user to use the continuation bank code when the given bank code is inactive * * @param dvDocument document containing bank code * @return true */ protected boolean checkBankCodeActive(DisbursementVoucherDocument dvDocument) { boolean continueRules = true; // if bank specification is not enabled, no need to validate bank if (!SpringContext.getBean(BankService.class).isBankSpecificationEnabled()) { return continueRules; } // refresh bank reference so continuation bank can be checked for active status dvDocument.refreshReferenceObject(KFSPropertyConstants.BANK); Bank bank = dvDocument.getBank(); // if bank is inactive and continuation is active, prompt user to use continuation bank if (bank != null && !bank.isActive() && bank.getContinuationBank().isActive()) { String questionText = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(KFSKeyConstants.QUESTION_BANK_INACTIVE); questionText = MessageFormat.format(questionText, dvDocument.getDisbVchrBankCode(), bank.getContinuationBankCode()); boolean useContinuation = super.askOrAnalyzeYesNoQuestion(KFSConstants.USE_CONTINUATION_BANK_QUESTION, questionText); if (useContinuation) { dvDocument.setDisbVchrBankCode(bank.getContinuationBankCode()); } } return continueRules; } /** * Returns true if wire transfer tab contains any data in any fields. * * @param wireTransfer disbursement voucher wire transfer * @return true if wire transfer tab contains any data in any fields. */ protected boolean hasWireTransferValues(PaymentSourceWireTransfer dvWireTransfer) { boolean hasValues = false; // Checks each explicit field in the tab for user entered values hasValues |= StringUtils.isNotBlank(dvWireTransfer.getAutomatedClearingHouseProfileNumber()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getBankName()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getBankRoutingNumber()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getBankCityName()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getBankStateCode()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getBankCountryCode()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getPayeeAccountNumber()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getAttentionLineText()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getCurrencyTypeName()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getAdditionalWireText()); hasValues |= StringUtils.isNotBlank(dvWireTransfer.getPayeeAccountName()); return hasValues; } /** * This method sets all values in the passed in disbursement wire transfer object to null * * @param wireTransfer */ protected void clearWireTransferValues(PaymentSourceWireTransfer dvWireTransfer) { dvWireTransfer.setAutomatedClearingHouseProfileNumber(null); dvWireTransfer.setBankName(null); dvWireTransfer.setBankRoutingNumber(null); dvWireTransfer.setBankCityName(null); dvWireTransfer.setBankStateCode(null); dvWireTransfer.setBankCountryCode(null); dvWireTransfer.setPayeeAccountNumber(null); dvWireTransfer.setAttentionLineText(null); dvWireTransfer.setCurrencyTypeName(null); dvWireTransfer.setAdditionalWireText(null); dvWireTransfer.setPayeeAccountName(null); } /** * put the valid payment reason codes along with their description together * * @param validPaymentReasonCodes the given valid payment reason codes * @return the valid payment reason codes along with their description as a string */ protected String getValidPaymentReasonsAsString(List<String> validPaymentReasonCodes) { List<String> payementReasonString = new ArrayList<String>(); if (validPaymentReasonCodes == null || validPaymentReasonCodes.isEmpty()) { return StringUtils.EMPTY; } List<KeyValue> reasons = new PaymentReasonValuesFinder().getKeyValues(); for (KeyValue reason : reasons) { if (validPaymentReasonCodes.contains(reason.getKey())) { payementReasonString.add(reason.getValue()); } } return payementReasonString.toString(); } }