001/*
002 * Copyright 2009 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 */
016package org.kuali.ole.module.purap.document.validation.impl;
017
018import org.apache.commons.lang.ObjectUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.ole.fp.businessobject.NonResidentAlienTaxPercent;
021import org.kuali.ole.module.purap.PurapConstants;
022import org.kuali.ole.module.purap.PurapConstants.PaymentRequestStatuses;
023import org.kuali.ole.module.purap.PurapKeyConstants;
024import org.kuali.ole.module.purap.PurapPropertyConstants;
025import org.kuali.ole.module.purap.document.PaymentRequestDocument;
026import org.kuali.ole.sys.document.validation.GenericValidation;
027import org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent;
028import org.kuali.rice.core.api.util.type.KualiDecimal;
029import org.kuali.rice.krad.service.BusinessObjectService;
030import org.kuali.rice.krad.util.GlobalVariables;
031import org.kuali.rice.krad.util.MessageMap;
032
033import java.math.BigDecimal;
034import java.util.ArrayList;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038
039public class PaymentRequestTaxAreaValidation extends GenericValidation {
040
041    private BusinessObjectService businessObjectService;
042
043    /** Map for allowed federal and state tax rates based on income class. *
044     private static HashMap<String, ArrayList<BigDecimal>> federalTaxRates;
045     private static HashMap<String, ArrayList<BigDecimal>> stateTaxRates;
046
047     //TODO these rates shall be kept in DB tables or as parameter
048     // set up the tax rate maps
049     static {
050     federalTaxRates = new HashMap<String, ArrayList<BigDecimal>>();
051     stateTaxRates = new HashMap<String, ArrayList<BigDecimal>>();
052
053     ArrayList<BigDecimal> fedrates = new ArrayList<BigDecimal>();
054     fedrates.add(new BigDecimal(30));
055     fedrates.add(new BigDecimal(14));
056     fedrates.add(new BigDecimal(0));
057     federalTaxRates.put("F", fedrates);
058
059     fedrates = new ArrayList<BigDecimal>();
060     fedrates.add(new BigDecimal(30));
061     fedrates.add(new BigDecimal(15));
062     fedrates.add(new BigDecimal(10));
063     fedrates.add(new BigDecimal(5));
064     fedrates.add(new BigDecimal(0));
065     federalTaxRates.put("R", fedrates);
066
067     fedrates = new ArrayList<BigDecimal>();
068     fedrates.add(new BigDecimal(30));
069     fedrates.add(new BigDecimal(0));
070     federalTaxRates.put("I", fedrates);
071     federalTaxRates.put("A", fedrates);
072     federalTaxRates.put("O", fedrates);
073
074     ArrayList<BigDecimal> strates = new ArrayList<BigDecimal>();
075     strates.add(new BigDecimal("3.40"));
076     strates.add(new BigDecimal(0));
077     stateTaxRates.put("F", strates);
078     stateTaxRates.put("A", strates);
079     stateTaxRates.put("O", strates);
080
081     strates = new ArrayList<BigDecimal>();
082     strates.add(new BigDecimal(0));
083     stateTaxRates.put("I", strates);
084     stateTaxRates.put("R", strates);
085     }
086     */
087
088    /**
089     * Process business rules applicable to tax area data before calculating the withholding tax on payment request.
090     *
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 (!StringUtils.equals(preq.getApplicationDocumentStatus(), PaymentRequestStatuses.APPDOC_AWAITING_TAX_REVIEW))
099            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(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     *
120     * @param paymentRequest - payment request document
121     * @return true if this validation passes; false otherwise.
122     */
123    protected boolean validateTaxIncomeClass(PaymentRequestDocument preq) {
124        boolean valid = true;
125        MessageMap errorMap = GlobalVariables.getMessageMap();
126
127        // TaxClassificationCode is required field
128        if (StringUtils.isEmpty(preq.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(preq.getTaxClassificationCode(), "N")) {
134            if (preq.getTaxFederalPercent() != null && preq.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 (preq.getTaxStatePercent() != null && preq.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(preq.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(preq.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 (preq.getTaxSpecialW4Amount() != null && preq.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(preq.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(preq.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(preq.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(preq.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(preq.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 (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     *
197     * @param paymentRequest - payment request document
198     * @return true if this validation passes; false otherwise.
199     */
200    protected boolean validateTaxRates(PaymentRequestDocument preq) {
201        boolean valid = true;
202        String code = preq.getTaxClassificationCode();
203        BigDecimal fedrate = preq.getTaxFederalPercent();
204        BigDecimal strate = preq.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 paymentRequest - payment request document
241     * @return true if this validation passes; false otherwise.
242     */
243    protected boolean validateTaxIndicators(PaymentRequestDocument preq) {
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(preq.getTaxExemptTreatyIndicator(), true)) {
249            if (ObjectUtils.equals(preq.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(preq.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(preq.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(preq.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 (preq.getTaxSpecialW4Amount() != null && preq.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 (preq.getTaxFederalPercent() != null && preq.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 (preq.getTaxStatePercent() != null && preq.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(preq.getTaxGrossUpIndicator(), true)) {
282            if (ObjectUtils.equals(preq.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(preq.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(preq.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(preq.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 (preq.getTaxSpecialW4Amount() != null && preq.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 (preq.getTaxFederalPercent() == null || preq.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(preq.getTaxForeignSourceIndicator(), true)) {
310            if (ObjectUtils.equals(preq.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(preq.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(preq.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(preq.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 (preq.getTaxSpecialW4Amount() != null && preq.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 (preq.getTaxFederalPercent() != null && preq.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 (preq.getTaxStatePercent() != null && preq.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(preq.getTaxUSAIDPerDiemIndicator(), true)) {
342            if (ObjectUtils.equals(preq.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(preq.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(preq.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(preq.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 (preq.getTaxSpecialW4Amount() != null && preq.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(preq.getTaxClassificationCode()) || !StringUtils.equalsIgnoreCase(preq.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 (preq.getTaxFederalPercent() != null && preq.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 (preq.getTaxStatePercent() != null && preq.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(preq.getTaxOtherExemptIndicator(), true)) {
378            if (ObjectUtils.equals(preq.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(preq.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(preq.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 (preq.getTaxSpecialW4Amount() != null && preq.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 (preq.getTaxFederalPercent() != null && preq.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 (preq.getTaxStatePercent() != null && preq.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 (preq.getTaxSpecialW4Amount() != null && preq.getTaxSpecialW4Amount().compareTo(new KualiDecimal(0)) != 0) {
406            if (ObjectUtils.equals(preq.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(preq.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(preq.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 (preq.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(preq.getTaxClassificationCode()) || !StringUtils.equalsIgnoreCase(preq.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 (preq.getTaxFederalPercent() != null && preq.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 (preq.getTaxStatePercent() != null && preq.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}