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.module.purap.document.validation.impl;
017    
018    import java.math.BigDecimal;
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.kuali.kfs.fp.businessobject.NonResidentAlienTaxPercent;
026    import org.kuali.kfs.module.purap.PurapConstants;
027    import org.kuali.kfs.module.purap.PurapKeyConstants;
028    import org.kuali.kfs.module.purap.PurapPropertyConstants;
029    import org.kuali.kfs.module.purap.PurapConstants.PaymentRequestStatuses;
030    import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
031    import org.kuali.kfs.sys.document.validation.GenericValidation;
032    import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
033    import org.kuali.rice.kns.service.BusinessObjectService;
034    import org.kuali.rice.kns.util.ErrorMap;
035    import org.kuali.rice.kns.util.GlobalVariables;
036    import org.kuali.rice.kns.util.KualiDecimal;
037    import org.kuali.rice.kns.util.MessageMap;
038    import org.kuali.rice.kns.util.ObjectUtils;
039    
040    public class PaymentRequestTaxAreaValidation extends GenericValidation {
041        
042        private BusinessObjectService businessObjectService;    
043        
044        /** Map for allowed federal and state tax rates based on income class. *
045        private static HashMap<String, ArrayList<BigDecimal>> federalTaxRates;
046        private static HashMap<String, ArrayList<BigDecimal>> stateTaxRates;    
047    
048        //TODO these rates shall be kept in DB tables or as parameter
049        // set up the tax rate maps
050        static {
051            federalTaxRates = new HashMap<String, ArrayList<BigDecimal>>();
052            stateTaxRates = new HashMap<String, ArrayList<BigDecimal>>();
053            
054            ArrayList<BigDecimal> fedrates = new ArrayList<BigDecimal>();
055            fedrates.add(new BigDecimal(30));
056            fedrates.add(new BigDecimal(14));
057            fedrates.add(new BigDecimal(0));
058            federalTaxRates.put("F", fedrates);
059    
060            fedrates = new ArrayList<BigDecimal>();
061            fedrates.add(new BigDecimal(30));
062            fedrates.add(new BigDecimal(15));
063            fedrates.add(new BigDecimal(10));
064            fedrates.add(new BigDecimal(5));
065            fedrates.add(new BigDecimal(0));
066            federalTaxRates.put("R", fedrates);
067    
068            fedrates = new ArrayList<BigDecimal>();
069            fedrates.add(new BigDecimal(30));
070            fedrates.add(new BigDecimal(0));
071            federalTaxRates.put("I", fedrates);
072            federalTaxRates.put("A", fedrates);
073            federalTaxRates.put("O", fedrates);
074    
075            ArrayList<BigDecimal> strates = new ArrayList<BigDecimal>();
076            strates.add(new BigDecimal("3.40"));
077            strates.add(new BigDecimal(0));
078            stateTaxRates.put("F", strates);
079            stateTaxRates.put("A", strates);
080            stateTaxRates.put("O", strates);
081    
082            strates = new ArrayList<BigDecimal>();
083            strates.add(new BigDecimal(0));
084            stateTaxRates.put("I", strates);
085            stateTaxRates.put("R", strates);
086        }
087        */
088    
089        /**
090         * Process business rules applicable to tax area data before calculating the withholding tax on payment request.
091         * @param paymentRequest - payment request document
092         * @return true if all business rules applicable passes; false otherwise.
093         */
094        public boolean validate(AttributedDocumentEvent event) {        
095            PaymentRequestDocument preq = (PaymentRequestDocument)event.getDocument();
096            
097            // do this validation only at route level of awaiting tax review
098            if (!preq.getStatusCode().equals(PaymentRequestStatuses.AWAITING_TAX_REVIEW)) 
099                return true;
100            
101            MessageMap errorMap = GlobalVariables.getMessageMap();        
102            errorMap.clearErrorPath();
103            //errorMap.addToErrorPath(KFSPropertyConstants.DOCUMENT);
104            errorMap.addToErrorPath(PurapConstants.PAYMENT_REQUEST_TAX_TAB_ERRORS);
105    
106            boolean valid = true;        
107            valid &= validateTaxIncomeClass(preq);        
108            valid &= validateTaxRates(preq);    
109            valid &= validateTaxIndicators(preq);
110    
111            errorMap.clearErrorPath();
112            return valid;
113        }
114    
115        /**
116         * Validates tax income class: when Non-Reportable income class is chosen, all other fields shall be left blank; 
117         * It assumed that the input tax income class code is valid (existing and active in the system) since it's chosen from drop-down list.
118         * otherwise tax rates and country are required;
119         * @param paymentRequest - payment request document
120         * @return true if this validation passes; false otherwise.
121         */
122        protected boolean validateTaxIncomeClass(PaymentRequestDocument preq) {
123            boolean valid = true;
124            MessageMap errorMap = GlobalVariables.getMessageMap();
125    
126            // TaxClassificationCode is required field
127            if (StringUtils.isEmpty(preq.getTaxClassificationCode())) {
128                valid = false;
129                errorMap.putError(PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_REQUIRED, PurapPropertyConstants.TAX_CLASSIFICATION_CODE);           
130            }
131            // If TaxClassificationCode is N (Non_Reportable, then other fields shall be blank.
132            else if (StringUtils.equalsIgnoreCase(preq.getTaxClassificationCode(), "N")) {
133                if (preq.getTaxFederalPercent() != null && preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
134                    valid = false;
135                    errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
136                }
137                if (preq.getTaxStatePercent() != null && preq.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
138                    valid = false;
139                    errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_STATE_PERCENT);
140                }
141                if (!StringUtils.isEmpty(preq.getTaxCountryCode())) {
142                    valid = false;
143                    errorMap.putError(PurapPropertyConstants.TAX_COUNTRY_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_COUNTRY_CODE);
144                }
145                if (!StringUtils.isEmpty(preq.getTaxNQIId())) {
146                    valid = false;
147                    errorMap.putError(PurapPropertyConstants.TAX_NQI_ID, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_NQI_ID);           
148                }
149                if (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new BigDecimal(0)) != 0) {
150                    valid = false;
151                    errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
152                }               
153                if (ObjectUtils.nullSafeEquals(preq.getTaxExemptTreatyIndicator(), true)) { 
154                    valid = false;
155                    errorMap.putError(PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR);
156                }                         
157                if (ObjectUtils.nullSafeEquals(preq.getTaxGrossUpIndicator(), true)) { 
158                    valid = false;
159                    errorMap.putError(PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR);
160                }               
161                if (ObjectUtils.nullSafeEquals(preq.getTaxForeignSourceIndicator(), true)) { 
162                    valid = false;
163                    errorMap.putError(PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR);
164                }               
165                if (ObjectUtils.nullSafeEquals(preq.getTaxUSAIDPerDiemIndicator(), true)) { 
166                    valid = false;
167                    errorMap.putError(PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR);
168                }               
169                if (ObjectUtils.nullSafeEquals(preq.getTaxOtherExemptIndicator(), true)) { 
170                    valid = false;
171                    errorMap.putError(PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR);
172                }               
173            }
174            else {
175                // If TaxClassificationCode is not N (Non_Reportable, then the federal/state tax percent and country are required.
176                if (preq.getTaxFederalPercent() == null) {       
177                    valid = false;
178                    errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_REQUIRED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_FEDERAL_PERCENT);           
179                }
180                if (preq.getTaxStatePercent() == null) {
181                    valid = false;
182                    errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_REQUIRED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_STATE_PERCENT);           
183                }
184                if (StringUtils.isEmpty(preq.getTaxCountryCode())) {
185                    valid = false;
186                    errorMap.putError(PurapPropertyConstants.TAX_COUNTRY_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_REQUIRED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_COUNTRY_CODE);           
187                }
188            }
189    
190            return valid;
191        }
192    
193        /**
194         * Validates federal and state tax rates based on each other and the income class.
195         * Validation will be bypassed if income class is empty or N, or tax rates are null.
196         * @param paymentRequest - payment request document
197         * @return true if this validation passes; false otherwise.
198         */
199        protected boolean validateTaxRates(PaymentRequestDocument preq) {
200            boolean valid = true;
201            String code = preq.getTaxClassificationCode();
202            BigDecimal fedrate = preq.getTaxFederalPercent();
203            BigDecimal strate = preq.getTaxStatePercent();
204            MessageMap errorMap = GlobalVariables.getMessageMap();
205    
206            // only test the cases when income class and tax rates aren't empty/N
207            if (StringUtils.isEmpty(code) || StringUtils.equalsIgnoreCase(code, "N") || fedrate == null || strate == null)
208                return true;
209    
210            // validate that the federal and state tax rates are among the allowed set
211            ArrayList<BigDecimal> fedrates = retrieveTaxRates(code, "F"); //(ArrayList<BigDecimal>) federalTaxRates.get(code);
212            if (!listContainsValue(fedrates, fedrate)) {
213                valid = false;
214                errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_INVALID_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_FEDERAL_PERCENT);                      
215            }
216            ArrayList<BigDecimal> strates = retrieveTaxRates(code, "S"); //(ArrayList<BigDecimal>) stateTaxRates.get(code);
217            if (!listContainsValue(strates, strate)) {
218                valid = false;
219                errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_INVALID_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_STATE_PERCENT);                      
220            }
221    
222            // validate that the federal and state tax rate abide to certain relationship
223            if (fedrate.compareTo(new BigDecimal(0)) == 0 && strate.compareTo(new BigDecimal(0)) != 0) {
224                valid = false;
225                errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapPropertyConstants.TAX_STATE_PERCENT);                      
226            } 
227            boolean hasstrate = code.equalsIgnoreCase("F") || code.equalsIgnoreCase("A") || code.equalsIgnoreCase("O");
228            if (fedrate.compareTo(new BigDecimal(0)) > 0 && strate.compareTo(new BigDecimal(0)) <= 0 && hasstrate) {
229                valid = false;
230                errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_NOT_ZERO_IF, PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapPropertyConstants.TAX_STATE_PERCENT);                      
231            } 
232    
233            return valid;
234        }
235    
236        /**
237         * Validates rules among tax treaty, gross up, foreign source, USAID, other exempt, and Special W-4.
238         * @param paymentRequest - payment request document
239         * @return true if this validation passes; false otherwise.
240         */
241        protected boolean validateTaxIndicators(PaymentRequestDocument preq) {
242            boolean valid = true;     
243            MessageMap errorMap = GlobalVariables.getMessageMap();
244    
245            // if choose tax treaty, cannot choose any of the other above 
246            if (ObjectUtils.nullSafeEquals(preq.getTaxExemptTreatyIndicator(), true)) {
247                if (ObjectUtils.nullSafeEquals(preq.getTaxGrossUpIndicator(), true)) { 
248                    valid = false;
249                    errorMap.putError(PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR);
250                }               
251                if (ObjectUtils.nullSafeEquals(preq.getTaxForeignSourceIndicator(), true)) { 
252                    valid = false;
253                    errorMap.putError(PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR);
254                }               
255                if (ObjectUtils.nullSafeEquals(preq.getTaxUSAIDPerDiemIndicator(), true)) { 
256                    valid = false;
257                    errorMap.putError(PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR);
258                }               
259                if (ObjectUtils.nullSafeEquals(preq.getTaxOtherExemptIndicator(), true)) { 
260                    valid = false;
261                    errorMap.putError(PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR);
262                }               
263                if (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) { 
264                    valid = false;
265                    errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
266                }               
267            }
268    
269            // if choose gross up, cannot choose any other above, and fed tax rate cannot be zero
270            if (ObjectUtils.nullSafeEquals(preq.getTaxGrossUpIndicator(), true)) {
271                if (ObjectUtils.nullSafeEquals(preq.getTaxExemptTreatyIndicator(), true)) { 
272                    valid = false;
273                    errorMap.putError(PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR);
274                }               
275                if (ObjectUtils.nullSafeEquals(preq.getTaxForeignSourceIndicator(), true)) { 
276                    valid = false;
277                    errorMap.putError(PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR);
278                }               
279                if (ObjectUtils.nullSafeEquals(preq.getTaxUSAIDPerDiemIndicator(), true)) { 
280                    valid = false;
281                    errorMap.putError(PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR);
282                }               
283                if (ObjectUtils.nullSafeEquals(preq.getTaxOtherExemptIndicator(), true)) { 
284                    valid = false;
285                    errorMap.putError(PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR);
286                }               
287                if (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) { 
288                    valid = false;
289                    errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
290                }               
291                if (preq.getTaxFederalPercent() == null || preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) == 0 ) {
292                    valid = false;
293                    errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_NOT_ZERO_IF, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
294                }
295            }
296    
297            // if choose foreign source, cannot choose any other above, and tax rates shall be zero
298            if (ObjectUtils.nullSafeEquals(preq.getTaxForeignSourceIndicator(), true)) {
299                if (ObjectUtils.nullSafeEquals(preq.getTaxExemptTreatyIndicator(), true)) { 
300                    valid = false;
301                    errorMap.putError(PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR);
302                }               
303                if (ObjectUtils.nullSafeEquals(preq.getTaxGrossUpIndicator(), true)) { 
304                    valid = false;
305                    errorMap.putError(PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR);
306                }               
307                if (ObjectUtils.nullSafeEquals(preq.getTaxUSAIDPerDiemIndicator(), true)) { 
308                    valid = false;
309                    errorMap.putError(PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR);
310                }               
311                if (ObjectUtils.nullSafeEquals(preq.getTaxOtherExemptIndicator(), true)) { 
312                    valid = false;
313                    errorMap.putError(PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR);
314                }               
315                if (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
316                    valid = false;
317                    errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
318                }               
319                if (preq.getTaxFederalPercent() != null && preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
320                    valid = false;
321                    errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
322                }
323                if (preq.getTaxStatePercent() != null && preq.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
324                    valid = false;
325                    errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_STATE_PERCENT);
326                }
327            }
328    
329            // if choose USAID per diem, cannot choose any other above except other exempt code, which must be checked; income class shall be fellowship with tax rates 0
330            if (ObjectUtils.nullSafeEquals(preq.getTaxUSAIDPerDiemIndicator(), true)) {
331                if (ObjectUtils.nullSafeEquals(preq.getTaxExemptTreatyIndicator(), true)) { 
332                    valid = false;
333                    errorMap.putError(PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR);
334                }               
335                if (ObjectUtils.nullSafeEquals(preq.getTaxGrossUpIndicator(), true)) { 
336                    valid = false;
337                    errorMap.putError(PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR);
338                }               
339                if (ObjectUtils.nullSafeEquals(preq.getTaxForeignSourceIndicator(), true)) { 
340                    valid = false;
341                    errorMap.putError(PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR);
342                }               
343                if (!ObjectUtils.nullSafeEquals(preq.getTaxOtherExemptIndicator(), true)) { 
344                    valid = false;
345                    errorMap.putError(PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_REQUIRED_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR);
346                }               
347                if (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
348                    valid = false;
349                    errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
350                }               
351                if (StringUtils.isEmpty(preq.getTaxClassificationCode()) || !StringUtils.equalsIgnoreCase(preq.getTaxClassificationCode(), "F")) {
352                    valid = false;
353                    errorMap.putError(PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_INVALID_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_CLASSIFICATION_CODE);
354                }
355                if (preq.getTaxFederalPercent() != null && preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0 ) {
356                    valid = false;
357                    errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
358                }
359                if (preq.getTaxStatePercent() != null && preq.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0 ) {
360                    valid = false;
361                    errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_USAID_PER_DIEM_INDICATOR, PurapPropertyConstants.TAX_STATE_PERCENT);
362                }
363            }
364    
365            // if choose exempt under other code, cannot choose any other above except USAID, and tax rates shall be zero
366            if (ObjectUtils.nullSafeEquals(preq.getTaxOtherExemptIndicator(), true)) {
367                if (ObjectUtils.nullSafeEquals(preq.getTaxExemptTreatyIndicator(), true)) { 
368                    valid = false;
369                    errorMap.putError(PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR);
370                }               
371                if (ObjectUtils.nullSafeEquals(preq.getTaxGrossUpIndicator(), true)) { 
372                    valid = false;
373                    errorMap.putError(PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR);
374                }               
375                if (ObjectUtils.nullSafeEquals(preq.getTaxForeignSourceIndicator(), true)) { 
376                    valid = false;
377                    errorMap.putError(PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR);
378                }               
379                if (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
380                    valid = false;
381                    errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
382                }               
383                if (preq.getTaxFederalPercent() != null && preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0 ) {
384                    valid = false;
385                    errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
386                }
387                if (preq.getTaxStatePercent() != null && preq.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0 ) {
388                    valid = false;
389                    errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_STATE_PERCENT);
390                }
391            }
392    
393            // if choose Special W-4, cannot choose tax treaty, gross up, and foreign source; income class shall be fellowship with tax rates 0
394            if (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0 ) {
395                if (ObjectUtils.nullSafeEquals(preq.getTaxExemptTreatyIndicator(), true)) { 
396                    valid = false;
397                    errorMap.putError(PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR);
398                }               
399                if (ObjectUtils.nullSafeEquals(preq.getTaxGrossUpIndicator(), true)) { 
400                    valid = false;
401                    errorMap.putError(PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR);
402                }               
403                if (ObjectUtils.nullSafeEquals(preq.getTaxForeignSourceIndicator(), true)) { 
404                    valid = false;
405                    errorMap.putError(PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR);
406                }               
407                if (preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) < 0) { 
408                    valid = false;
409                    errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_MUST_NOT_NEGATIVE, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
410                }                          
411                if (StringUtils.isEmpty(preq.getTaxClassificationCode()) || !StringUtils.equalsIgnoreCase(preq.getTaxClassificationCode(), "F")) {
412                    valid = false;
413                    errorMap.putError(PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_INVALID_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_CLASSIFICATION_CODE);
414                }
415                if (preq.getTaxFederalPercent() != null && preq.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0 ) {
416                    valid = false;
417                    errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
418                }
419                if (preq.getTaxStatePercent() != null && preq.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0 ) {
420                    valid = false;
421                    errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_STATE_PERCENT);
422                }
423            }
424    
425            return valid;
426        }
427    
428        /**
429         * Initiates the federal and state tax rate maps for the purpose of validation on tax rates.
430         *
431        private void loadTaxRates() {        
432            Collection<TaxIncomeClassCode> incomeClasses = retrieveAllTaxIncomeClasses();
433            for (TaxIncomeClassCode incomeClass : incomeClasses) {
434                String incomeCode = incomeClass.getCode();
435                ArrayList<BigDecimal> fedrates = retrieveTaxRates(incomeCode, "F"); // federal rates
436                federalTaxRates.put(incomeCode, fedrates);
437                ArrayList<BigDecimal> strates = retrieveTaxRates(incomeCode, "S"); // state rates
438                federalTaxRates.put(incomeCode, strates);
439            }        
440        }
441        */
442        
443        /**
444         * Retrieves all valid tax income classes in the system.
445         *
446        public Collection<TaxIncomeClassCode> retrieveAllTaxIncomeClasses() {
447            return businessObjectService.findAll(TaxIncomeClassCode.class);
448        }
449        */
450        
451        /**
452         * Retrieve active NonResidentAlien tax rate percent from database based on the specified income class and federal/state tax type.
453         * @param incomeClassCode The specified income class type code.
454         * @param incomeTaxTypeCode The specified income tax type code.
455         * @return The array list containing the tax rates retrieved.
456         */
457        public ArrayList<BigDecimal> retrieveTaxRates(String incomeClassCode, String incomeTaxTypeCode) {
458            ArrayList<BigDecimal> rates = new ArrayList<BigDecimal>();
459            Map<String, String> criterion = new HashMap<String, String>();
460            criterion.put("incomeClassCode", incomeClassCode);
461            criterion.put("incomeTaxTypeCode", incomeTaxTypeCode);
462            criterion.put("active", "Y"); // only retrieve active tax percents
463            List<NonResidentAlienTaxPercent> percents = (List<NonResidentAlienTaxPercent>)businessObjectService.findMatching(NonResidentAlienTaxPercent.class, criterion);
464            
465            for (NonResidentAlienTaxPercent percent : percents) {
466                rates.add(percent.getIncomeTaxPercent().bigDecimalValue());
467            }        
468            return rates;
469        }
470        
471        /**
472         * Returns true if the specified ArrayList contains the specified BigDecimal value.
473         * @param list the specified ArrayList
474         * @param value the specified BigDecimal
475         */
476        protected boolean listContainsValue(ArrayList<BigDecimal> list, BigDecimal value) {
477            if (list == null || value == null)
478                return false;
479            for (BigDecimal val : list) {
480                if (val.compareTo(value) == 0)
481                    return true;
482            }
483            return false;     
484        }
485        
486        public BusinessObjectService getBusinessObjectService() {
487            return businessObjectService;
488        }
489    
490        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
491            this.businessObjectService = businessObjectService;
492        }    
493    }