View Javadoc
1   /*
2    * Copyright 2009 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.module.purap.document.validation.impl;
17  
18  import org.apache.commons.lang.ObjectUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.ole.fp.businessobject.NonResidentAlienTaxPercent;
21  import org.kuali.ole.module.purap.PurapConstants;
22  import org.kuali.ole.module.purap.PurapConstants.InvoiceStatuses;
23  import org.kuali.ole.module.purap.PurapKeyConstants;
24  import org.kuali.ole.module.purap.PurapPropertyConstants;
25  import org.kuali.ole.module.purap.document.InvoiceDocument;
26  import org.kuali.ole.sys.document.validation.GenericValidation;
27  import org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent;
28  import org.kuali.rice.core.api.util.type.KualiDecimal;
29  import org.kuali.rice.krad.service.BusinessObjectService;
30  import org.kuali.rice.krad.util.GlobalVariables;
31  import org.kuali.rice.krad.util.MessageMap;
32  
33  import java.math.BigDecimal;
34  import java.util.ArrayList;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  
39  public class InvoiceTaxAreaValidation extends GenericValidation {
40  
41      private BusinessObjectService businessObjectService;
42  
43      /** Map for allowed federal and state tax rates based on income class. *
44       private static HashMap<String, ArrayList<BigDecimal>> federalTaxRates;
45       private static HashMap<String, ArrayList<BigDecimal>> stateTaxRates;
46  
47       //TODO these rates shall be kept in DB tables or as parameter
48       // set up the tax rate maps
49       static {
50       federalTaxRates = new HashMap<String, ArrayList<BigDecimal>>();
51       stateTaxRates = new HashMap<String, ArrayList<BigDecimal>>();
52  
53       ArrayList<BigDecimal> fedrates = new ArrayList<BigDecimal>();
54       fedrates.add(new BigDecimal(30));
55       fedrates.add(new BigDecimal(14));
56       fedrates.add(new BigDecimal(0));
57       federalTaxRates.put("F", fedrates);
58  
59       fedrates = new ArrayList<BigDecimal>();
60       fedrates.add(new BigDecimal(30));
61       fedrates.add(new BigDecimal(15));
62       fedrates.add(new BigDecimal(10));
63       fedrates.add(new BigDecimal(5));
64       fedrates.add(new BigDecimal(0));
65       federalTaxRates.put("R", fedrates);
66  
67       fedrates = new ArrayList<BigDecimal>();
68       fedrates.add(new BigDecimal(30));
69       fedrates.add(new BigDecimal(0));
70       federalTaxRates.put("I", fedrates);
71       federalTaxRates.put("A", fedrates);
72       federalTaxRates.put("O", fedrates);
73  
74       ArrayList<BigDecimal> strates = new ArrayList<BigDecimal>();
75       strates.add(new BigDecimal("3.40"));
76       strates.add(new BigDecimal(0));
77       stateTaxRates.put("F", strates);
78       stateTaxRates.put("A", strates);
79       stateTaxRates.put("O", strates);
80  
81       strates = new ArrayList<BigDecimal>();
82       strates.add(new BigDecimal(0));
83       stateTaxRates.put("I", strates);
84       stateTaxRates.put("R", strates);
85       }
86       */
87  
88      /**
89       * Process business rules applicable to tax area data before calculating the withholding tax on invoice.
90       *
91       * @param event - event
92       * @return true if all business rules applicable passes; false otherwise.
93       */
94      public boolean validate(AttributedDocumentEvent event) {
95          InvoiceDocument invoice = (InvoiceDocument) event.getDocument();
96  
97          // do this validation only at route level of awaiting tax review
98          if (!StringUtils.equals(invoice.getApplicationDocumentStatus(), InvoiceStatuses.APPDOC_AWAITING_TAX_REVIEW))
99              return true;
100 
101         MessageMap errorMap = GlobalVariables.getMessageMap();
102         errorMap.clearErrorPath();
103         //errorMap.addToErrorPath(OLEPropertyConstants.DOCUMENT);
104         errorMap.addToErrorPath(PurapConstants.PAYMENT_REQUEST_TAX_TAB_ERRORS);
105 
106         boolean valid = true;
107         valid &= validateTaxIncomeClass(invoice);
108         valid &= validateTaxRates(invoice);
109         valid &= validateTaxIndicators(invoice);
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      *
120      * @param invoice - invoice document
121      * @return true if this validation passes; false otherwise.
122      */
123     protected boolean validateTaxIncomeClass(InvoiceDocument invoice) {
124         boolean valid = true;
125         MessageMap errorMap = GlobalVariables.getMessageMap();
126 
127         // TaxClassificationCode is required field
128         if (StringUtils.isEmpty(invoice.getTaxClassificationCode())) {
129             valid = false;
130             errorMap.putError(PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_REQUIRED, PurapPropertyConstants.TAX_CLASSIFICATION_CODE);
131         }
132         // If TaxClassificationCode is N (Non_Reportable, then other fields shall be blank.
133         else if (StringUtils.equalsIgnoreCase(invoice.getTaxClassificationCode(), "N")) {
134             if (invoice.getTaxFederalPercent() != null && invoice.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
135                 valid = false;
136                 errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
137             }
138             if (invoice.getTaxStatePercent() != null && invoice.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
139                 valid = false;
140                 errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_STATE_PERCENT);
141             }
142             if (!StringUtils.isEmpty(invoice.getTaxCountryCode())) {
143                 valid = false;
144                 errorMap.putError(PurapPropertyConstants.TAX_COUNTRY_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_COUNTRY_CODE);
145             }
146             if (!StringUtils.isEmpty(invoice.getTaxNQIId())) {
147                 valid = false;
148                 errorMap.putError(PurapPropertyConstants.TAX_NQI_ID, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_NQI_ID);
149             }
150             if (invoice.getTaxSpecialW4Amount() != null && invoice.getTaxSpecialW4Amount().compareTo(new BigDecimal(0)) != 0) {
151                 valid = false;
152                 errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
153             }
154             if (ObjectUtils.equals(invoice.getTaxExemptTreatyIndicator(), true)) {
155                 valid = false;
156                 errorMap.putError(PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR);
157             }
158             if (ObjectUtils.equals(invoice.getTaxGrossUpIndicator(), true)) {
159                 valid = false;
160                 errorMap.putError(PurapPropertyConstants.TAX_GROSS_UP_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_GROSS_UP_INDICATOR);
161             }
162             if (ObjectUtils.equals(invoice.getTaxForeignSourceIndicator(), true)) {
163                 valid = false;
164                 errorMap.putError(PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR);
165             }
166             if (ObjectUtils.equals(invoice.getTaxUSAIDPerDiemIndicator(), true)) {
167                 valid = false;
168                 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);
169             }
170             if (ObjectUtils.equals(invoice.getTaxOtherExemptIndicator(), true)) {
171                 valid = false;
172                 errorMap.putError(PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_DISALLOWED_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR);
173             }
174         } else {
175             // If TaxClassificationCode is not N (Non_Reportable, then the federal/state tax percent and country are required.
176             if (invoice.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 (invoice.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(invoice.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      *
197      * @param invoice - invoice document
198      * @return true if this validation passes; false otherwise.
199      */
200     protected boolean validateTaxRates(InvoiceDocument invoice) {
201         boolean valid = true;
202         String code = invoice.getTaxClassificationCode();
203         BigDecimal fedrate = invoice.getTaxFederalPercent();
204         BigDecimal strate = invoice.getTaxStatePercent();
205         MessageMap errorMap = GlobalVariables.getMessageMap();
206 
207         // only test the cases when income class and tax rates aren't empty/N
208         if (StringUtils.isEmpty(code) || StringUtils.equalsIgnoreCase(code, "N") || fedrate == null || strate == null)
209             return true;
210 
211         // validate that the federal and state tax rates are among the allowed set
212         ArrayList<BigDecimal> fedrates = retrieveTaxRates(code, "F"); //(ArrayList<BigDecimal>) federalTaxRates.get(code);
213         if (!listContainsValue(fedrates, fedrate)) {
214             valid = false;
215             errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_INVALID_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
216         }
217         ArrayList<BigDecimal> strates = retrieveTaxRates(code, "S"); //(ArrayList<BigDecimal>) stateTaxRates.get(code);
218         if (!listContainsValue(strates, strate)) {
219             valid = false;
220             errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_INVALID_IF, PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapPropertyConstants.TAX_STATE_PERCENT);
221         }
222 
223         // validate that the federal and state tax rate abide to certain relationship
224         if (fedrate.compareTo(new BigDecimal(0)) == 0 && strate.compareTo(new BigDecimal(0)) != 0) {
225             valid = false;
226             errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapPropertyConstants.TAX_STATE_PERCENT);
227         }
228         boolean hasstrate = code.equalsIgnoreCase("F") || code.equalsIgnoreCase("A") || code.equalsIgnoreCase("O");
229         if (fedrate.compareTo(new BigDecimal(0)) > 0 && strate.compareTo(new BigDecimal(0)) <= 0 && hasstrate) {
230             valid = false;
231             errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_NOT_ZERO_IF, PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapPropertyConstants.TAX_STATE_PERCENT);
232         }
233 
234         return valid;
235     }
236 
237     /**
238      * Validates rules among tax treaty, gross up, foreign source, USAID, other exempt, and Special W-4.
239      *
240      * @param invoice - invoice document
241      * @return true if this validation passes; false otherwise.
242      */
243     protected boolean validateTaxIndicators(InvoiceDocument invoice) {
244         boolean valid = true;
245         MessageMap errorMap = GlobalVariables.getMessageMap();
246 
247         // if choose tax treaty, cannot choose any of the other above 
248         if (ObjectUtils.equals(invoice.getTaxExemptTreatyIndicator(), true)) {
249             if (ObjectUtils.equals(invoice.getTaxGrossUpIndicator(), true)) {
250                 valid = false;
251                 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);
252             }
253             if (ObjectUtils.equals(invoice.getTaxForeignSourceIndicator(), true)) {
254                 valid = false;
255                 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);
256             }
257             if (ObjectUtils.equals(invoice.getTaxUSAIDPerDiemIndicator(), true)) {
258                 valid = false;
259                 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);
260             }
261             if (ObjectUtils.equals(invoice.getTaxOtherExemptIndicator(), true)) {
262                 valid = false;
263                 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);
264             }
265             if (invoice.getTaxSpecialW4Amount() != null && invoice.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
266                 valid = false;
267                 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);
268             }
269             // Below Lines commented as this is duplicate check. Check already made in validateTaxIncomeClass
270 //            if (invoice.getTaxFederalPercent() != null && invoice.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
271 //                valid = false;
272 //                errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
273 //            }
274 //            if (invoice.getTaxStatePercent() != null && invoice.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
275 //                valid = false;
276 //                errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_EXEMPT_TREATY_INDICATOR, PurapPropertyConstants.TAX_STATE_PERCENT);
277 //            }
278         }
279 
280         // if choose gross up, cannot choose any other above, and fed tax rate cannot be zero
281         if (ObjectUtils.equals(invoice.getTaxGrossUpIndicator(), true)) {
282             if (ObjectUtils.equals(invoice.getTaxExemptTreatyIndicator(), true)) {
283                 valid = false;
284                 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);
285             }
286             if (ObjectUtils.equals(invoice.getTaxForeignSourceIndicator(), true)) {
287                 valid = false;
288                 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);
289             }
290             if (ObjectUtils.equals(invoice.getTaxUSAIDPerDiemIndicator(), true)) {
291                 valid = false;
292                 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);
293             }
294             if (ObjectUtils.equals(invoice.getTaxOtherExemptIndicator(), true)) {
295                 valid = false;
296                 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);
297             }
298             if (invoice.getTaxSpecialW4Amount() != null && invoice.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
299                 valid = false;
300                 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);
301             }
302             if (invoice.getTaxFederalPercent() == null || invoice.getTaxFederalPercent().compareTo(new BigDecimal(0)) == 0) {
303                 valid = false;
304                 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);
305             }
306         }
307 
308         // if choose foreign source, cannot choose any other above, and tax rates shall be zero
309         if (ObjectUtils.equals(invoice.getTaxForeignSourceIndicator(), true)) {
310             if (ObjectUtils.equals(invoice.getTaxExemptTreatyIndicator(), true)) {
311                 valid = false;
312                 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);
313             }
314             if (ObjectUtils.equals(invoice.getTaxGrossUpIndicator(), true)) {
315                 valid = false;
316                 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);
317             }
318             if (ObjectUtils.equals(invoice.getTaxUSAIDPerDiemIndicator(), true)) {
319                 valid = false;
320                 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);
321             }
322             if (ObjectUtils.equals(invoice.getTaxOtherExemptIndicator(), true)) {
323                 valid = false;
324                 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);
325             }
326             if (invoice.getTaxSpecialW4Amount() != null && invoice.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
327                 valid = false;
328                 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);
329             }
330             if (invoice.getTaxFederalPercent() != null && invoice.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
331                 valid = false;
332                 errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
333             }
334             if (invoice.getTaxStatePercent() != null && invoice.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
335                 valid = false;
336                 errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_FOREIGN_SOURCE_INDICATOR, PurapPropertyConstants.TAX_STATE_PERCENT);
337             }
338         }
339 
340         // 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
341         if (ObjectUtils.equals(invoice.getTaxUSAIDPerDiemIndicator(), true)) {
342             if (ObjectUtils.equals(invoice.getTaxExemptTreatyIndicator(), true)) {
343                 valid = false;
344                 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);
345             }
346             if (ObjectUtils.equals(invoice.getTaxGrossUpIndicator(), true)) {
347                 valid = false;
348                 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);
349             }
350             if (ObjectUtils.equals(invoice.getTaxForeignSourceIndicator(), true)) {
351                 valid = false;
352                 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);
353             }
354             if (!ObjectUtils.equals(invoice.getTaxOtherExemptIndicator(), true)) {
355                 valid = false;
356                 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);
357             }
358             if (invoice.getTaxSpecialW4Amount() != null && invoice.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
359                 valid = false;
360                 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);
361             }
362             if (StringUtils.isEmpty(invoice.getTaxClassificationCode()) || !StringUtils.equalsIgnoreCase(invoice.getTaxClassificationCode(), "F")) {
363                 valid = false;
364                 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);
365             }
366             if (invoice.getTaxFederalPercent() != null && invoice.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
367                 valid = false;
368                 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);
369             }
370             if (invoice.getTaxStatePercent() != null && invoice.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
371                 valid = false;
372                 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);
373             }
374         }
375 
376         // if choose exempt under other code, cannot choose any other above except USAID, and tax rates shall be zero
377         if (ObjectUtils.equals(invoice.getTaxOtherExemptIndicator(), true)) {
378             if (ObjectUtils.equals(invoice.getTaxExemptTreatyIndicator(), true)) {
379                 valid = false;
380                 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);
381             }
382             if (ObjectUtils.equals(invoice.getTaxGrossUpIndicator(), true)) {
383                 valid = false;
384                 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);
385             }
386             if (ObjectUtils.equals(invoice.getTaxForeignSourceIndicator(), true)) {
387                 valid = false;
388                 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);
389             }
390             if (invoice.getTaxSpecialW4Amount() != null && invoice.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
391                 valid = false;
392                 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);
393             }
394             if (invoice.getTaxFederalPercent() != null && invoice.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
395                 valid = false;
396                 errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
397             }
398             if (invoice.getTaxStatePercent() != null && invoice.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
399                 valid = false;
400                 errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_OTHER_EXEMPT_INDICATOR, PurapPropertyConstants.TAX_STATE_PERCENT);
401             }
402         }
403 
404         // if choose Special W-4, cannot choose tax treaty, gross up, and foreign source; income class shall be fellowship with tax rates 0
405         if (invoice.getTaxSpecialW4Amount() != null && invoice.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
406             if (ObjectUtils.equals(invoice.getTaxExemptTreatyIndicator(), true)) {
407                 valid = false;
408                 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);
409             }
410             if (ObjectUtils.equals(invoice.getTaxGrossUpIndicator(), true)) {
411                 valid = false;
412                 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);
413             }
414             if (ObjectUtils.equals(invoice.getTaxForeignSourceIndicator(), true)) {
415                 valid = false;
416                 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);
417             }
418             if (invoice.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) < 0) {
419                 valid = false;
420                 errorMap.putError(PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_MUST_NOT_NEGATIVE, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT);
421             }
422             if (StringUtils.isEmpty(invoice.getTaxClassificationCode()) || !StringUtils.equalsIgnoreCase(invoice.getTaxClassificationCode(), "F")) {
423                 valid = false;
424                 errorMap.putError(PurapPropertyConstants.TAX_CLASSIFICATION_CODE, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_FIELD_VALUE_INVALID_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_CLASSIFICATION_CODE);
425             }
426             if (invoice.getTaxFederalPercent() != null && invoice.getTaxFederalPercent().compareTo(new BigDecimal(0)) != 0) {
427                 valid = false;
428                 errorMap.putError(PurapPropertyConstants.TAX_FEDERAL_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_FEDERAL_PERCENT);
429             }
430             if (invoice.getTaxStatePercent() != null && invoice.getTaxStatePercent().compareTo(new BigDecimal(0)) != 0) {
431                 valid = false;
432                 errorMap.putError(PurapPropertyConstants.TAX_STATE_PERCENT, PurapKeyConstants.ERROR_PAYMENT_REQUEST_TAX_RATE_MUST_ZERO_IF, PurapPropertyConstants.TAX_SPECIAL_W4_AMOUNT, PurapPropertyConstants.TAX_STATE_PERCENT);
433             }
434         }
435 
436         return valid;
437     }
438 
439     /**
440      * Initiates the federal and state tax rate maps for the purpose of validation on tax rates.
441      *
442      private void loadTaxRates() {
443      Collection<TaxIncomeClassCode> incomeClasses = retrieveAllTaxIncomeClasses();
444      for (TaxIncomeClassCode incomeClass : incomeClasses) {
445      String incomeCode = incomeClass.getCode();
446      ArrayList<BigDecimal> fedrates = retrieveTaxRates(incomeCode, "F"); // federal rates
447      federalTaxRates.put(incomeCode, fedrates);
448      ArrayList<BigDecimal> strates = retrieveTaxRates(incomeCode, "S"); // state rates
449      federalTaxRates.put(incomeCode, strates);
450      }
451      }
452      */
453 
454     /**
455      * Retrieves all valid tax income classes in the system.
456      *
457      public Collection<TaxIncomeClassCode> retrieveAllTaxIncomeClasses() {
458      return businessObjectService.findAll(TaxIncomeClassCode.class);
459      }
460      */
461 
462     /**
463      * Retrieve active NonResidentAlien tax rate percent from database based on the specified income class and federal/state tax type.
464      *
465      * @param incomeClassCode   The specified income class type code.
466      * @param incomeTaxTypeCode The specified income tax type code.
467      * @return The array list containing the tax rates retrieved.
468      */
469     public ArrayList<BigDecimal> retrieveTaxRates(String incomeClassCode, String incomeTaxTypeCode) {
470         ArrayList<BigDecimal> rates = new ArrayList<BigDecimal>();
471         Map<String, String> criterion = new HashMap<String, String>();
472         criterion.put("incomeClassCode", incomeClassCode);
473         criterion.put("incomeTaxTypeCode", incomeTaxTypeCode);
474         criterion.put("active", "Y"); // only retrieve active tax percents
475         List<NonResidentAlienTaxPercent> percents = (List<NonResidentAlienTaxPercent>) businessObjectService.findMatching(NonResidentAlienTaxPercent.class, criterion);
476 
477         for (NonResidentAlienTaxPercent percent : percents) {
478             rates.add(percent.getIncomeTaxPercent().bigDecimalValue());
479         }
480         return rates;
481     }
482 
483     /**
484      * Returns true if the specified ArrayList contains the specified BigDecimal value.
485      *
486      * @param list  the specified ArrayList
487      * @param value the specified BigDecimal
488      */
489     protected boolean listContainsValue(ArrayList<BigDecimal> list, BigDecimal value) {
490         if (list == null || value == null)
491             return false;
492         for (BigDecimal val : list) {
493             if (val.compareTo(value) == 0)
494                 return true;
495         }
496         return false;
497     }
498 
499     public BusinessObjectService getBusinessObjectService() {
500         return businessObjectService;
501     }
502 
503     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
504         this.businessObjectService = businessObjectService;
505     }
506 }