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