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 }