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.fp.document.service.impl; 017 018 import java.math.BigDecimal; 019 import java.util.ArrayList; 020 import java.util.Arrays; 021 import java.util.Collection; 022 import java.util.HashMap; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Map; 026 027 import org.apache.commons.lang.StringUtils; 028 import org.kuali.kfs.fp.businessobject.DisbursementVoucherNonResidentAlienTax; 029 import org.kuali.kfs.fp.businessobject.NonResidentAlienTaxPercent; 030 import org.kuali.kfs.fp.document.DisbursementVoucherConstants; 031 import org.kuali.kfs.fp.document.DisbursementVoucherDocument; 032 import org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService; 033 import org.kuali.kfs.sys.KFSConstants; 034 import org.kuali.kfs.sys.KFSKeyConstants; 035 import org.kuali.kfs.sys.KFSPropertyConstants; 036 import org.kuali.kfs.sys.businessobject.AccountingLine; 037 import org.kuali.kfs.sys.businessobject.SourceAccountingLine; 038 import org.kuali.kfs.sys.context.SpringContext; 039 import org.kuali.kfs.vnd.businessobject.VendorDetail; 040 import org.kuali.rice.kim.bo.Person; 041 import org.kuali.rice.kim.service.PersonService; 042 import org.kuali.rice.kns.bo.PersistableBusinessObject; 043 import org.kuali.rice.kns.exception.InfrastructureException; 044 import org.kuali.rice.kns.service.BusinessObjectService; 045 import org.kuali.rice.kns.service.MaintenanceDocumentService; 046 import org.kuali.rice.kns.service.ParameterService; 047 import org.kuali.rice.kns.util.ErrorMap; 048 import org.kuali.rice.kns.util.GlobalVariables; 049 import org.kuali.rice.kns.util.KualiDecimal; 050 import org.kuali.rice.kns.util.MessageMap; 051 052 /** 053 * This is the default implementation of the DisbursementVoucherExtractService interface. 054 * This class handles queries and validation on tax id numbers. 055 */ 056 public class DisbursementVoucherTaxServiceImpl implements DisbursementVoucherTaxService, DisbursementVoucherConstants { 057 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisbursementVoucherTaxServiceImpl.class); 058 059 private ParameterService parameterService; 060 private BusinessObjectService businessObjectService; 061 private MaintenanceDocumentService maintenanceDocumentService; 062 063 /** 064 * This method retrieves the universal id of the individual or business entity who matches the tax id number and type 065 * code given. 066 * 067 * @param taxIDNumber The tax identification number of the user being retrieved. 068 * @param taxPayerTypeCode The tax payer type code of the user being retrieved. See the TAX_TYPE_* constants defined in 069 * DisbursementVoucherRuleConstants for examples of valid tax type codes. 070 * @return The universal id of the individual who matches the tax id and type code given. Null if no matching user is found. 071 * 072 * @see org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService#getEmployeeNumber(java.lang.String, java.lang.String) 073 */ 074 public String getUniversalId(String taxIDNumber, String taxPayerTypeCode) { 075 if (TAX_TYPE_FEIN.equals(taxPayerTypeCode)) { 076 return null; 077 } 078 079 Person person = (Person) SpringContext.getBean(PersonService.class).getPersonByExternalIdentifier(org.kuali.rice.kim.util.KimConstants.PersonExternalIdentifierTypes.TAX, taxIDNumber).get(0); 080 081 String universalId = null; 082 if (person != null) { 083 universalId = person.getPrincipalId(); 084 } 085 return universalId; 086 } 087 088 /** 089 * This method retrieves the vendor identification code for the vendor found who has a matching tax id and tax payer type 090 * code. 091 * 092 * @param taxIDNumber The tax id number used to retrieve the associated vendor. 093 * @param taxPayerTypeCode The tax payer type code used to retrieve the associated vendor. See the TAX_TYPE_* constants defined in 094 * DisbursementVoucherRuleConstants for examples of valid tax type codes. 095 * @return The id of the vendor found matching the tax id and type code provided. Null if no matching vendor is found. 096 * 097 * @see org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService#getPayeeNumber(java.lang.String, java.lang.String) 098 */ 099 public String getVendorId(String taxIDNumber, String taxPayerTypeCode) { 100 String vendorId = null; 101 102 Map taxIDCrit = new HashMap(); 103 taxIDCrit.put("taxIdNumber", taxIDNumber); 104 taxIDCrit.put("taxpayerTypeCode", taxPayerTypeCode); 105 Collection<VendorDetail> foundPayees = businessObjectService.findMatching(VendorDetail.class, taxIDCrit); 106 107 if (!foundPayees.isEmpty()) { 108 VendorDetail vendor = (VendorDetail) foundPayees.iterator().next(); 109 vendorId = vendor.getVendorHeaderGeneratedIdentifier().toString(); 110 } 111 112 return vendorId; 113 } 114 115 /** 116 * This method generates non-resident alien (NRA) tax lines for the given disbursement voucher. 117 * 118 * The NRA tax lines consist of three possible sets of tax lines: 119 * - Gross up tax lines 120 * - Federal tax lines 121 * - State tax lines 122 * 123 * Gross up tax lines are generated if the income tax gross up code is set on the DisbursementVoucherNonResidentAlienTax 124 * attribute of the disbursement voucher. 125 * 126 * Federal tax lines are generated if the federal tax rate in the DisbursementVoucherNonResidentAlienTax attribute is 127 * other than zero. 128 * 129 * State tax lines are generated if the state tax rate in the DisbursementVoucherNonResidentAlienTax attribute is 130 * other than zero. 131 * 132 * @param document The disbursement voucher the NRA tax lines will be added to. 133 * 134 * @see org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService#generateNRATaxLines(org.kuali.kfs.fp.document.DisbursementVoucherDocument) 135 */ 136 protected void generateNRATaxLines(DisbursementVoucherDocument document) { 137 // retrieve first accounting line for tax line attributes 138 AccountingLine line1 = document.getSourceAccountingLine(0); 139 140 List taxLineNumbers = new ArrayList(); 141 142 // generate gross up 143 if (document.getDvNonResidentAlienTax().isIncomeTaxGrossUpCode()) { 144 AccountingLine grossLine = null; 145 try { 146 grossLine = (SourceAccountingLine) document.getSourceAccountingLineClass().newInstance(); 147 } 148 catch (IllegalAccessException e) { 149 throw new InfrastructureException("unable to access sourceAccountingLineClass", e); 150 } 151 catch (InstantiationException e) { 152 throw new InfrastructureException("unable to instantiate sourceAccountingLineClass", e); 153 } 154 155 grossLine.setDocumentNumber(document.getDocumentNumber()); 156 grossLine.setSequenceNumber(document.getNextSourceLineNumber()); 157 grossLine.setChartOfAccountsCode(line1.getChartOfAccountsCode()); 158 grossLine.setAccountNumber(line1.getAccountNumber()); 159 grossLine.setFinancialObjectCode(line1.getFinancialObjectCode()); 160 161 // calculate gross up amount and set as line amount 162 BigDecimal federalTaxPercent = document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent().bigDecimalValue(); 163 BigDecimal stateTaxPercent = document.getDvNonResidentAlienTax().getStateIncomeTaxPercent().bigDecimalValue(); 164 BigDecimal documentAmount = document.getDisbVchrCheckTotalAmount().bigDecimalValue(); 165 166 KualiDecimal grossAmount1 = new KualiDecimal((documentAmount.multiply(federalTaxPercent).divide(new BigDecimal(100).subtract(federalTaxPercent).subtract(stateTaxPercent), 5, BigDecimal.ROUND_HALF_UP))); 167 KualiDecimal grossAmount2 = new KualiDecimal((documentAmount.multiply(stateTaxPercent).divide(new BigDecimal(100).subtract(federalTaxPercent).subtract(stateTaxPercent), 5, BigDecimal.ROUND_HALF_UP))); 168 grossLine.setAmount(grossAmount1.add(grossAmount2)); 169 170 // put line number in line number list, and update next line property in document 171 taxLineNumbers.add(grossLine.getSequenceNumber()); 172 document.setNextSourceLineNumber(new Integer(document.getNextSourceLineNumber().intValue() + 1)); 173 174 // add to source accounting lines 175 grossLine.refresh(); 176 document.getSourceAccountingLines().add(grossLine); 177 178 // update check total, is added because line amount is negative, so this will take check amount down 179 document.setDisbVchrCheckTotalAmount(document.getDisbVchrCheckTotalAmount().add(grossLine.getAmount())); 180 } 181 182 KualiDecimal taxableAmount = document.getDisbVchrCheckTotalAmount(); 183 184 // generate federal tax line 185 if (!(KualiDecimal.ZERO.equals(document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent()))) { 186 String federalTaxChart = parameterService.getParameterValue(DisbursementVoucherDocument.class, DisbursementVoucherConstants.FEDERAL_TAX_PARM_PREFIX + DisbursementVoucherConstants.TAX_PARM_CHART_SUFFIX); 187 String federalTaxAccount = parameterService.getParameterValue(DisbursementVoucherDocument.class, DisbursementVoucherConstants.FEDERAL_TAX_PARM_PREFIX + DisbursementVoucherConstants.TAX_PARM_ACCOUNT_SUFFIX); 188 String federalTaxObjectCode = parameterService.getParameterValue(DisbursementVoucherDocument.class, DisbursementVoucherConstants.FEDERAL_TAX_PARM_PREFIX + DisbursementVoucherConstants.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, document.getDvNonResidentAlienTax().getIncomeClassCode()); 189 if (StringUtils.isBlank(federalTaxChart) || StringUtils.isBlank(federalTaxAccount) || StringUtils.isBlank(federalTaxObjectCode)) { 190 LOG.error("Unable to retrieve federal tax parameters."); 191 throw new RuntimeException("Unable to retrieve federal tax parameters."); 192 } 193 194 AccountingLine federalTaxLine = generateTaxAccountingLine(document, federalTaxChart, federalTaxAccount, federalTaxObjectCode, document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent(), taxableAmount); 195 196 // put line number in line number list, and update next line property in document 197 taxLineNumbers.add(federalTaxLine.getSequenceNumber()); 198 document.setNextSourceLineNumber(new Integer(document.getNextSourceLineNumber().intValue() + 1)); 199 200 // add to source accounting lines 201 federalTaxLine.refresh(); 202 document.getSourceAccountingLines().add(federalTaxLine); 203 204 // update check total, is added because line amount is negative, so this will take check amount down 205 document.setDisbVchrCheckTotalAmount(document.getDisbVchrCheckTotalAmount().add(federalTaxLine.getAmount())); 206 } 207 208 // generate state tax line 209 if (!(KualiDecimal.ZERO.equals(document.getDvNonResidentAlienTax().getStateIncomeTaxPercent()))) { 210 String stateTaxChart = parameterService.getParameterValue(DisbursementVoucherDocument.class, DisbursementVoucherConstants.STATE_TAX_PARM_PREFIX + DisbursementVoucherConstants.TAX_PARM_CHART_SUFFIX); 211 String stateTaxAccount = parameterService.getParameterValue(DisbursementVoucherDocument.class, DisbursementVoucherConstants.STATE_TAX_PARM_PREFIX + DisbursementVoucherConstants.TAX_PARM_ACCOUNT_SUFFIX); 212 String stateTaxObjectCode = parameterService.getParameterValues(DisbursementVoucherDocument.class, DisbursementVoucherConstants.STATE_TAX_PARM_PREFIX + DisbursementVoucherConstants.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, document.getDvNonResidentAlienTax().getIncomeClassCode()).get(0); 213 214 if (StringUtils.isBlank(stateTaxChart) || StringUtils.isBlank(stateTaxAccount) || StringUtils.isBlank(stateTaxObjectCode)) { 215 LOG.error("Unable to retrieve state tax parameters."); 216 throw new RuntimeException("Unable to retrieve state tax parameters."); 217 } 218 219 AccountingLine stateTaxLine = generateTaxAccountingLine(document, stateTaxChart, stateTaxAccount, stateTaxObjectCode, document.getDvNonResidentAlienTax().getStateIncomeTaxPercent(), taxableAmount); 220 221 // put line number in line number list, and update next line property in document 222 taxLineNumbers.add(stateTaxLine.getSequenceNumber()); 223 document.setNextSourceLineNumber(new Integer(document.getNextSourceLineNumber().intValue() + 1)); 224 225 // add to source accounting lines 226 stateTaxLine.refresh(); 227 document.getSourceAccountingLines().add(stateTaxLine); 228 229 // update check total, is added because line amount is negative, so this will take check amount down 230 document.setDisbVchrCheckTotalAmount(document.getDisbVchrCheckTotalAmount().add(stateTaxLine.getAmount())); 231 } 232 233 // update line number field 234 document.getDvNonResidentAlienTax().setFinancialDocumentAccountingLineText(StringUtils.join(taxLineNumbers.iterator(), ",")); 235 } 236 237 /** 238 * Generates an accounting line for the chart, account, object code & tax percentage values given. 239 * 240 * @param document The disbursement voucher the tax will be applied to. 241 * @param chart The chart code to be assigned to the accounting line generated. 242 * @param account The account code to be assigned to the accounting line generated. 243 * @param objectCode The object code used on the accounting line generated. 244 * @param taxPercent The tax rate to be used to calculate the tax amount. 245 * @param taxableAmount The total amount that is taxable. This amount is used in conjunction with the tax percent 246 * to calculate the amount for the accounting lined being generated. 247 * @return A fully populated AccountingLine instance representing the amount of tax that will be applied to the 248 * disbursement voucher provided. 249 */ 250 protected AccountingLine generateTaxAccountingLine(DisbursementVoucherDocument document, String chart, String account, String objectCode, KualiDecimal taxPercent, KualiDecimal taxableAmount) { 251 AccountingLine taxLine = null; 252 try { 253 taxLine = (SourceAccountingLine) document.getSourceAccountingLineClass().newInstance(); 254 } 255 catch (IllegalAccessException e) { 256 throw new InfrastructureException("unable to access sourceAccountingLineClass", e); 257 } 258 catch (InstantiationException e) { 259 throw new InfrastructureException("unable to instantiate sourceAccountingLineClass", e); 260 } 261 262 taxLine.setDocumentNumber(document.getDocumentNumber()); 263 taxLine.setSequenceNumber(document.getNextSourceLineNumber()); 264 taxLine.setChartOfAccountsCode(chart); 265 taxLine.setAccountNumber(account); 266 taxLine.setFinancialObjectCode(objectCode); 267 268 // calculate tax amount and set as line amount 269 BigDecimal amount = taxableAmount.bigDecimalValue(); 270 BigDecimal tax = taxPercent.bigDecimalValue(); 271 BigDecimal taxDecimal = tax.divide(new BigDecimal(100), 5, BigDecimal.ROUND_HALF_UP); 272 KualiDecimal taxAmount = new KualiDecimal(amount.multiply(taxDecimal).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)); 273 taxLine.setAmount(taxAmount.negated()); 274 275 return taxLine; 276 } 277 278 279 /** 280 * This method validates the non-resident alien (NRA) tax information for the document and if the information validates, 281 * the NRA tax lines are generated. 282 * 283 * @param document The disbursement voucher document the NRA tax information will be validated and the subsequent 284 * tax lines generated for. 285 * 286 * @see org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService#processNonResidentAlienTax(org.kuali.kfs.fp.document.DisbursementVoucherDocument, 287 * java.util.List) 288 */ 289 public void processNonResidentAlienTax(DisbursementVoucherDocument document) { 290 if (validateNRATaxInformation(document)) { 291 generateNRATaxLines(document); 292 } 293 } 294 295 /** 296 * Removes non-resident alien (NRA) tax lines from the document's accounting lines and updates the check total. 297 * 298 * @param document The disbursement voucher the NRA tax lines will be removed from. 299 */ 300 public void clearNRATaxLines(DisbursementVoucherDocument document) { 301 ArrayList<SourceAccountingLine> taxLines = new ArrayList<SourceAccountingLine>(); 302 KualiDecimal taxTotal = KualiDecimal.ZERO; 303 304 DisbursementVoucherNonResidentAlienTax dvnrat = document.getDvNonResidentAlienTax(); 305 if (dvnrat != null) { 306 List<Integer> previousTaxLineNumbers = getNRATaxLineNumbers(dvnrat.getFinancialDocumentAccountingLineText()); 307 308 // get tax lines out of source lines 309 boolean previousGrossUp = false; 310 List<SourceAccountingLine> srcLines = document.getSourceAccountingLines(); 311 for (SourceAccountingLine line : srcLines) { 312 if (previousTaxLineNumbers.contains(line.getSequenceNumber())) { 313 taxLines.add(line); 314 315 // check if tax line was a positive amount, in which case we had a gross up 316 if ((KualiDecimal.ZERO).compareTo(line.getAmount()) < 0) { 317 previousGrossUp = true; 318 } 319 else { 320 taxTotal = taxTotal.add(line.getAmount().abs()); 321 } 322 } 323 } 324 325 // remove tax lines 326 /* 327 * NOTE: a custom remove method needed to be used here because the .equals() method for 328 * AccountingLineBase does not take amount into account when determining equality. 329 * This lead to the issues described in KULRNE-6201. 330 */ 331 Iterator<SourceAccountingLine> saLineIter = document.getSourceAccountingLines().iterator(); 332 while(saLineIter.hasNext()) { 333 SourceAccountingLine saLine = saLineIter.next(); 334 for(SourceAccountingLine taxLine : taxLines) { 335 if(saLine.equals(taxLine)) { 336 if(saLine.getAmount().equals(taxLine.getAmount())) { 337 saLineIter.remove(); 338 } 339 } 340 } 341 } 342 343 // update check total if not grossed up 344 if (!previousGrossUp) { 345 document.setDisbVchrCheckTotalAmount(document.getDisbVchrCheckTotalAmount().add(taxTotal)); 346 } 347 348 // clear line string 349 dvnrat.setFinancialDocumentAccountingLineText(""); 350 } 351 } 352 353 /** 354 * This method retrieves the non-resident alien (NRA) tax amount using the disbursement voucher given to calculate the 355 * amount. If the vendor is not a non-resident alien or they are and there is no gross up code set, the amount returned 356 * will be zero. If the vendor is a non-resident alien and gross up has been set, the amount is calculated by 357 * retrieving all the source accounting lines for the disbursement voucher provided and summing the amounts of all the 358 * lines that are NRA tax lines. 359 * 360 * @param document The disbursement voucher the NRA tax line amount will be calculated for. 361 * @return The NRA tax amount applicable to the given disbursement voucher or zero if the voucher does not have any 362 * NRA tax lines. 363 * 364 * @see org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService#getNonResidentAlienTaxAmount(org.kuali.kfs.fp.document.DisbursementVoucherDocument) 365 */ 366 public KualiDecimal getNonResidentAlienTaxAmount(DisbursementVoucherDocument document) { 367 KualiDecimal taxAmount = KualiDecimal.ZERO; 368 369 // if not nra payment or gross has been done, no tax amount should have been taken out 370 if (!document.getDvPayeeDetail().isDisbVchrAlienPaymentCode() || (document.getDvPayeeDetail().isDisbVchrAlienPaymentCode() && document.getDvNonResidentAlienTax().isIncomeTaxGrossUpCode())) { 371 return taxAmount; 372 } 373 374 // get tax line numbers 375 List taxLineNumbers = getNRATaxLineNumbers(document.getDvNonResidentAlienTax().getFinancialDocumentAccountingLineText()); 376 377 for (Iterator iter = document.getSourceAccountingLines().iterator(); iter.hasNext();) { 378 SourceAccountingLine line = (SourceAccountingLine) iter.next(); 379 380 // check if line is nra tax line 381 if (taxLineNumbers.contains(line.getSequenceNumber())) { 382 taxAmount = taxAmount.add(line.getAmount().negated()); 383 } 384 } 385 386 return taxAmount; 387 } 388 389 /** 390 * This method performs a series of validation checks to ensure that the disbursement voucher given contains non-resident 391 * alien specific information and non-resident alien tax lines are necessary. 392 * 393 * The following steps are taken to validate the disbursement voucher given: 394 * - Set all percentages (ie. federal, state) to zero if their current value is null. 395 * - Call DisbursementVoucherDocumentRule.validateNonResidentAlienInformation to perform more in-depth validation. 396 * - The vendor for the disbursement voucher given is a non-resident alien. 397 * - No reference document exists for the assigned DisbursementVoucherNonResidentAlienTax attribute of the voucher given. 398 * - There is at least one source accounting line to generate the tax line from. 399 * - Both the state and federal tax percentages are greater than zero. 400 * - The total check amount is not negative. 401 * - The total of the accounting lines is not negative. 402 * - The total check amount is equal to the total of the accounting lines. 403 * 404 * 405 * @param document The disbursement voucher document to validate the tax lines for. 406 * @return True if the information associated with non-resident alien tax is correct and valid, false otherwise. 407 * 408 * @see org.kuali.kfs.fp.document.service.DisbursementVoucherTaxService#validateNRATaxInformation(org.kuali.kfs.fp.document.DisbursementVoucherDocument) 409 * @see org.kuali.kfs.fp.document.validation.impl.DisbursementVoucherDocumentRule#validateNonResidentAlienInformation(DisbursementVoucherDocument) 410 */ 411 protected boolean validateNRATaxInformation(DisbursementVoucherDocument document) { 412 MessageMap errors = GlobalVariables.getMessageMap(); 413 414 // set nulls to 0 415 if (document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent() == null) { 416 document.getDvNonResidentAlienTax().setFederalIncomeTaxPercent(KualiDecimal.ZERO); 417 } 418 419 if (document.getDvNonResidentAlienTax().getStateIncomeTaxPercent() == null) { 420 document.getDvNonResidentAlienTax().setStateIncomeTaxPercent(KualiDecimal.ZERO); 421 } 422 423 validateNonResidentAlienInformation(document); 424 425 if (!GlobalVariables.getMessageMap().isEmpty()) { 426 return false; 427 } 428 429 /* make sure vendor is nra */ 430 if (!document.getDvPayeeDetail().isDisbVchrAlienPaymentCode()) { 431 errors.putErrorWithoutFullErrorPath("DVNRATaxErrors", KFSKeyConstants.ERROR_DV_GENERATE_TAX_NOT_NRA); 432 return false; 433 } 434 435 /* don't generate tax if reference doc is given */ 436 if (StringUtils.isNotBlank(document.getDvNonResidentAlienTax().getReferenceFinancialDocumentNumber())) { 437 errors.putErrorWithoutFullErrorPath("DVNRATaxErrors", KFSKeyConstants.ERROR_DV_GENERATE_TAX_DOC_REFERENCE); 438 return false; 439 } 440 441 // check attributes needed to generate lines 442 /* need at least 1 line */ 443 if (!(document.getSourceAccountingLines().size() >= 1)) { 444 errors.putErrorWithoutFullErrorPath("DVNRATaxErrors", KFSKeyConstants.ERROR_DV_GENERATE_TAX_NO_SOURCE); 445 return false; 446 } 447 448 /* make sure both fed and state tax percents are not 0, in which case there is no need to generate lines */ 449 if (KualiDecimal.ZERO.equals(document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent()) && KualiDecimal.ZERO.equals(document.getDvNonResidentAlienTax().getStateIncomeTaxPercent())) { 450 errors.putErrorWithoutFullErrorPath("DVNRATaxErrors", KFSKeyConstants.ERROR_DV_GENERATE_TAX_BOTH_0); 451 return false; 452 } 453 454 /* check total cannot be negative */ 455 if (KualiDecimal.ZERO.compareTo(document.getDisbVchrCheckTotalAmount()) == 1) { 456 errors.putErrorWithoutFullErrorPath("document.disbVchrCheckTotalAmount", KFSKeyConstants.ERROR_NEGATIVE_OR_ZERO_CHECK_TOTAL); 457 return false; 458 } 459 460 /* total accounting lines cannot be negative */ 461 if (KualiDecimal.ZERO.compareTo(document.getSourceTotal()) == 1) { 462 errors.putErrorWithoutFullErrorPath(KFSConstants.ACCOUNTING_LINE_ERRORS, KFSKeyConstants.ERROR_NEGATIVE_ACCOUNTING_TOTAL); 463 return false; 464 } 465 466 /* total of accounting lines must match check total */ 467 if (document.getDisbVchrCheckTotalAmount().compareTo(document.getSourceTotal()) != 0) { 468 errors.putErrorWithoutFullErrorPath(KFSConstants.ACCOUNTING_LINE_ERRORS, KFSKeyConstants.ERROR_CHECK_ACCOUNTING_TOTAL); 469 return false; 470 } 471 472 return true; 473 } 474 475 /** 476 * Parses the tax line string given and returns a list of line numbers as Integers. 477 * 478 * @param taxLineString The string to be parsed. 479 * @return A collection of line numbers represented as Integers. 480 */ 481 public List<Integer> getNRATaxLineNumbers(String taxLineString) { 482 List<Integer> taxLineNumbers = new ArrayList(); 483 if (StringUtils.isNotBlank(taxLineString)) { 484 List<String> taxLineNumberStrings = Arrays.asList(StringUtils.split(taxLineString, ",")); 485 for (String lineNumber : taxLineNumberStrings) { 486 taxLineNumbers.add(Integer.valueOf(lineNumber)); 487 } 488 } 489 490 return taxLineNumbers; 491 } 492 493 /** 494 * This method sets the parameterService attribute to the value given. 495 * @param parameterService The ParameterService to be set. 496 */ 497 public void setParameterService(ParameterService parameterService) { 498 this.parameterService = parameterService; 499 } 500 501 /** 502 * Gets the value of the businessObjectService instance. 503 * @return Returns the businessObjectService. 504 */ 505 public BusinessObjectService getBusinessObjectService() { 506 return businessObjectService; 507 } 508 509 /** 510 * This method sets the businessObjectService attribute to the value given. 511 * @param businessObjectService The businessObjectService to set. 512 */ 513 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 514 this.businessObjectService = businessObjectService; 515 } 516 517 /** 518 * Gets the value of the maintenanceDocumentService instance. 519 * @return Returns the maintenanceDocumentService. 520 */ 521 public MaintenanceDocumentService getMaintenanceDocumentService() { 522 return maintenanceDocumentService; 523 } 524 525 /** 526 * This method sets the maintenanceDocumentService attribute to the value given. 527 * @param maintenanceDocumentService The maintenanceDocumentService to set. 528 */ 529 public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) { 530 this.maintenanceDocumentService = maintenanceDocumentService; 531 } 532 533 /** 534 * Validates fields for an alien payment. 535 * 536 * @param document submitted disbursement voucher document 537 */ 538 public void validateNonResidentAlienInformation(DisbursementVoucherDocument document) { 539 MessageMap errors = GlobalVariables.getMessageMap(); 540 541 errors.addToErrorPath(KFSPropertyConstants.DV_NON_RESIDENT_ALIEN_TAX); 542 543 /* income class code required */ 544 if (StringUtils.isBlank(document.getDvNonResidentAlienTax().getIncomeClassCode())) { 545 errors.putError(KFSPropertyConstants.INCOME_CLASS_CODE, KFSKeyConstants.ERROR_REQUIRED, "Income class code "); 546 } 547 else { 548 /* for foreign source or treaty exempt, non reportable, tax percents must be 0 and gross indicator can not be checked */ 549 if (document.getDvNonResidentAlienTax().isForeignSourceIncomeCode() || document.getDvNonResidentAlienTax().isIncomeTaxTreatyExemptCode() || NRA_TAX_INCOME_CLASS_NON_REPORTABLE.equals(document.getDvNonResidentAlienTax().getIncomeClassCode())) { 550 551 if ((document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent() != null && !(KualiDecimal.ZERO.equals(document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent())))) { 552 errors.putError(KFSPropertyConstants.FEDERAL_INCOME_TAX_PERCENT, KFSKeyConstants.ERROR_DV_FEDERAL_TAX_NOT_ZERO); 553 } 554 555 if ((document.getDvNonResidentAlienTax().getStateIncomeTaxPercent() != null && !(KualiDecimal.ZERO.equals(document.getDvNonResidentAlienTax().getStateIncomeTaxPercent())))) { 556 errors.putError(KFSPropertyConstants.STATE_INCOME_TAX_PERCENT, KFSKeyConstants.ERROR_DV_STATE_TAX_NOT_ZERO); 557 } 558 559 if (document.getDvNonResidentAlienTax().isIncomeTaxGrossUpCode()) { 560 errors.putError(KFSPropertyConstants.INCOME_TAX_GROSS_UP_CODE, KFSKeyConstants.ERROR_DV_GROSS_UP_INDICATOR); 561 } 562 563 if (NRA_TAX_INCOME_CLASS_NON_REPORTABLE.equals(document.getDvNonResidentAlienTax().getIncomeClassCode()) && StringUtils.isNotBlank(document.getDvNonResidentAlienTax().getPostalCountryCode())) { 564 errors.putError(KFSPropertyConstants.POSTAL_COUNTRY_CODE, KFSKeyConstants.ERROR_DV_POSTAL_COUNTRY_CODE); 565 } 566 } 567 else { 568 if (document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent() == null) { 569 errors.putError(KFSPropertyConstants.FEDERAL_INCOME_TAX_PERCENT, KFSKeyConstants.ERROR_REQUIRED, "Federal tax percent "); 570 } 571 else { 572 // check tax percent is in non-resident alien tax percent table for income class code 573 NonResidentAlienTaxPercent taxPercent = new NonResidentAlienTaxPercent(); 574 taxPercent.setIncomeClassCode(document.getDvNonResidentAlienTax().getIncomeClassCode()); 575 taxPercent.setIncomeTaxTypeCode(FEDERAL_TAX_TYPE_CODE); 576 taxPercent.setIncomeTaxPercent(document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent()); 577 578 PersistableBusinessObject retrievedPercent = getBusinessObjectService().retrieve(taxPercent); 579 if (retrievedPercent == null) { 580 errors.putError(KFSPropertyConstants.FEDERAL_INCOME_TAX_PERCENT, KFSKeyConstants.ERROR_DV_INVALID_FED_TAX_PERCENT, new String[] { document.getDvNonResidentAlienTax().getFederalIncomeTaxPercent().toString(), document.getDvNonResidentAlienTax().getIncomeClassCode() }); 581 } 582 } 583 584 if (document.getDvNonResidentAlienTax().getStateIncomeTaxPercent() == null) { 585 errors.putError(KFSPropertyConstants.STATE_INCOME_TAX_PERCENT, KFSKeyConstants.ERROR_REQUIRED, "State tax percent "); 586 } 587 else { 588 NonResidentAlienTaxPercent taxPercent = new NonResidentAlienTaxPercent(); 589 taxPercent.setIncomeClassCode(document.getDvNonResidentAlienTax().getIncomeClassCode()); 590 taxPercent.setIncomeTaxTypeCode(STATE_TAX_TYPE_CODE); 591 taxPercent.setIncomeTaxPercent(document.getDvNonResidentAlienTax().getStateIncomeTaxPercent()); 592 593 PersistableBusinessObject retrievedPercent = getBusinessObjectService().retrieve(taxPercent); 594 if (retrievedPercent == null) { 595 errors.putError(KFSPropertyConstants.STATE_INCOME_TAX_PERCENT, KFSKeyConstants.ERROR_DV_INVALID_STATE_TAX_PERCENT, new String[] { document.getDvNonResidentAlienTax().getStateIncomeTaxPercent().toString(), document.getDvNonResidentAlienTax().getIncomeClassCode() }); 596 } 597 } 598 } 599 } 600 601 /* country code required, unless income type is nonreportable */ 602 if (StringUtils.isBlank(document.getDvNonResidentAlienTax().getPostalCountryCode()) && !NRA_TAX_INCOME_CLASS_NON_REPORTABLE.equals(document.getDvNonResidentAlienTax().getIncomeClassCode())) { 603 errors.putError(KFSPropertyConstants.POSTAL_COUNTRY_CODE, KFSKeyConstants.ERROR_REQUIRED, "Country code "); 604 } 605 606 errors.removeFromErrorPath(KFSPropertyConstants.DV_NON_RESIDENT_ALIEN_TAX); 607 } 608 609 } 610