001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.vnd.document.validation.impl; 017 018 import java.lang.reflect.Field; 019 import java.util.ArrayList; 020 import java.util.Date; 021 import java.util.HashMap; 022 import java.util.HashSet; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.Set; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.kuali.kfs.coa.businessobject.Chart; 030 import org.kuali.kfs.coa.businessobject.Organization; 031 import org.kuali.kfs.sys.KFSConstants; 032 import org.kuali.kfs.sys.KFSKeyConstants; 033 import org.kuali.kfs.sys.KFSPropertyConstants; 034 import org.kuali.kfs.sys.context.SpringContext; 035 import org.kuali.kfs.sys.service.PostalCodeValidationService; 036 import org.kuali.kfs.vnd.VendorConstants; 037 import org.kuali.kfs.vnd.VendorKeyConstants; 038 import org.kuali.kfs.vnd.VendorParameterConstants; 039 import org.kuali.kfs.vnd.VendorPropertyConstants; 040 import org.kuali.kfs.vnd.businessobject.AddressType; 041 import org.kuali.kfs.vnd.businessobject.OwnershipType; 042 import org.kuali.kfs.vnd.businessobject.VendorAddress; 043 import org.kuali.kfs.vnd.businessobject.VendorCommodityCode; 044 import org.kuali.kfs.vnd.businessobject.VendorContact; 045 import org.kuali.kfs.vnd.businessobject.VendorContract; 046 import org.kuali.kfs.vnd.businessobject.VendorContractOrganization; 047 import org.kuali.kfs.vnd.businessobject.VendorCustomerNumber; 048 import org.kuali.kfs.vnd.businessobject.VendorDefaultAddress; 049 import org.kuali.kfs.vnd.businessobject.VendorDetail; 050 import org.kuali.kfs.vnd.businessobject.VendorHeader; 051 import org.kuali.kfs.vnd.businessobject.VendorType; 052 import org.kuali.kfs.vnd.document.service.VendorService; 053 import org.kuali.kfs.vnd.service.PhoneNumberService; 054 import org.kuali.kfs.vnd.service.TaxNumberService; 055 import org.kuali.rice.kns.bo.PersistableBusinessObject; 056 import org.kuali.rice.kns.document.MaintenanceDocument; 057 import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; 058 import org.kuali.rice.kns.service.BusinessObjectService; 059 import org.kuali.rice.kns.service.DataDictionaryService; 060 import org.kuali.rice.kns.service.DateTimeService; 061 import org.kuali.rice.kns.service.ParameterService; 062 import org.kuali.rice.kns.service.PersistenceService; 063 import org.kuali.rice.kns.util.GlobalVariables; 064 import org.kuali.rice.kns.util.KualiDecimal; 065 import org.kuali.rice.kns.util.ObjectUtils; 066 067 /** 068 * Business rules applicable to VendorDetail document. 069 */ 070 public class VendorRule extends MaintenanceDocumentRuleBase { 071 072 private VendorDetail oldVendor; 073 private VendorDetail newVendor; 074 075 076 /** 077 * Overrides the setupBaseConvenienceObjects from the superclass because we cannot use the setupBaseConvenienceObjects from the 078 * superclass. The reason we cannot use the superclass method is because it calls the updateNonUpdateableReferences for 079 * everything and we cannot do that for parent vendors, because we want to update vendor header information only on parent 080 * vendors, so the saving of the vendor header is done manually. If we call the updateNonUpdateableReferences, it is going to 081 * overwrite any changes that the user might have done in the vendor header with the existing values in the database. 082 * 083 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#setupBaseConvenienceObjects(org.kuali.rice.kns.document.MaintenanceDocument) 084 */ 085 @Override 086 public void setupBaseConvenienceObjects(MaintenanceDocument document) { 087 oldVendor = (VendorDetail) document.getOldMaintainableObject().getBusinessObject(); 088 newVendor = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); 089 super.setNewBo(newVendor); 090 setupConvenienceObjects(); 091 } 092 093 /** 094 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#setupConvenienceObjects() 095 */ 096 @Override 097 public void setupConvenienceObjects() { 098 // setup oldVendor convenience objects, make sure all possible sub-objects are populated 099 refreshSubObjects(oldVendor); 100 101 // setup newVendor convenience objects, make sure all possible sub-objects are populated 102 refreshSubObjects(newVendor); 103 } 104 105 106 /** 107 * Refreshes the references of vendor detail and its sub objects 108 * 109 * @param vendor VendorDetail document 110 */ 111 void refreshSubObjects(VendorDetail vendor) { 112 if (vendor == null) { 113 return; 114 } 115 116 // If this is a division vendor, we need to do a refreshNonUpdateableReferences 117 // and also refreshes the vendor header, since the user aren't supposed to 118 // make any updates of vendor header's attributes while editing a division vendor 119 if (!vendor.isVendorParentIndicator()) { 120 vendor.refreshNonUpdateableReferences(); 121 vendor.getVendorHeader().refreshNonUpdateableReferences(); 122 123 } 124 else { 125 // Retrieve the references objects of the vendor header of this vendor. 126 List<String> headerFieldNames = getObjectReferencesListFromBOClass(VendorHeader.class); 127 vendor.getVendorHeader().refreshNonUpdateableReferences(); 128 SpringContext.getBean(PersistenceService.class).retrieveReferenceObjects(vendor.getVendorHeader(), headerFieldNames); 129 130 // We still need to retrieve all the other references of this vendor in addition to 131 // vendor header. Since this is a parent vendor, whose vendor header saving is handled manually, 132 // we have already retrieved references for vendor header's attributes above, so we should 133 // exclude retrieving reference objects of vendor header. 134 List<String> detailFieldNames = getObjectReferencesListFromBOClass(vendor.getClass()); 135 detailFieldNames.remove(VendorConstants.VENDOR_HEADER_ATTR); 136 SpringContext.getBean(PersistenceService.class).retrieveReferenceObjects(vendor, detailFieldNames); 137 } 138 139 // refresh addresses 140 if (vendor.getVendorAddresses() != null) { 141 for (VendorAddress address : vendor.getVendorAddresses()) { 142 address.refreshNonUpdateableReferences(); 143 if (address.getVendorDefaultAddresses() != null) { 144 for (VendorDefaultAddress defaultAddress : address.getVendorDefaultAddresses()) { 145 defaultAddress.refreshNonUpdateableReferences(); 146 } 147 } 148 } 149 } 150 // refresh contacts 151 if (vendor.getVendorContacts() != null) { 152 for (VendorContact contact : vendor.getVendorContacts()) { 153 contact.refreshNonUpdateableReferences(); 154 } 155 } 156 // refresh contracts 157 if (vendor.getVendorContracts() != null) { 158 for (VendorContract contract : vendor.getVendorContracts()) { 159 contract.refreshNonUpdateableReferences(); 160 } 161 } 162 } 163 164 /** 165 * This is currently used as a helper to get a list of object references (e.g. vendorType, vendorOwnershipType, etc) from a 166 * BusinessObject (e.g. VendorHeader, VendorDetail, etc) class dynamically. Feel free to enhance it, refactor it or move it to a 167 * superclass or elsewhere as you see appropriate. 168 * 169 * @param theClass The Class name of the object whose objects references list are extracted 170 * @return List a List of attributes of the class 171 */ 172 private List getObjectReferencesListFromBOClass(Class theClass) { 173 List<String> results = new ArrayList(); 174 for (Field theField : theClass.getDeclaredFields()) { 175 // only get persistable business object references 176 if ( PersistableBusinessObject.class.isAssignableFrom( theField.getType() ) ) { 177 results.add(theField.getName()); 178 } 179 } 180 return results; 181 } 182 183 /** 184 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 185 */ 186 @Override 187 protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) { 188 boolean valid = processValidation(document); 189 return valid & super.processCustomApproveDocumentBusinessRules(document); 190 } 191 192 /** 193 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 194 */ 195 @Override 196 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 197 boolean valid = processValidation(document); 198 return valid & super.processCustomRouteDocumentBusinessRules(document); 199 } 200 201 /** 202 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 203 */ 204 @Override 205 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { 206 boolean valid = true; 207 return valid & super.processCustomSaveDocumentBusinessRules(document); 208 } 209 210 /** 211 * Validates VendorDetail and its VendorContracts. 212 * 213 * @param document MaintenanceDocument instance 214 * @return boolean false or true 215 */ 216 private boolean processValidation(MaintenanceDocument document) { 217 boolean valid = true; 218 219 valid &= processVendorValidation(document); 220 valid &= processContactValidation(document); 221 if (ObjectUtils.isNotNull(newVendor.getVendorHeader().getVendorType())) { 222 valid &= processAddressValidation(document); 223 valid &= processContractValidation(document); 224 valid &= processCommodityCodeValidation(document); 225 } 226 227 return valid; 228 } 229 230 /** 231 * Validates VendorDetail document. 232 * 233 * @param document MaintenanceDocument instance 234 * @return boolean false or true 235 */ 236 boolean processVendorValidation(MaintenanceDocument document) { 237 boolean valid = true; 238 VendorDetail vendorDetail = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); 239 240 valid &= validateTaxTypeAndTaxNumberBlankness(vendorDetail); 241 valid &= validateParentVendorTaxNumber(vendorDetail); 242 valid &= validateOwnershipTypeAllowed(vendorDetail); 243 valid &= validateTaxNumberFromTaxNumberService(vendorDetail); 244 valid &= validateRestrictedReasonRequiredness(vendorDetail); 245 valid &= validateInactiveReasonRequiredness(vendorDetail); 246 247 if (ObjectUtils.isNotNull(vendorDetail.getVendorHeader().getVendorType())) { 248 valid &= validateTaxNumberRequiredness(vendorDetail); 249 } 250 251 valid &= validateVendorNames(vendorDetail); 252 valid &= validateVendorSoldToNumber(vendorDetail); 253 valid &= validateMinimumOrderAmount(vendorDetail); 254 valid &= validateOwnershipCategory(vendorDetail); 255 valid &= validateVendorWithholdingTaxDates(vendorDetail); 256 valid &= validateVendorW8BenOrW9ReceivedIndicator(vendorDetail); 257 return valid; 258 } 259 260 /** 261 * Validates that if the vendor is set to be inactive, the inactive reason is required. 262 * 263 * @param vendorDetail the VendorDetail object to be validated 264 * @return boolean false if the vendor is inactive and the inactive reason is empty or if the vendor is active and the inactive reason is not empty 265 */ 266 boolean validateInactiveReasonRequiredness(VendorDetail vendorDetail) { 267 boolean activeIndicator = vendorDetail.isActiveIndicator(); 268 boolean emptyInactiveReason = StringUtils.isEmpty(vendorDetail.getVendorInactiveReasonCode()); 269 270 // return false if the vendor is inactive and the inactive reason is empty 271 if (!activeIndicator && emptyInactiveReason) { 272 putFieldError(VendorPropertyConstants.VENDOR_INACTIVE_REASON, VendorKeyConstants.ERROR_INACTIVE_REASON_REQUIRED); 273 return false; 274 } 275 // return false if the vendor is active and the inactive reason is not empty 276 if (activeIndicator && !emptyInactiveReason) { 277 putFieldError(VendorPropertyConstants.VENDOR_INACTIVE_REASON, VendorKeyConstants.ERROR_INACTIVE_REASON_NOT_ALLOWED); 278 return false; 279 } 280 return true; 281 } 282 283 /** 284 * Validates that if the vendor is not foreign and if the vendor type's tax number required indicator is true, then the tax 285 * number is required. If the vendor foreign indicator is true, then the tax number is not required regardless of its vendor 286 * type. 287 * 288 * @param vendorDetail the VendorDetail object to be validated 289 * @return boolean false if there is no tax number and the indicator is true. 290 */ 291 boolean validateTaxNumberRequiredness(VendorDetail vendorDetail) { 292 if (!vendorDetail.getVendorHeader().getVendorForeignIndicator() && vendorDetail.getVendorHeader().getVendorType().isVendorTaxNumberRequiredIndicator() && StringUtils.isBlank(vendorDetail.getVendorHeader().getVendorTaxNumber())) { 293 if (vendorDetail.isVendorParentIndicator()) { 294 putFieldError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_TYPE_REQUIRES_TAX_NUMBER, vendorDetail.getVendorHeader().getVendorType().getVendorTypeDescription()); 295 } 296 else { 297 putFieldError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_PARENT_NEEDS_CHANGED); 298 } 299 return false; 300 } 301 return true; 302 } 303 304 /** 305 * Validates that, if the vendor is set to be restricted, the restricted reason is required. 306 * 307 * @param vendorDetail The VendorDetail object to be validated 308 * @return boolean false if the vendor is restricted and the restricted reason is empty 309 */ 310 boolean validateRestrictedReasonRequiredness(VendorDetail vendorDetail) { 311 if (ObjectUtils.isNotNull(vendorDetail.getVendorRestrictedIndicator()) && vendorDetail.getVendorRestrictedIndicator() && StringUtils.isEmpty(vendorDetail.getVendorRestrictedReasonText())) { 312 putFieldError(VendorPropertyConstants.VENDOR_RESTRICTED_REASON_TEXT, VendorKeyConstants.ERROR_RESTRICTED_REASON_REQUIRED); 313 return false; 314 } 315 return true; 316 } 317 318 /** 319 * Validates that if vendor is parent, then tax # and tax type combo should be unique by checking for the existence of vendor(s) 320 * with the same tax # and tax type in the existing vendor header table. Ideally we're also supposed to check for pending 321 * vendors, but at the moment, the pending vendors are under research investigation, so we're only checking the existing vendors 322 * for now. If the vendor is a parent and the validation fails, display the actual error message. If the vendor is not a parent 323 * and the validation fails, display the error message that the parent of this vendor needs to be changed, please contact 324 * Purchasing Dept. While checking for the existence of vendors with the same tax # and tax type, exclude the vendors with the 325 * same id. KULPURAP-302: Allow a duplication of a tax number in vendor header if there are only "inactive" header records with 326 * the duplicate record 327 * 328 * @param vendorDetail the VendorDetail object to be validated 329 * @return boolean true if the vendorDetail passes the unique tax # and tax type validation. 330 */ 331 boolean validateParentVendorTaxNumber(VendorDetail vendorDetail) { 332 boolean valid = true; 333 boolean isParent = vendorDetail.isVendorParentIndicator(); 334 Map criteria = new HashMap(); 335 criteria.put(VendorPropertyConstants.VENDOR_TAX_TYPE_CODE, vendorDetail.getVendorHeader().getVendorTaxTypeCode()); 336 criteria.put(VendorPropertyConstants.VENDOR_TAX_NUMBER, vendorDetail.getVendorHeader().getVendorTaxNumber()); 337 criteria.put(KFSPropertyConstants.ACTIVE_INDICATOR, true); 338 Map negativeCriteria = new HashMap(); 339 int existingVendor = 0; 340 // If this is editing an existing vendor, we have to include the current vendor's 341 // header generated id in the negative criteria so that the current vendor is 342 // excluded from the search 343 if (ObjectUtils.isNotNull(vendorDetail.getVendorHeaderGeneratedIdentifier())) { 344 negativeCriteria.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, vendorDetail.getVendorHeaderGeneratedIdentifier()); 345 existingVendor = getBoService().countMatching(VendorDetail.class, criteria, negativeCriteria); 346 } 347 else { 348 // If this is creating a new vendor, we can't include the header generated id 349 // in the negative criteria because it's null, so we'll only look for existing 350 // vendors with the same tax # and tax type regardless of the vendor header generated id. 351 existingVendor = getBoService().countMatching(VendorDetail.class, criteria); 352 } 353 if (existingVendor > 0) { 354 if (isParent) { 355 putFieldError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_TAX_TYPE_AND_NUMBER_COMBO_EXISTS); 356 } 357 else { 358 putFieldError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_PARENT_NEEDS_CHANGED); 359 } 360 valid &= false; 361 } 362 return valid; 363 } 364 365 /** 366 * Validates that the following business rules are satisfied: 1. Tax type cannot be blank if the tax # is not blank. 2. Tax type 367 * cannot be set if the tax # is blank. If the vendor is a parent and the validation fails, we'll display an error message 368 * indicating that the tax type cannot be blank if the tax # is not blank or that the tax type cannot be set if the tax # is 369 * blank. If the vendor is not a parent and the validation fails, we'll display an error message indicating that the parent of 370 * this vendor needs to be changed, please contact Purchasing Dept. 371 * 372 * @param vendorDetail the VendorDetail object to be validated 373 * @return boolean true if the vendor Detail passes the validation and false otherwise. 374 */ 375 boolean validateTaxTypeAndTaxNumberBlankness(VendorDetail vendorDetail) { 376 boolean valid = true; 377 boolean isParent = vendorDetail.isVendorParentIndicator(); 378 if (!StringUtils.isBlank(vendorDetail.getVendorHeader().getVendorTaxNumber()) && (StringUtils.isBlank(vendorDetail.getVendorHeader().getVendorTaxTypeCode()))) { 379 if (isParent) { 380 putFieldError(VendorPropertyConstants.VENDOR_TAX_TYPE_CODE, VendorKeyConstants.ERROR_VENDOR_TAX_TYPE_CANNOT_BE_BLANK); 381 } 382 valid &= false; 383 } 384 else if (StringUtils.isBlank(vendorDetail.getVendorHeader().getVendorTaxNumber()) && !StringUtils.isBlank(vendorDetail.getVendorHeader().getVendorTaxTypeCode())) { 385 if (isParent) { 386 putFieldError(VendorPropertyConstants.VENDOR_TAX_TYPE_CODE, VendorKeyConstants.ERROR_VENDOR_TAX_TYPE_CANNOT_BE_SET); 387 } 388 valid &= false; 389 } 390 391 if (!valid && !isParent) { 392 putFieldError(VendorPropertyConstants.VENDOR_TAX_TYPE_CODE, VendorKeyConstants.ERROR_VENDOR_PARENT_NEEDS_CHANGED); 393 } 394 395 return valid; 396 } 397 398 399 /** 400 * Validates the vendorName, vendorFirstName and vendorLastName fields according to these business rules: 1. At least one of the 401 * three vendor name fields must be filled in. 2. Both of the two ways of entering vendor name (One vendor name field vs 402 * VendorFirstName/VendorLastName) cannot be used 3. If either the vendor first name or the vendor last name have been entered, 403 * the other must be entered. 404 * 405 * @param vendorDetail The VendorDetail object to be validated 406 * @return boolean true if the vendorDetail passes this validation and false otherwise. 407 */ 408 protected boolean validateVendorNames(VendorDetail vendorDetail) { 409 boolean valid = true; 410 if (StringUtils.isBlank(vendorDetail.getVendorName())) { 411 // At least one of the three vendor name fields must be filled in. 412 if (StringUtils.isBlank(vendorDetail.getVendorFirstName()) && StringUtils.isBlank(vendorDetail.getVendorLastName())) { 413 414 putFieldError(VendorPropertyConstants.VENDOR_NAME, VendorKeyConstants.ERROR_VENDOR_NAME_REQUIRED); 415 valid &= false; 416 } 417 // If either the vendor first name or the vendor last name have been entered, the other must be entered. 418 else if (StringUtils.isBlank(vendorDetail.getVendorFirstName()) || StringUtils.isBlank(vendorDetail.getVendorLastName())) { 419 420 putFieldError(VendorPropertyConstants.VENDOR_NAME, VendorKeyConstants.ERROR_VENDOR_BOTH_NAME_REQUIRED); 421 valid &= false; 422 } 423 } 424 else { 425 // Both of the two ways of entering vendor name (One vendor name field vs VendorFirstName/VendorLastName) cannot be used 426 if (!StringUtils.isBlank(vendorDetail.getVendorFirstName()) || !StringUtils.isBlank(vendorDetail.getVendorLastName())) { 427 428 putFieldError(VendorPropertyConstants.VENDOR_NAME, VendorKeyConstants.ERROR_VENDOR_NAME_INVALID); 429 valid &= false; 430 } 431 } 432 return valid; 433 } 434 435 /** 436 * Validates the vendorSoldToNumber field to ensure that it's a valid existing vendor number; 437 * and if so set vendorSoldToName accordingly. 438 * 439 * @param document - the maintenanceDocument being evaluated 440 * @return boolean true if the vendorDetail in the document contains valid vendorSoldToNumber. 441 */ 442 protected boolean validateVendorSoldToNumber(VendorDetail vendorDetail) { 443 boolean valid = true; 444 String vendorSoldToNumber = vendorDetail.getVendorSoldToNumber(); 445 446 // if vendor number is empty, clear all vendorSoldTo fields 447 if (StringUtils.isEmpty(vendorSoldToNumber)) { 448 vendorDetail.setSoldToVendorDetail(null); 449 vendorDetail.setVendorSoldToGeneratedIdentifier(null); 450 vendorDetail.setVendorSoldToAssignedIdentifier(null); 451 vendorDetail.setVendorSoldToNumber(null); 452 vendorDetail.setVendorSoldToName(null); 453 return valid; 454 } 455 456 VendorDetail vendorSoldTo = SpringContext.getBean(VendorService.class).getVendorDetail(vendorSoldToNumber); 457 if (vendorSoldTo != null) { 458 // if vendor number is valid, set all vendorSoldTo fields 459 vendorDetail.setSoldToVendorDetail(vendorSoldTo); 460 vendorDetail.setVendorSoldToGeneratedIdentifier(vendorSoldTo.getVendorHeaderGeneratedIdentifier()); 461 vendorDetail.setVendorSoldToAssignedIdentifier(vendorSoldTo.getVendorDetailAssignedIdentifier()); 462 vendorDetail.setVendorSoldToName(vendorSoldTo.getVendorName()); 463 } 464 else { 465 // otherwise clear vendorSoldToName 466 vendorDetail.setSoldToVendorDetail(null); 467 vendorDetail.setVendorSoldToName(null); 468 valid = false; 469 putFieldError(VendorPropertyConstants.VENDOR_SOLD_TO_NUMBER, VendorKeyConstants.VENDOR_SOLD_TO_NUMBER_INVALID); 470 } 471 472 return valid; 473 } 474 475 /** 476 * Validates the ownership type codes that aren't allowed for the tax type of the vendor. The rules are : 1. If tax type is 477 * "SSN", then check the ownership type against the allowed types for "SSN" in the Rules table. 2. If tax type is "FEIN", then 478 * check the ownership type against the allowed types for "FEIN" in the Rules table. If the vendor is a parent and the 479 * validation fails, display the actual error message. If the vendor is not a parent and the validation fails, display the error 480 * message that the parent of this vendor needs to be changed, please contact Purchasing Dept. 481 * 482 * @param vendorDetail The VendorDetail object to be validated 483 * @return boolean true if the ownership type is allowed and FALSE otherwise. 484 */ 485 private boolean validateOwnershipTypeAllowed(VendorDetail vendorDetail) { 486 boolean valid = true; 487 boolean isParent = vendorDetail.isVendorParentIndicator(); 488 String ownershipTypeCode = vendorDetail.getVendorHeader().getVendorOwnershipCode(); 489 String taxTypeCode = vendorDetail.getVendorHeader().getVendorTaxTypeCode(); 490 if (StringUtils.isNotEmpty(ownershipTypeCode) && StringUtils.isNotEmpty(taxTypeCode)) { 491 if (VendorConstants.TAX_TYPE_FEIN.equals(taxTypeCode)) { 492 if (!SpringContext.getBean(ParameterService.class).getParameterEvaluator(VendorDetail.class, VendorParameterConstants.FEIN_ALLOWED_OWNERSHIP_TYPES, ownershipTypeCode).evaluationSucceeds()) { 493 valid &= false; 494 } 495 } 496 else if (VendorConstants.TAX_TYPE_SSN.equals(taxTypeCode)) { 497 if (!SpringContext.getBean(ParameterService.class).getParameterEvaluator(VendorDetail.class, VendorParameterConstants.SSN_ALLOWED_OWNERSHIP_TYPES, ownershipTypeCode).evaluationSucceeds()) { 498 valid &= false; 499 } 500 } 501 } 502 if (!valid && isParent) { 503 putFieldError(VendorPropertyConstants.VENDOR_OWNERSHIP_CODE, VendorKeyConstants.ERROR_OWNERSHIP_TYPE_CODE_NOT_ALLOWED, new String[] { vendorDetail.getVendorHeader().getVendorOwnership().getVendorOwnershipDescription(), taxTypeCode }); 504 } 505 else if (!valid && !isParent) { 506 putFieldError(VendorPropertyConstants.VENDOR_OWNERSHIP_CODE, VendorKeyConstants.ERROR_VENDOR_PARENT_NEEDS_CHANGED); 507 } 508 return valid; 509 } 510 511 512 /** 513 * Validates that the minimum order amount is less than the maximum allowed amount. 514 * 515 * @param vendorDetail The VendorDetail object to be validated 516 * @return booelan true if the vendorMinimumOrderAmount is less than the maximum allowed amount. 517 */ 518 private boolean validateMinimumOrderAmount(VendorDetail vendorDetail) { 519 boolean valid = true; 520 KualiDecimal minimumOrderAmount = vendorDetail.getVendorMinimumOrderAmount(); 521 if (ObjectUtils.isNotNull(minimumOrderAmount)) { 522 KualiDecimal VENDOR_MIN_ORDER_AMOUNT = new KualiDecimal(SpringContext.getBean(ParameterService.class).getParameterValue(VendorDetail.class, VendorParameterConstants.VENDOR_MIN_ORDER_AMOUNT)); 523 if (ObjectUtils.isNotNull(VENDOR_MIN_ORDER_AMOUNT) && (VENDOR_MIN_ORDER_AMOUNT.compareTo(minimumOrderAmount) < 1) || (minimumOrderAmount.isNegative())) { 524 putFieldError(VendorPropertyConstants.VENDOR_MIN_ORDER_AMOUNT, VendorKeyConstants.ERROR_VENDOR_MAX_MIN_ORDER_AMOUNT, VENDOR_MIN_ORDER_AMOUNT.toString()); 525 valid &= false; 526 } 527 } 528 return valid; 529 } 530 531 /** 532 * Validates that if the ownership category allowed indicator is false, the vendor does not have ownership category. It will 533 * return false if the vendor contains ownership category. If the vendor is a parent and the validation fails, display the 534 * actual error message. If the vendor is not a parent and the validation fails, display the error message that the parent of 535 * this vendor needs to be changed, please contact Purchasing Dept. 536 * 537 * @param vendorDetail The VendorDetail to be validated 538 * @return boolean true if the vendor does not contain ownership category and false otherwise 539 */ 540 private boolean validateOwnershipCategory(VendorDetail vendorDetail) { 541 boolean valid = true; 542 boolean isParent = vendorDetail.isVendorParentIndicator(); 543 OwnershipType ot = vendorDetail.getVendorHeader().getVendorOwnership(); 544 if (ot != null && !ot.getVendorOwnershipCategoryAllowedIndicator()) { 545 if (ObjectUtils.isNotNull(vendorDetail.getVendorHeader().getVendorOwnershipCategory())) { 546 valid &= false; 547 } 548 } 549 if (!valid && isParent) { 550 putFieldError(VendorPropertyConstants.VENDOR_OWNERSHIP_CATEGORY_CODE, VendorKeyConstants.ERROR_OWNERSHIP_CATEGORY_CODE_NOT_ALLOWED, new String[] { vendorDetail.getVendorHeader().getVendorOwnershipCategory().getVendorOwnershipCategoryDescription(), vendorDetail.getVendorHeader().getVendorOwnership().getVendorOwnershipDescription() }); 551 } 552 else if (!valid && !isParent) { 553 putFieldError(VendorPropertyConstants.VENDOR_OWNERSHIP_CODE, VendorKeyConstants.ERROR_VENDOR_PARENT_NEEDS_CHANGED); 554 } 555 return valid; 556 } 557 558 /** 559 * Calls the methods in TaxNumberService to validate the tax number for these business rules: 1. Tax number must be 9 digits and 560 * cannot be all zeros (but can be blank). 2. First three digits of a SSN cannot be 000. 3. First three digits of a SSN cannot 561 * be 666. 4. Middle two digits of a SSN cannot be 00. 5. Last four digits of a SSN cannot be 0000. 6. First two digits of a 562 * FEIN cannot be 00. 7. Check system parameters for not allowed tax numbers 563 * 564 * @param vendorDetail The VendorDetail object to be validated 565 * @return boolean true if the tax number is a valid tax number and false otherwise. 566 */ 567 private boolean validateTaxNumberFromTaxNumberService(VendorDetail vendorDetail) { 568 boolean valid = true; 569 boolean isParent = vendorDetail.isVendorParentIndicator(); 570 String taxNumber = vendorDetail.getVendorHeader().getVendorTaxNumber(); 571 String taxType = vendorDetail.getVendorHeader().getVendorTaxTypeCode(); 572 if (!StringUtils.isEmpty(taxType) && !StringUtils.isEmpty(taxNumber)) { 573 valid = SpringContext.getBean(TaxNumberService.class).isValidTaxNumber(taxNumber, taxType); 574 if (!valid && isParent) { 575 putFieldError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_TAX_NUMBER_INVALID); 576 } 577 valid = SpringContext.getBean(TaxNumberService.class).isAllowedTaxNumber(taxNumber); 578 if (!valid && isParent) { 579 putFieldError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_TAX_NUMBER_NOT_ALLOWED); 580 } 581 } 582 if (!valid && !isParent) { 583 putFieldError(VendorPropertyConstants.VENDOR_TAX_NUMBER, VendorKeyConstants.ERROR_VENDOR_PARENT_NEEDS_CHANGED); 584 } 585 586 return valid; 587 } 588 589 /** 590 * Validates commodity code related rules. 591 * 592 * @param document MaintenanceDocument 593 * @return boolean false or true 594 */ 595 boolean processCommodityCodeValidation(MaintenanceDocument document) { 596 boolean valid = true; 597 List<VendorCommodityCode> vendorCommodities = newVendor.getVendorCommodities(); 598 boolean commodityCodeRequired = newVendor.getVendorHeader().getVendorType().isCommodityRequiredIndicator(); 599 if (commodityCodeRequired) { 600 if (vendorCommodities.size() == 0) { 601 //display error that the commodity code is required for this type of vendor. 602 String propertyName = "add." + VendorPropertyConstants.VENDOR_COMMODITIES_CODE_PURCHASING_COMMODITY_CODE; 603 putFieldError(propertyName, VendorKeyConstants.ERROR_VENDOR_COMMODITY_CODE_IS_REQUIRED_FOR_THIS_VENDOR_TYPE); 604 valid = false; 605 } 606 //We only need to validate the default indicator if there is at least 607 //one commodity code for the vendor. 608 else if (vendorCommodities.size() > 0) { 609 valid &= validateCommodityCodeDefaultIndicator(vendorCommodities); 610 } 611 } 612 else if (vendorCommodities.size() > 0) { 613 //If the commodity code is not required, but the vendor contains at least one commodity code, 614 //we have to check that there is only one commodity code with default indicator = Y. 615 int defaultCount = 0; 616 for (int i=0; i < vendorCommodities.size(); i++) { 617 VendorCommodityCode vcc = vendorCommodities.get(i); 618 if (vcc.isCommodityDefaultIndicator()) { 619 defaultCount ++; 620 if (defaultCount > 1) { 621 valid = false; 622 String propertyName = VendorPropertyConstants.VENDOR_COMMODITIES_CODE + "[" + i + "]." + VendorPropertyConstants.VENDOR_COMMODITIES_DEFAULT_INDICATOR; 623 putFieldError(propertyName, VendorKeyConstants.ERROR_VENDOR_COMMODITY_CODE_REQUIRE_ONE_DEFAULT_IND); 624 break; 625 } 626 } 627 } 628 } 629 630 return valid; 631 } 632 633 /** 634 * Validates that there is one and only one default indicator selected 635 * for commodity code if the vendor contains at least one commodity code. 636 * 637 * @param vendorCommodities the list of VendorCommodityCode to be validated 638 * @return boolean true or false 639 */ 640 private boolean validateCommodityCodeDefaultIndicator(List<VendorCommodityCode> vendorCommodities) { 641 boolean valid = true; 642 643 boolean foundDefaultIndicator = false; 644 for (int i=0; i < vendorCommodities.size(); i++) { 645 VendorCommodityCode vcc = vendorCommodities.get(i); 646 if (vcc.isCommodityDefaultIndicator()) { 647 if (!foundDefaultIndicator) { 648 foundDefaultIndicator = true; 649 } 650 else { 651 //display error that there can only be 1 commodity code with default indicator = true. 652 String propertyName = VendorPropertyConstants.VENDOR_COMMODITIES_CODE + "[" + i + "]." + VendorPropertyConstants.VENDOR_COMMODITIES_DEFAULT_INDICATOR; 653 putFieldError(propertyName, VendorKeyConstants.ERROR_VENDOR_COMMODITY_CODE_REQUIRE_ONE_DEFAULT_IND); 654 valid = false; 655 } 656 } 657 } 658 if (!foundDefaultIndicator && vendorCommodities.size() > 0) { 659 //display error that there must be one commodity code selected as the default commodity code for the vendor. 660 String propertyName = VendorPropertyConstants.VENDOR_COMMODITIES_CODE + "[0]." + VendorPropertyConstants.VENDOR_COMMODITIES_DEFAULT_INDICATOR; 661 putFieldError(propertyName, VendorKeyConstants.ERROR_VENDOR_COMMODITY_CODE_REQUIRE_ONE_DEFAULT_IND); 662 valid = false; 663 } 664 return valid; 665 } 666 667 /** 668 * Validates vendor address fields. 669 * 670 * @param document MaintenanceDocument 671 * @return boolean false or true 672 */ 673 boolean processAddressValidation(MaintenanceDocument document) { 674 boolean valid = true; 675 boolean validAddressType = false; 676 677 List<VendorAddress> addresses = newVendor.getVendorAddresses(); 678 String vendorTypeCode = newVendor.getVendorHeader().getVendorTypeCode(); 679 String vendorAddressTypeRequiredCode = newVendor.getVendorHeader().getVendorType().getVendorAddressTypeRequiredCode(); 680 681 for (int i = 0; i < addresses.size(); i++) { 682 VendorAddress address = addresses.get(i); 683 String errorPath = MAINTAINABLE_ERROR_PREFIX + VendorPropertyConstants.VENDOR_ADDRESS + "[" + i + "]"; 684 GlobalVariables.getMessageMap().clearErrorPath(); 685 GlobalVariables.getMessageMap().addToErrorPath(errorPath); 686 687 this.getDictionaryValidationService().validateBusinessObject(address); 688 if (!GlobalVariables.getMessageMap().isEmpty()) { 689 valid = false; 690 } 691 692 if (address.getVendorAddressTypeCode().equals(vendorAddressTypeRequiredCode)) { 693 validAddressType = true; 694 } 695 696 valid &= checkFaxNumber(address); 697 valid &= checkAddressCountryEmptyStateZip(address); 698 699 GlobalVariables.getMessageMap().clearErrorPath(); 700 } 701 702 // validate Address Type 703 String vendorAddressTabPrefix = KFSConstants.ADD_PREFIX + "." + VendorPropertyConstants.VENDOR_ADDRESS + "."; 704 if (!StringUtils.isBlank(vendorTypeCode) && !StringUtils.isBlank(vendorAddressTypeRequiredCode) && !validAddressType) { 705 String[] parameters = new String[] { vendorTypeCode, vendorAddressTypeRequiredCode }; 706 putFieldError(vendorAddressTabPrefix + VendorPropertyConstants.VENDOR_ADDRESS_TYPE_CODE, VendorKeyConstants.ERROR_ADDRESS_TYPE, parameters); 707 String addressLine1Label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(VendorAddress.class, VendorPropertyConstants.VENDOR_ADDRESS_LINE_1); 708 String addressCityLabel = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(VendorAddress.class, VendorPropertyConstants.VENDOR_ADDRESS_CITY); 709 String addressCountryLabel = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(VendorAddress.class, VendorPropertyConstants.VENDOR_ADDRESS_COUNTRY); 710 putFieldError(vendorAddressTabPrefix + VendorPropertyConstants.VENDOR_ADDRESS_LINE_1, KFSKeyConstants.ERROR_REQUIRED, addressLine1Label); 711 putFieldError(vendorAddressTabPrefix + VendorPropertyConstants.VENDOR_ADDRESS_CITY, KFSKeyConstants.ERROR_REQUIRED, addressCityLabel); 712 putFieldError(vendorAddressTabPrefix + VendorPropertyConstants.VENDOR_ADDRESS_COUNTRY, KFSKeyConstants.ERROR_REQUIRED, addressCountryLabel); 713 valid = false; 714 } 715 716 valid &= validateDefaultAddressCampus(newVendor); 717 718 // Check to see if all divisions have one desired address for this vendor type 719 Map fieldValues = new HashMap(); 720 fieldValues.put(VendorPropertyConstants.VENDOR_HEADER_GENERATED_ID, newVendor.getVendorHeaderGeneratedIdentifier()); 721 // Find all the addresses for this vendor and its divisions: 722 List<VendorAddress> vendorDivisionAddresses = new ArrayList(SpringContext.getBean(BusinessObjectService.class).findMatchingOrderBy(VendorAddress.class, fieldValues, VendorPropertyConstants.VENDOR_DETAIL_ASSIGNED_ID, true)); 723 724 // This set stores the vendorDetailedAssignedIds for the vendor divisions which is 725 // bascically the division numbers 0, 1, 2, ... 726 HashSet<Integer> vendorDetailedIds = new HashSet(); 727 // This set stores the vendor division numbers of the ones which have one address of the desired type 728 HashSet<Integer> vendorDivisionsIdsWithDesiredAddressType = new HashSet(); 729 730 for (VendorAddress vendorDivisionAddress : vendorDivisionAddresses) { 731 // We need to exclude the first one Since we already checked for this in valid AddressType above. 732 if (vendorDivisionAddress.getVendorDetailAssignedIdentifier() != 0) { 733 vendorDetailedIds.add(vendorDivisionAddress.getVendorDetailAssignedIdentifier()); 734 if (vendorDivisionAddress.getVendorAddressTypeCode().equalsIgnoreCase(vendorAddressTypeRequiredCode)) { 735 vendorDivisionsIdsWithDesiredAddressType.add(vendorDivisionAddress.getVendorDetailAssignedIdentifier()); 736 } 737 } 738 } 739 740 // If the number of divisions with the desired address type is less than the number of divisions for his vendor 741 if (vendorDivisionsIdsWithDesiredAddressType.size() < vendorDetailedIds.size()) { 742 Iterator itr = vendorDetailedIds.iterator(); 743 Integer value; 744 String vendorId; 745 746 while (itr.hasNext()) { 747 value = (Integer) itr.next(); 748 if (!vendorDivisionsIdsWithDesiredAddressType.contains(value)) { 749 vendorId = newVendor.getVendorHeaderGeneratedIdentifier().toString() + '-' + value.toString(); 750 String[] parameters = new String[] { vendorId, vendorTypeCode, vendorAddressTypeRequiredCode }; 751 putFieldError(vendorAddressTabPrefix + VendorPropertyConstants.VENDOR_TYPE_CODE, VendorKeyConstants.ERROR_ADDRESS_TYPE_DIVISIONS, parameters); 752 valid = false; 753 } 754 } 755 } 756 757 return valid; 758 } 759 760 /** 761 * Validates that if US is selected for the country then the state and zip cannot be empty. Also, 762 * zip format validation is added if US is selected. 763 * 764 * @param addresses VendorAddress which is being validated 765 * @return boolean false if the country is United States and there is no state or zip code 766 */ 767 boolean checkAddressCountryEmptyStateZip(VendorAddress address) { 768 //GlobalVariables.getMessageMap().clearErrorPath(); 769 //GlobalVariables.getMessageMap().addToErrorPath(KFSPropertyConstants.DOCUMENT + "." + KFSPropertyConstants.NEW_MAINTAINABLE_OBJECT + "." + VendorPropertyConstants.VENDOR_ADDRESS); 770 boolean valid = SpringContext.getBean(PostalCodeValidationService.class).validateAddress(address.getVendorCountryCode(), address.getVendorStateCode(), address.getVendorZipCode(), VendorPropertyConstants.VENDOR_ADDRESS_STATE, VendorPropertyConstants.VENDOR_ADDRESS_ZIP); 771 //GlobalVariables.getMessageMap().clearErrorPath(); 772 return valid; 773 } 774 775 /** 776 * Checks if the "allow default indicator" is true or false for this address. 777 * 778 * @param addresses VendorAddress which is being validated 779 * @return boolean false or true 780 */ 781 782 boolean findAllowDefaultAddressIndicatorHelper(VendorAddress vendorAddress) { 783 784 AddressType addressType = new AddressType(); 785 786 addressType = vendorAddress.getVendorAddressType(); 787 if (ObjectUtils.isNull(addressType)) { 788 return false; 789 } 790 // Retrieving the Default Address Indicator for this Address Type: 791 return addressType.getVendorDefaultIndicator(); 792 793 } 794 795 /** 796 * If add button is selected on Default Address, checks if the allow default indicator is set to false for this address type 797 * then it does not allow user to select a default address for this address and if it is true then it allows only one campus to 798 * be default for this address. 799 * 800 * @param vendorDetail VendorDetail document 801 * @param addedDefaultAddress VendorDefaultAddress which is being added 802 * @param parent The VendorAddress which we are adding a default address to it 803 * @return boolean false or true 804 */ 805 boolean checkDefaultAddressCampus(VendorDetail vendorDetail, VendorDefaultAddress addedDefaultAddress, VendorAddress parent) { 806 VendorAddress vendorAddress = parent; 807 if (ObjectUtils.isNull(vendorAddress)) { 808 return false; 809 } 810 811 int j = vendorDetail.getVendorAddresses().indexOf(vendorAddress); 812 String errorPath = MAINTAINABLE_ERROR_PREFIX + VendorPropertyConstants.VENDOR_ADDRESS + "[" + j + "]"; 813 GlobalVariables.getMessageMap().addToErrorPath(errorPath); 814 815 // Retrieving the Default Address Indicator for this Address Type: 816 boolean allowDefaultAddressIndicator = findAllowDefaultAddressIndicatorHelper(vendorAddress); 817 String addedAddressCampusCode = addedDefaultAddress.getVendorCampusCode(); 818 String addedAddressTypeCode = vendorAddress.getVendorAddressTypeCode(); 819 820 // if the selected address type does not allow defaults, then the user should not be allowed to 821 // select the default indicator or add any campuses to the address 822 if (allowDefaultAddressIndicator == false) { 823 String[] parameters = new String[] { addedAddressTypeCode }; 824 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS + "[" + 0 + "]." + VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS_CAMPUS, VendorKeyConstants.ERROR_ADDRESS_DEFAULT_CAMPUS_NOT_ALLOWED, parameters); 825 return false; 826 } 827 828 List<VendorDefaultAddress> vendorDefaultAddresses = vendorAddress.getVendorDefaultAddresses(); 829 for (int i = 0; i < vendorDefaultAddresses.size(); i++) { 830 VendorDefaultAddress vendorDefaultAddress = vendorDefaultAddresses.get(i); 831 if (vendorDefaultAddress.getVendorCampusCode().equalsIgnoreCase(addedAddressCampusCode)) { 832 String[] parameters = new String[] { addedAddressCampusCode, addedAddressTypeCode }; 833 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS + "[" + i + "]." + VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS_CAMPUS, VendorKeyConstants.ERROR_ADDRESS_DEFAULT_CAMPUS, parameters); 834 return false; 835 } 836 } 837 838 return true; 839 } 840 841 /** 842 * Checks if the allow default indicator is set to false for this address the default indicator cannot be set to true/yes. If 843 * "allow default indicator" is set to true/yes for address type, one address must have the default indicator set (no more, no 844 * less) and only one campus to be set as default for this address. 845 * 846 * @param vendorDetail VendorDetail document 847 * @return boolean false or true 848 */ 849 850 boolean validateDefaultAddressCampus(VendorDetail vendorDetail) { 851 List<VendorAddress> vendorAddresses = vendorDetail.getVendorAddresses(); 852 String addressTypeCode; 853 String campusCode; 854 boolean valid = true; 855 boolean previousValue = false; 856 857 // This is a HashMap to store the default Address Type Codes and their associated default Indicator 858 HashMap addressTypeCodeDefaultIndicator = new HashMap(); 859 860 // This is a HashMap to store Address Type Codes and Address Campus Codes for Default Addresses 861 HashMap addressTypeDefaultCampus = new HashMap(); 862 863 // This is a HashSet for storing only the Address Type Codes which have at least one default Indicator set to true 864 HashSet addressTypesHavingDefaultTrue = new HashSet(); 865 866 int i = 0; 867 for (VendorAddress address : vendorAddresses) { 868 addressTypeCode = address.getVendorAddressTypeCode(); 869 String errorPath = MAINTAINABLE_ERROR_PREFIX + VendorPropertyConstants.VENDOR_ADDRESS + "[" + i + "]"; 870 GlobalVariables.getMessageMap().addToErrorPath(errorPath); 871 String[] parameters = new String[] { addressTypeCode }; 872 873 // If "allow default indicator" is set to true/yes for address type, one address must have the default indicator set (no 874 // more, no less). 875 // For example, if a vendor contains three PO type addresses and the PO address type is set to allow defaults in the 876 // address type table, 877 // then only one of these PO addresses can have the default indicator set to true/yes. 878 879 if (findAllowDefaultAddressIndicatorHelper(address)) { 880 if (address.isVendorDefaultAddressIndicator()) { 881 addressTypesHavingDefaultTrue.add(addressTypeCode); 882 } 883 if (!addressTypeCodeDefaultIndicator.isEmpty() && addressTypeCodeDefaultIndicator.containsKey(addressTypeCode)) { 884 previousValue = ((Boolean) addressTypeCodeDefaultIndicator.get(addressTypeCode)).booleanValue(); 885 } 886 887 if (addressTypeCodeDefaultIndicator.put(addressTypeCode, address.isVendorDefaultAddressIndicator()) != null && previousValue && address.isVendorDefaultAddressIndicator()) { 888 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS_INDICATOR, VendorKeyConstants.ERROR_ADDRESS_DEFAULT_INDICATOR, parameters); 889 valid = false; 890 } 891 892 } 893 // If "allow default indicator" is set to false/no for address type, the default indicator cannot be set to true/yes. 894 else { 895 if (address.isVendorDefaultAddressIndicator()) { 896 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS_INDICATOR, VendorKeyConstants.ERROR_ADDRESS_DEFAULT_ADDRESS_NOT_ALLOWED, parameters); 897 valid = false; 898 } 899 900 } 901 902 List<VendorDefaultAddress> vendorDefaultAddresses = address.getVendorDefaultAddresses(); 903 904 // If "allow default indicator" is set to true/yes for address type, a campus can only be set on one of each type of 905 // Address. 906 // For example, Bloomington can not be included in the campus list for two PO type addresses. 907 // Each campus can only have one default address. 908 int j = 0; 909 for (VendorDefaultAddress defaultAddress : vendorDefaultAddresses) { 910 campusCode = (String) addressTypeDefaultCampus.put(addressTypeCode, defaultAddress.getVendorCampusCode()); 911 if (StringUtils.isNotBlank(campusCode) && campusCode.equalsIgnoreCase(defaultAddress.getVendorCampusCode())) { 912 String[] newParameters = new String[] { defaultAddress.getVendorCampusCode(), addressTypeCode }; 913 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS + "[" + j + "]." + VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS_CAMPUS, VendorKeyConstants.ERROR_ADDRESS_DEFAULT_CAMPUS, newParameters); 914 valid = false; 915 } 916 j++; 917 } 918 i++; 919 GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); 920 } 921 922 // If "allow default indicator" is set to true/yes for address type, one address must have the default indicator set to true 923 if (!addressTypeCodeDefaultIndicator.isEmpty()) { 924 Set<String> addressTypes = addressTypeCodeDefaultIndicator.keySet(); 925 926 for (String addressType : addressTypes) { 927 if (!addressTypesHavingDefaultTrue.contains(addressType)) { 928 String[] parameters = new String[] { addressType }; 929 int addressIndex = 0; 930 for (VendorAddress address : vendorAddresses) { 931 String propertyName = VendorPropertyConstants.VENDOR_ADDRESS + "[" + addressIndex + "]." + VendorPropertyConstants.VENDOR_DEFAULT_ADDRESS_INDICATOR; 932 if (address.getVendorAddressType().getVendorAddressTypeCode().equalsIgnoreCase(addressType)) { 933 putFieldError(propertyName, VendorKeyConstants.ERROR_ADDRESS_DEFAULT_INDICATOR, parameters); 934 break; 935 } 936 addressIndex++; 937 } 938 valid = false; 939 } 940 941 } 942 } 943 944 return valid; 945 } 946 947 948 /** 949 * Validates that the Vendor Fax Number is a valid phone number. 950 * 951 * @param addresses VendorAddress instance 952 * @return boolean false or true 953 */ 954 boolean checkFaxNumber(VendorAddress address) { 955 boolean valid = true; 956 String faxNumber = address.getVendorFaxNumber(); 957 if (StringUtils.isNotEmpty(faxNumber) && !SpringContext.getBean(PhoneNumberService.class).isValidPhoneNumber(faxNumber)) { 958 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_FAX_NUMBER, VendorKeyConstants.ERROR_FAX_NUMBER); 959 valid &= false; 960 } 961 return valid; 962 } 963 964 /** 965 * A stub method as placeholder for future Contact Validation 966 * 967 * @param document MaintenanceDocument instance 968 * @return boolean false or true 969 */ 970 private boolean processContactValidation(MaintenanceDocument document) { 971 boolean valid = true; 972 int i = 0; 973 for (VendorContact contact : newVendor.getVendorContacts()) { 974 String errorPath = MAINTAINABLE_ERROR_PREFIX + VendorPropertyConstants.VENDOR_CONTACT + "[" + i + "]"; 975 GlobalVariables.getMessageMap().addToErrorPath(errorPath); 976 977 this.getDictionaryValidationService().validateBusinessObject(contact); 978 if (!GlobalVariables.getMessageMap().isEmpty()) { 979 valid = false; 980 } 981 i++; 982 GlobalVariables.getMessageMap().clearErrorPath(); 983 } 984 return valid; 985 } 986 987 /** 988 * Validates vendor customer numbers 989 * 990 * @param document MaintenanceDocument instance 991 * @return boolean false or true 992 */ 993 private boolean processCustomerNumberValidation(MaintenanceDocument document) { 994 boolean valid = true; 995 996 List<VendorCustomerNumber> customerNumbers = newVendor.getVendorCustomerNumbers(); 997 for (VendorCustomerNumber customerNumber : customerNumbers) { 998 valid &= validateVendorCustomerNumber(customerNumber); 999 } 1000 return valid; 1001 } 1002 1003 /** 1004 * Validates vendor customer number. The chart and org must exist in the database. 1005 * 1006 * @param customerNumber VendorCustomerNumber 1007 * @return boolean false or true 1008 */ 1009 boolean validateVendorCustomerNumber(VendorCustomerNumber customerNumber) { 1010 boolean valid = true; 1011 1012 // The chart and org must exist in the database. 1013 String chartOfAccountsCode = customerNumber.getChartOfAccountsCode(); 1014 String orgCode = customerNumber.getVendorOrganizationCode(); 1015 if (!StringUtils.isBlank(chartOfAccountsCode) && !StringUtils.isBlank(orgCode)) { 1016 Map chartOrgMap = new HashMap(); 1017 chartOrgMap.put("chartOfAccountsCode", chartOfAccountsCode); 1018 if (SpringContext.getBean(BusinessObjectService.class).countMatching(Chart.class, chartOrgMap) < 1) { 1019 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CUSTOMER_NUMBER_CHART_OF_ACCOUNTS_CODE, KFSKeyConstants.ERROR_EXISTENCE, chartOfAccountsCode); 1020 valid &= false; 1021 } 1022 chartOrgMap.put("organizationCode", orgCode); 1023 if (SpringContext.getBean(BusinessObjectService.class).countMatching(Organization.class, chartOrgMap) < 1) { 1024 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CUSTOMER_NUMBER_ORGANIZATION_CODE, KFSKeyConstants.ERROR_EXISTENCE, orgCode); 1025 valid &= false; 1026 } 1027 } 1028 return valid; 1029 } 1030 1031 /** 1032 * Validates vendor contract. If the vendorContractAllowedIndicator is false, it cannot have vendor contracts, then return false 1033 * 1034 * @param document MaintenanceDocument 1035 * @return boolean false or true 1036 */ 1037 private boolean processContractValidation(MaintenanceDocument document) { 1038 boolean valid = true; 1039 List<VendorContract> contracts = newVendor.getVendorContracts(); 1040 if (ObjectUtils.isNull(contracts)) { 1041 return valid; 1042 } 1043 1044 // If the vendorContractAllowedIndicator is false, it cannot have vendor contracts, return false; 1045 if (contracts.size() > 0 && !newVendor.getVendorHeader().getVendorType().isVendorContractAllowedIndicator()) { 1046 valid = false; 1047 String errorPath = MAINTAINABLE_ERROR_PREFIX + VendorPropertyConstants.VENDOR_CONTRACT + "[0]"; 1048 GlobalVariables.getMessageMap().addToErrorPath(errorPath); 1049 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_NAME, VendorKeyConstants.ERROR_VENDOR_CONTRACT_NOT_ALLOWED); 1050 GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); 1051 return valid; 1052 } 1053 1054 for (int i = 0; i < contracts.size(); i++) { 1055 VendorContract contract = contracts.get(i); 1056 1057 String errorPath = MAINTAINABLE_ERROR_PREFIX + VendorPropertyConstants.VENDOR_CONTRACT + "[" + i + "]"; 1058 GlobalVariables.getMessageMap().addToErrorPath(errorPath); 1059 1060 valid &= validateVendorContractPOLimitAndExcludeFlagCombination(contract); 1061 valid &= validateVendorContractBeginEndDates(contract); 1062 valid &= processContractB2BValidation(document, contract, i); 1063 1064 GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); 1065 } 1066 1067 1068 return valid; 1069 } 1070 1071 /** 1072 * Validates that the proper combination of Exclude Indicator and APO Amount is present on a vendor contract. Do not perform 1073 * this validation on Contract add line as the user cannot currently enter the sub-collection of contract-orgs so we should not 1074 * force this until the document is submitted. The rules are : 1. Must enter a Default APO Limit or at least one organization 1075 * with an APO Amount. 2. If the Exclude Indicator for an organization is N, an organization APO Amount is required. 3. If the 1076 * Exclude Indicator for an organization is Y, the organization APO Amount is not allowed. 1077 * 1078 * @param contract VendorContract 1079 * @return boolean true if the proper combination of Exclude Indicator and APO Amount is present, otherwise flase. 1080 */ 1081 boolean validateVendorContractPOLimitAndExcludeFlagCombination(VendorContract contract) { 1082 boolean valid = true; 1083 boolean NoOrgHasApoLimit = true; 1084 1085 List<VendorContractOrganization> organizations = contract.getVendorContractOrganizations(); 1086 if (ObjectUtils.isNotNull(organizations)) { 1087 int organizationCounter = 0; 1088 for (VendorContractOrganization organization : organizations) { 1089 if (ObjectUtils.isNotNull(organization.getVendorContractPurchaseOrderLimitAmount())) { 1090 NoOrgHasApoLimit = false; 1091 } 1092 valid &= validateVendorContractOrganization(organization); 1093 organizationCounter++; 1094 } 1095 } 1096 if (NoOrgHasApoLimit && ObjectUtils.isNull(contract.getOrganizationAutomaticPurchaseOrderLimit())) { 1097 // Rule #1 in the above java doc has been violated. 1098 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_DEFAULT_APO_LIMIT, VendorKeyConstants.ERROR_VENDOR_CONTRACT_NO_APO_LIMIT); 1099 valid &= false; 1100 } 1101 return valid; 1102 } 1103 1104 /** 1105 * Validates that: 1. If the VendorContractBeginningDate is entered then the VendorContractEndDate is also entered, and vice 1106 * versa. 2. If both dates are entered, the VendorContractBeginningDate is before the VendorContractEndDate. The date fields are 1107 * required so we should know that we have valid dates. 1108 * 1109 * @param contract VendorContract 1110 * @return boolean true if the beginning date is before the end date, false if only one date is entered or the beginning date is 1111 * after the end date. 1112 */ 1113 boolean validateVendorContractBeginEndDates(VendorContract contract) { 1114 boolean valid = true; 1115 1116 if (ObjectUtils.isNotNull(contract.getVendorContractBeginningDate()) && ObjectUtils.isNull(contract.getVendorContractEndDate())) { 1117 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_END_DATE, VendorKeyConstants.ERROR_VENDOR_CONTRACT_BEGIN_DATE_NO_END_DATE); 1118 valid &= false; 1119 } 1120 else { 1121 if (ObjectUtils.isNull(contract.getVendorContractBeginningDate()) && ObjectUtils.isNotNull(contract.getVendorContractEndDate())) { 1122 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_BEGIN_DATE, VendorKeyConstants.ERROR_VENDOR_CONTRACT_END_DATE_NO_BEGIN_DATE); 1123 valid &= false; 1124 } 1125 } 1126 if (valid && ObjectUtils.isNotNull(contract.getVendorContractBeginningDate()) && ObjectUtils.isNotNull(contract.getVendorContractEndDate())) { 1127 if (contract.getVendorContractBeginningDate().after(contract.getVendorContractEndDate())) { 1128 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_BEGIN_DATE, VendorKeyConstants.ERROR_VENDOR_CONTRACT_BEGIN_DATE_AFTER_END); 1129 valid &= false; 1130 } 1131 } 1132 1133 return valid; 1134 } 1135 1136 /** 1137 * Validates a vendor contract organization. The rules are : 1. If the Exclude Indicator for the organization is N, an 1138 * organization APO Amount is required. 2. If the Exclude Indicator for the organization is Y, an organization APO Amount is not 1139 * allowed. 3. The chart and org for the organization must exist in the database. 1140 * 1141 * @param organization VendorContractOrganization 1142 * @return boolean true if these three rules are passed, otherwise false. 1143 */ 1144 boolean validateVendorContractOrganization(VendorContractOrganization organization) { 1145 boolean valid = true; 1146 1147 boolean isExcluded = organization.isVendorContractExcludeIndicator(); 1148 if (isExcluded) { 1149 if (ObjectUtils.isNotNull(organization.getVendorContractPurchaseOrderLimitAmount())) { 1150 // Rule #2 in the above java doc has been violated. 1151 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_ORGANIZATION_APO_LIMIT, VendorKeyConstants.ERROR_VENDOR_CONTRACT_ORG_EXCLUDED_WITH_APO_LIMIT); 1152 valid &= false; 1153 } 1154 } 1155 else { // isExcluded = false 1156 if (ObjectUtils.isNull(organization.getVendorContractPurchaseOrderLimitAmount())) { 1157 // Rule #1 in the above java doc has been violated. 1158 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_ORGANIZATION_APO_LIMIT, VendorKeyConstants.ERROR_VENDOR_CONTRACT_ORG_NOT_EXCLUDED_NO_APO_LIMIT); 1159 valid &= false; 1160 } 1161 } 1162 1163 // The chart and org must exist in the database. 1164 String chartOfAccountsCode = organization.getChartOfAccountsCode(); 1165 String orgCode = organization.getOrganizationCode(); 1166 if (!StringUtils.isBlank(chartOfAccountsCode) && !StringUtils.isBlank(orgCode)) { 1167 Map chartOrgMap = new HashMap(); 1168 chartOrgMap.put("chartOfAccountsCode", chartOfAccountsCode); 1169 if (SpringContext.getBean(BusinessObjectService.class).countMatching(Chart.class, chartOrgMap) < 1) { 1170 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_CHART_OF_ACCOUNTS_CODE, KFSKeyConstants.ERROR_EXISTENCE, chartOfAccountsCode); 1171 valid &= false; 1172 } 1173 chartOrgMap.put("organizationCode", orgCode); 1174 if (SpringContext.getBean(BusinessObjectService.class).countMatching(Organization.class, chartOrgMap) < 1) { 1175 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_ORGANIZATION_CODE, KFSKeyConstants.ERROR_EXISTENCE, orgCode); 1176 valid &= false; 1177 } 1178 } 1179 1180 return valid; 1181 } 1182 1183 /** 1184 * Validates vendor contracts against single B2B restriction on a vendor/campus basis. Only one B2B contract allowed per vendor/campus 1185 * 1186 * @param document MaintenanceDocument 1187 * @return boolean false or true 1188 */ 1189 private boolean processContractB2BValidation(MaintenanceDocument document, VendorContract contract, int contractPos) { 1190 boolean valid = true; 1191 List<Integer> indexOfB2BContracts = new ArrayList(); 1192 //list of contracts already associated with vendor 1193 List<VendorContract> contracts = newVendor.getVendorContracts(); 1194 if (ObjectUtils.isNull(contracts)) { 1195 return valid; 1196 } 1197 //find all b2b contracts for comparison 1198 if(contractPos == -1){ 1199 if(contract.getVendorB2bIndicator()){ 1200 for (int i = 0; i < contracts.size(); i++) { 1201 VendorContract vndrContract = contracts.get(i); 1202 if(vndrContract.getVendorB2bIndicator()){ 1203 //check for duplicate campus; vendor is implicitly the same 1204 if(contract.getVendorCampusCode().equals(vndrContract.getVendorCampusCode())){ 1205 valid &= false; 1206 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_B2B_INDICATOR, VendorKeyConstants.ERROR_VENDOR_CONTRACT_B2B_LIMIT_EXCEEDED, contract.getVendorCampusCode()); 1207 } 1208 } 1209 } 1210 } 1211 } else 1212 { 1213 if(contract.getVendorB2bIndicator()){ 1214 for (int i = 0; i < contracts.size(); i++) { 1215 VendorContract vndrContract = contracts.get(i); 1216 if(vndrContract.getVendorB2bIndicator()){ 1217 //make sure we're not checking contracts against themselves 1218 if(i != contractPos){ 1219 //check for duplicate campus; vendor is implicitly the same 1220 if(contract.getVendorCampusCode().equals(vndrContract.getVendorCampusCode())){ 1221 valid &= false; 1222 String [] errorArray = new String []{contract.getVendorContractName(), contract.getVendorCampusCode()}; 1223 GlobalVariables.getMessageMap().putError(VendorPropertyConstants.VENDOR_CONTRACT_B2B_INDICATOR, VendorKeyConstants.ERROR_VENDOR_CONTRACT_B2B_LIMIT_EXCEEDED_DB, errorArray); 1224 } 1225 } 1226 } 1227 } 1228 } 1229 } 1230 return valid; 1231 } 1232 1233 /** 1234 * Validates business rules for VendorDetail document collection add lines. Add lines are the initial lines on a collections, 1235 * i.e. the ones next to the "Add" button 1236 * 1237 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, 1238 * java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject) 1239 */ 1240 @Override 1241 public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) { 1242 boolean success = true; 1243 1244 // this incoming bo needs to be refreshed because it doesn't have its subobjects setup 1245 bo.refreshNonUpdateableReferences(); 1246 1247 if (bo instanceof VendorAddress) { 1248 VendorAddress address = (VendorAddress) bo; 1249 success &= checkAddressCountryEmptyStateZip(address); 1250 VendorDetail vendorDetail = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); 1251 } 1252 if (bo instanceof VendorContract) { 1253 VendorContract contract = (VendorContract) bo; 1254 success &= validateVendorContractBeginEndDates(contract); 1255 success &= processContractB2BValidation(document,contract, -1); 1256 } 1257 if (bo instanceof VendorContractOrganization) { 1258 VendorContractOrganization contractOrg = (VendorContractOrganization) bo; 1259 success &= validateVendorContractOrganization(contractOrg); 1260 } 1261 if (bo instanceof VendorCustomerNumber) { 1262 VendorCustomerNumber customerNumber = (VendorCustomerNumber) bo; 1263 success &= validateVendorCustomerNumber(customerNumber); 1264 } 1265 if (bo instanceof VendorDefaultAddress) { 1266 VendorDefaultAddress defaultAddress = (VendorDefaultAddress) bo; 1267 String parentName = StringUtils.substringBeforeLast(collectionName, "."); 1268 VendorAddress parent = (VendorAddress) ObjectUtils.getPropertyValue(this.getNewBo(), parentName); 1269 VendorDetail vendorDetail = (VendorDetail) document.getNewMaintainableObject().getBusinessObject(); 1270 success &= checkDefaultAddressCampus(vendorDetail, defaultAddress, parent); 1271 } 1272 1273 return success; 1274 } 1275 1276 /** 1277 * Validates the rule that if a document has a federal witholding tax begin date and end date, the begin date should come before 1278 * the end date. 1279 * 1280 * @param vdDocument VendorDetail 1281 * @return boolean false or true 1282 */ 1283 private boolean validateVendorWithholdingTaxDates(VendorDetail vdDocument) { 1284 boolean valid = true; 1285 DateTimeService dateTimeService = SpringContext.getBean(DateTimeService.class); 1286 1287 Date beginDate = vdDocument.getVendorHeader().getVendorFederalWithholdingTaxBeginningDate(); 1288 Date endDate = vdDocument.getVendorHeader().getVendorFederalWithholdingTaxEndDate(); 1289 if (ObjectUtils.isNotNull(beginDate) && ObjectUtils.isNotNull(endDate)) { 1290 if (dateTimeService.dateDiff(beginDate, endDate, false) <= 0) { 1291 putFieldError(VendorPropertyConstants.VENDOR_FEDERAL_WITHOLDING_TAX_BEGINNING_DATE, VendorKeyConstants.ERROR_VENDOR_TAX_BEGIN_DATE_AFTER_END); 1292 valid &= false; 1293 } 1294 } 1295 return valid; 1296 1297 } 1298 1299 /** 1300 * Validates the rule that both w9 received and w-8ben cannot be set to yes 1301 * 1302 * @param vdDocument VendorDetail 1303 * @return boolean false or true 1304 */ 1305 private boolean validateVendorW8BenOrW9ReceivedIndicator(VendorDetail vdDocument) { 1306 boolean valid = true; 1307 1308 if (ObjectUtils.isNotNull(vdDocument.getVendorHeader().getVendorW9ReceivedIndicator()) && ObjectUtils.isNotNull(vdDocument.getVendorHeader().getVendorW8BenReceivedIndicator()) && vdDocument.getVendorHeader().getVendorW9ReceivedIndicator() && vdDocument.getVendorHeader().getVendorW8BenReceivedIndicator()) { 1309 putFieldError(VendorPropertyConstants.VENDOR_W9_RECEIVED_INDICATOR, VendorKeyConstants.ERROR_VENDOR_W9_AND_W8_RECEIVED_INDICATOR_BOTH_TRUE); 1310 valid &= false; 1311 } 1312 return valid; 1313 1314 } 1315 1316 /** 1317 * Overrides the method in MaintenanceDocumentRuleBase to give error message to the user when 1318 * the user tries to add a vendor contract when the vendor type of the vendor does not allow 1319 * contract. 1320 * 1321 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject) 1322 */ 1323 @Override 1324 public boolean processAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) { 1325 if (collectionName.equals(VendorPropertyConstants.VENDOR_CONTRACT)) { 1326 VendorDetail vendorDetail = (VendorDetail)document.getDocumentBusinessObject(); 1327 vendorDetail.getVendorHeader().refreshReferenceObject("vendorType"); 1328 VendorType vendorType = vendorDetail.getVendorHeader().getVendorType(); 1329 if (!vendorType.isVendorContractAllowedIndicator()) { 1330 String propertyName = "add." + collectionName + "." + VendorPropertyConstants.VENDOR_CONTRACT_NAME; 1331 putFieldError(propertyName, VendorKeyConstants.ERROR_VENDOR_CONTRACT_NOT_ALLOWED); 1332 return false; 1333 } 1334 } 1335 return super.processAddCollectionLineBusinessRules(document, collectionName, bo); 1336 } 1337 }