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    }