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 }