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