/* * 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.ar.document.validation.impl; import java.sql.Date; import java.sql.Timestamp; import java.util.Calendar; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateUtils; import org.apache.log4j.Logger; import org.kuali.kfs.module.ar.ArKeyConstants; import org.kuali.kfs.module.ar.ArPropertyConstants; import org.kuali.kfs.module.ar.businessobject.Customer; import org.kuali.kfs.module.ar.businessobject.CustomerAddress; import org.kuali.kfs.module.ar.document.service.CustomerService; 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.rice.core.api.datetime.DateTimeService; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.MessageMap; import org.kuali.rice.krad.util.ObjectUtils; public class CustomerRule extends MaintenanceDocumentRuleBase { protected static Logger LOG = org.apache.log4j.Logger.getLogger(CustomerRule.class); protected Customer oldCustomer; protected Customer newCustomer; protected DateTimeService dateTimeService = SpringContext.getBean(DateTimeService.class); /** * This method initializes the old and new customer * * @param document */ protected void initializeAttributes(MaintenanceDocument document) { if (newCustomer == null) { newCustomer = (Customer) document.getNewMaintainableObject().getBusinessObject(); } if (oldCustomer == null) { oldCustomer = (Customer) document.getOldMaintainableObject().getBusinessObject(); } } /** * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) */ @Override protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { boolean isValid = true; isValid &= super.processCustomRouteDocumentBusinessRules(document); MessageMap errorMap = GlobalVariables.getMessageMap(); //negate the return value from hasErrors() becase when there are no errors //the method returns false so we need to negate the resuls otherwise //out validations will fail. isValid &= !errorMap.hasErrors(); if (isValid) { initializeAttributes(document); isValid &= checkCustomerHasAddress(newCustomer); if (isValid) { isValid &= validateAddresses(newCustomer); } if (isValid) { isValid &= checkAddresses(newCustomer); } if (isValid) { isValid &= checkTaxNumber(newCustomer); } if (isValid) { isValid &= checkNameIsValidLength(newCustomer.getCustomerName()); } if (isValid) { isValid &= checkStopWorkReason(); } // TODO This should probably be done in a BO 'before insert' hook, rather than in the business rule validation, // unless there's some reason not clear why it needs to happen here. if (isValid && document.isNew() && StringUtils.isBlank(newCustomer.getCustomerNumber())) { isValid &= setCustomerNumber(); } } return isValid; } /** * This method sets the new customer number * * @return Returns true if the customer number is set successfully, false otherwise. */ protected boolean setCustomerNumber() { // TODO This should probably be done in a BO 'before insert' hook, rather than in the business rule validation, // unless there's some reason not clear why it needs to happen here. boolean success = true; try { String customerNumber = SpringContext.getBean(CustomerService.class).getNextCustomerNumber(newCustomer); newCustomer.setCustomerNumber(customerNumber); if (oldCustomer != null) { oldCustomer.setCustomerNumber(customerNumber); } } catch (StringIndexOutOfBoundsException sibe) { // It is expected that if a StringIndexOutOfBoundsException occurs, it is due to the customer name being less than three // characters GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_NAME, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_NAME_LESS_THAN_THREE_CHARACTERS); success = false; } return success; } /** * This method checks if the new customer has at least one address * * @param newCustomer the new customer * @return true is the new customer has at least one address, false otherwise */ public boolean checkCustomerHasAddress(Customer newCustomer) { boolean success = true; if (newCustomer.getCustomerAddresses().isEmpty()) { success = false; GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES, ArKeyConstants.CustomerConstants.ERROR_AT_LEAST_ONE_ADDRESS); } return success; } /** * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, * java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject) */ @Override public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) { boolean isValid = true; isValid &= super.processCustomAddCollectionLineBusinessRules(document, collectionName, line); if (collectionName.equals(ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES)) { CustomerAddress customerAddress = (CustomerAddress) line; if (isValid) { isValid &= checkAddressIsValid(customerAddress); isValid &= validateEndDateForNewAddressLine(customerAddress.getCustomerAddressEndDate()); } if (isValid) { Customer customer = (Customer) document.getNewMaintainableObject().getBusinessObject(); if (customerAddress.getCustomerAddressTypeCode().equalsIgnoreCase(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY)) { for (int i = 0; i < customer.getCustomerAddresses().size(); i++) { if (customer.getCustomerAddresses().get(i).getCustomerAddressTypeCode().equalsIgnoreCase(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY)) { customer.getCustomerAddresses().get(i).setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_ALTERNATE); // OK // break; } } } // if new address is not Primary, check if there is an active primary address for this customer. If not, make a new // address primary else { boolean isActivePrimaryAddress = false; Date endDate; for (int i = 0; i < customer.getCustomerAddresses().size(); i++) { if (customer.getCustomerAddresses().get(i).getCustomerAddressTypeCode().equalsIgnoreCase(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY)) { endDate = customer.getCustomerAddresses().get(i).getCustomerAddressEndDate(); // check if endDate qualifies this customer address as inactive (if endDate is a passed date or present // date) if (!checkEndDateIsValid(endDate, false)) { customer.getCustomerAddresses().get(i).setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_ALTERNATE); } else { isActivePrimaryAddress = true; } } } if (!isActivePrimaryAddress) { customerAddress.setCustomerAddressTypeCode(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY); } } } } return isValid; } /** * This method checks if customer end date is valid: 1. if a new address is being added, customer end date must be a future date * 2. if inactivating an address, customer end date must be current or future date * * @param endDate * @param canBeTodaysDateFlag * @return True if endDate is valid. */ public boolean checkEndDateIsValid(Date endDate, boolean canBeTodaysDateFlag) { boolean isValid = true; if (ObjectUtils.isNull(endDate)) { return isValid; } Timestamp today = dateTimeService.getCurrentTimestamp(); today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime()); // end date must be todays date or future date if (canBeTodaysDateFlag) { if (endDate.before(today)) { isValid = false; } } // end date must be a future date else { if (!endDate.after(today)) { isValid = false; } } return isValid; } public boolean validateEndDateForNewAddressLine(Date endDate) { boolean isValid = checkEndDateIsValid(endDate, false); if (!isValid) { String propertyName = ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES_ADD_NEW_ADDRESS + "." + ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_END_DATE; putFieldError(propertyName, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_END_DATE_MUST_BE_FUTURE_DATE); } return isValid; } public boolean validateEndDateForExistingCustomerAddress(Date newEndDate, int ind) { boolean isValid = checkEndDateIsValid(newEndDate, true); // valid end date for an existing customer address; // 1. blank <=> no date entered // 2. todays date -> makes address inactive // 3. future date // 4. if end date is a passed date AND it hasn't been updated <=> oldEndDate = newEndDate // // invalid end date for an existing customer address // 1. if end date is a passed date AND it has been updated <=> oldEndDate != newEndDate if (!isValid) { Date oldEndDate = oldCustomer.getCustomerAddresses().get(ind).getCustomerAddressEndDate(); // passed end date has been entered if (ObjectUtils.isNull(oldEndDate) || ObjectUtils.isNotNull(oldEndDate) && !oldEndDate.toString().equals(newEndDate.toString())) { String propertyName = ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES + "[" + ind + "]." + ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_END_DATE; putFieldError(propertyName, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_END_DATE_MUST_BE_CURRENT_OR_FUTURE_DATE); } else { isValid = true; } } return isValid; } /** * This method checks if the customer name entered is greater than or equal to three (3) characters long. This rule was * implemented to ensure that there are three characters available from the name to be used as a the customer code. * * @param customerName The name of the customer. * @return True if the name is greater than or equal to 3 characters long. */ public boolean checkNameIsValidLength(String customerName) { boolean success = true; if (customerName.length() < 3) { success = false; GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_NAME, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_NAME_LESS_THAN_THREE_CHARACTERS); } if (customerName.indexOf(' ') > -1 && customerName.indexOf(' ') < 3) { success = false; GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_NAME, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_NAME_NO_SPACES_IN_FIRST_THREE_CHARACTERS); } return success; } /** * This method checks if the address is valid * * @param customerAddress * @return true if valid, false otherwise */ public boolean checkAddressIsValid(CustomerAddress customerAddress, int ind) { boolean isValid = true; String propertyName = ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES + "[" + ind + "]."; if (ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_US.equalsIgnoreCase(customerAddress.getCustomerCountryCode())) { if (customerAddress.getCustomerZipCode() == null || "".equalsIgnoreCase(customerAddress.getCustomerZipCode())) { isValid = false; putFieldError(propertyName + ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_ZIP_CODE, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_ZIP_CODE_REQUIRED_WHEN_COUNTTRY_US); } if (customerAddress.getCustomerStateCode() == null || "".equalsIgnoreCase(customerAddress.getCustomerStateCode())) { isValid = false; putFieldError(propertyName + ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_STATE_CODE, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_STATE_CODE_REQUIRED_WHEN_COUNTTRY_US); } } else { if (customerAddress.getCustomerInternationalMailCode() == null || "".equalsIgnoreCase(customerAddress.getCustomerInternationalMailCode())) { isValid = false; // GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_INTERNATIONAL_MAIL_CODE, // ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_INTERNATIONAL_MAIL_CODE_REQUIRED_WHEN_COUNTTRY_NON_US); putFieldError(propertyName + ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_INTERNATIONAL_MAIL_CODE, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_INTERNATIONAL_MAIL_CODE_REQUIRED_WHEN_COUNTTRY_NON_US); } if (customerAddress.getCustomerAddressInternationalProvinceName() == null || "".equalsIgnoreCase(customerAddress.getCustomerAddressInternationalProvinceName())) { isValid = false; // GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_INTERNATIONAL_PROVINCE_NAME, // ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_INTERNATIONAL_PROVINCE_NAME_REQUIRED_WHEN_COUNTTRY_NON_US); putFieldError(propertyName + ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_INTERNATIONAL_PROVINCE_NAME, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_INTERNATIONAL_PROVINCE_NAME_REQUIRED_WHEN_COUNTTRY_NON_US); } } return isValid; } public boolean checkAddressIsValid(CustomerAddress customerAddress) { boolean isValid = true; if (ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_US.equalsIgnoreCase(customerAddress.getCustomerCountryCode())) { if (customerAddress.getCustomerZipCode() == null || "".equalsIgnoreCase(customerAddress.getCustomerZipCode())) { isValid = false; GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_ZIP_CODE, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_ZIP_CODE_REQUIRED_WHEN_COUNTTRY_US); } if (customerAddress.getCustomerStateCode() == null || "".equalsIgnoreCase(customerAddress.getCustomerStateCode())) { isValid = false; GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_STATE_CODE, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_STATE_CODE_REQUIRED_WHEN_COUNTTRY_US); } } else { if (customerAddress.getCustomerInternationalMailCode() == null || "".equalsIgnoreCase(customerAddress.getCustomerInternationalMailCode())) { isValid = false; GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_INTERNATIONAL_MAIL_CODE, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_INTERNATIONAL_MAIL_CODE_REQUIRED_WHEN_COUNTTRY_NON_US); } if (customerAddress.getCustomerAddressInternationalProvinceName() == null || "".equalsIgnoreCase(customerAddress.getCustomerAddressInternationalProvinceName())) { isValid = false; GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_INTERNATIONAL_PROVINCE_NAME, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_ADDRESS_INTERNATIONAL_PROVINCE_NAME_REQUIRED_WHEN_COUNTTRY_NON_US); } } return isValid; } /** * This method checks if the customer addresses are valid: has one and only one primary address * * @param customer * @return true if valid, false otherwise */ public boolean checkAddresses(Customer customer) { boolean isValid = true; boolean hasPrimaryAddress = false; for (CustomerAddress customerAddress : customer.getCustomerAddresses()) { if (customerAddress.getCustomerAddressTypeCode().equalsIgnoreCase(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY)) { if (hasPrimaryAddress) { isValid = false; GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES, ArKeyConstants.CustomerConstants.ERROR_ONLY_ONE_PRIMARY_ADDRESS); } else { hasPrimaryAddress = true; } } } // customer must have at least one primary address if (!hasPrimaryAddress) { isValid = false; GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES, ArKeyConstants.CustomerConstants.ERROR_ONLY_ONE_PRIMARY_ADDRESS); } return isValid; } public boolean validateAddresses(Customer customer) { boolean isValid = true; int i = 0; // this block is to validate email addresses for (CustomerAddress customerAddress : customer.getCustomerAddresses()) { if (ObjectUtils.isNotNull(customerAddress.getCustomerEmailAddress())) { String errorPath = KRADConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES + "[" + i + "]"; GlobalVariables.getMessageMap().addToErrorPath(errorPath); this.getDictionaryValidationService().validateBusinessObject(customerAddress); GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); } i++; } i = 0; for (CustomerAddress customerAddress : customer.getCustomerAddresses()) { isValid &= checkAddressIsValid(customerAddress, i); isValid &= validateEndDateForExistingCustomerAddress(customerAddress.getCustomerAddressEndDate(), i); i++; } if (GlobalVariables.getMessageMap().getErrorCount() > 0) { isValid = false; } if (isValid) { i = 0; for (CustomerAddress customerAddress : customer.getCustomerAddresses()) { if (customerAddress.getCustomerAddressTypeCode().equalsIgnoreCase(ArKeyConstants.CustomerConstants.CUSTOMER_ADDRESS_TYPE_CODE_PRIMARY) && ObjectUtils.isNotNull(customerAddress.getCustomerAddressEndDate())) { isValid &= checkIfPrimaryAddressActive(customerAddress.getCustomerAddressEndDate(), i); } i++; } } return isValid; } public boolean checkIfPrimaryAddressActive(Date newEndDate, int ind) { // if here -> this is a Primary Address, customer end date is not null boolean isActiveAddressFlag = checkEndDateIsValid(newEndDate, false); if (!isActiveAddressFlag) { String propertyName = ArPropertyConstants.CustomerFields.CUSTOMER_TAB_ADDRESSES + "[" + ind + "]." + ArPropertyConstants.CustomerFields.CUSTOMER_ADDRESS_END_DATE; putFieldError(propertyName, ArKeyConstants.CustomerConstants.ERROR_CUSTOMER_PRIMARY_ADDRESS_MUST_HAVE_FUTURE_END_DATE); } return isActiveAddressFlag; } /** * This method checks if tax number is entered when tax number is required * * @param customer * @return true if tax number is required and tax number is entered or if tax number is not required, false if tax number * required and tax number not entered */ public boolean checkTaxNumber(Customer customer) { boolean isValid = true; if (isTaxNumberRequired()) { boolean noTaxNumber = (customer.getCustomerTaxNbr() == null || customer.getCustomerTaxNbr().equalsIgnoreCase("")); if (noTaxNumber) { isValid = false; GlobalVariables.getMessageMap().putError(KFSConstants.MAINTENANCE_NEW_MAINTAINABLE + ArPropertyConstants.CustomerFields.CUSTOMER_SOCIAL_SECURITY_NUMBER, ArKeyConstants.CustomerConstants.ERROR_TAX_NUMBER_IS_REQUIRED); } } return isValid; } /** * This method checks if tax number is required * * @return true if tax number is required, false otherwise */ public boolean isTaxNumberRequired() { boolean paramExists = SpringContext.getBean(ParameterService.class).parameterExists(Customer.class, KFSConstants.CustomerParameter.TAX_NUMBER_REQUIRED_IND); if (paramExists) { return SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(Customer.class, KFSConstants.CustomerParameter.TAX_NUMBER_REQUIRED_IND); } else { return false; } } /** * This method checks if the Stop Work Reason has been entered if the Stop Work flag has been checked. * * @return true if Stop Work flag hasn't been checked, or if it has been checked and the Stop Work Reason has been entered, * false otherwise */ protected boolean checkStopWorkReason() { boolean success = true; if (newCustomer.isStopWorkIndicator()) { if (StringUtils.isBlank(newCustomer.getStopWorkReason())) { success = false; putFieldError(KFSPropertyConstants.STOP_WORK_REASON, KFSKeyConstants.ERROR_STOP_WORK_REASON_REQUIRED); } } return success; } }