View Javadoc
1   /*
2    * Copyright 2008 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.fp.document.validation.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.ole.fp.businessobject.DisbursementVoucherNonEmployeeTravel;
20  import org.kuali.ole.fp.document.DisbursementVoucherConstants;
21  import org.kuali.ole.fp.document.DisbursementVoucherDocument;
22  import org.kuali.ole.fp.document.service.DisbursementVoucherTaxService;
23  import org.kuali.ole.fp.document.service.DisbursementVoucherTravelService;
24  import org.kuali.ole.sys.OLEConstants;
25  import org.kuali.ole.sys.OLEKeyConstants;
26  import org.kuali.ole.sys.OLEPropertyConstants;
27  import org.kuali.ole.sys.context.SpringContext;
28  import org.kuali.ole.sys.document.AccountingDocument;
29  import org.kuali.ole.sys.document.validation.GenericValidation;
30  import org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent;
31  import org.kuali.rice.core.api.parameter.ParameterEvaluator;
32  import org.kuali.rice.core.api.parameter.ParameterEvaluatorService;
33  import org.kuali.rice.core.api.util.type.KualiDecimal;
34  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
35  import org.kuali.rice.kns.service.DictionaryValidationService;
36  import org.kuali.rice.krad.util.GlobalVariables;
37  import org.kuali.rice.krad.util.MessageMap;
38  import org.kuali.rice.krad.util.ObjectUtils;
39  
40  public class DisbursementVoucherNonEmployeeTravelValidation extends GenericValidation {
41      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisbursementVoucherNonEmployeeTravelValidation.class);
42  
43      private ParameterService parameterService;
44      private DisbursementVoucherTaxService disbursementVoucherTaxService;
45      private DisbursementVoucherTravelService disbursementVoucherTravelService;
46      private DictionaryValidationService dictionaryValidationService;
47      private AccountingDocument accountingDocumentForValidation;
48      
49      /**
50       * @see org.kuali.ole.sys.document.validation.Validation#validate(org.kuali.ole.sys.document.validation.event.AttributedDocumentEvent)
51       */
52      public boolean validate(AttributedDocumentEvent event) {  
53          LOG.debug("validate start");
54          boolean isValid = true;
55          
56          DisbursementVoucherDocument document = (DisbursementVoucherDocument) accountingDocumentForValidation;
57          DisbursementVoucherNonEmployeeTravel nonEmployeeTravel = document.getDvNonEmployeeTravel();
58          
59          // skip the validation if the payment reason is not noneployee travel or the payee is an employee
60          if (!isTravelNonEmplPaymentReason(document) || document.getDvPayeeDetail().isEmployee()) {
61              return true;
62          }
63          
64          MessageMap errors = GlobalVariables.getMessageMap();
65          errors.addToErrorPath(OLEPropertyConstants.DOCUMENT);
66          errors.addToErrorPath(OLEPropertyConstants.DV_NON_EMPLOYEE_TRAVEL);
67          
68          getDictionaryValidationService().validateBusinessObjectsRecursively(document.getDvNonEmployeeTravel(), 1);
69  
70          /* travel from and to state required if country is us */
71          if (OLEConstants.COUNTRY_CODE_UNITED_STATES.equals(nonEmployeeTravel.getDvTravelFromCountryCode()) && StringUtils.isBlank(nonEmployeeTravel.getDisbVchrTravelFromStateCode())) {
72              errors.putError(OLEPropertyConstants.DISB_VCHR_TRAVEL_FROM_STATE_CODE, OLEKeyConstants.ERROR_DV_TRAVEL_FROM_STATE);
73              isValid = false;
74          }
75          
76          if (OLEConstants.COUNTRY_CODE_UNITED_STATES.equals(nonEmployeeTravel.getDisbVchrTravelToCountryCode()) && StringUtils.isBlank(nonEmployeeTravel.getDisbVchrTravelToStateCode())) {
77              errors.putError(OLEPropertyConstants.DISB_VCHR_TRAVEL_TO_STATE_CODE, OLEKeyConstants.ERROR_DV_TRAVEL_TO_STATE);
78              isValid = false;
79          }
80  
81          if (!isValid) {
82              errors.removeFromErrorPath(OLEPropertyConstants.DV_NON_EMPLOYEE_TRAVEL);
83              errors.removeFromErrorPath(OLEPropertyConstants.DOCUMENT);
84              return false;
85          }
86  
87          /* must fill in all required per diem fields if any field is filled in */
88          boolean perDiemSectionComplete = validatePerDiemSection(document, errors);
89  
90          /* must fill in all required personal vehicle fields if any field is filled in */
91          boolean personalVehicleSectionComplete = validatePersonalVehicleSection(document, errors);
92  
93          /* must have per diem change message if actual amount is different from calculated amount */
94          if (perDiemSectionComplete) { // Only validate if per diem section is filled in
95              if (nonEmployeeTravel.getDisbVchrPerdiemCalculatedAmt().compareTo(nonEmployeeTravel.getDisbVchrPerdiemActualAmount()) != 0 && StringUtils.isBlank(nonEmployeeTravel.getDvPerdiemChangeReasonText())) {
96                  errors.putError(OLEPropertyConstants.DV_PERDIEM_CHANGE_REASON_TEXT, OLEKeyConstants.ERROR_DV_PERDIEM_CHANGE_REQUIRED);
97                  isValid = false;
98              }
99          }
100 
101         /* make sure per diem fields have not changed since the per diem amount calculation */
102         if (perDiemSectionComplete) { // Only validate if per diem section is filled in
103             KualiDecimal calculatedPerDiem = getDisbursementVoucherTravelService().calculatePerDiemAmount(nonEmployeeTravel.getDvPerdiemStartDttmStamp(), nonEmployeeTravel.getDvPerdiemEndDttmStamp(), nonEmployeeTravel.getDisbVchrPerdiemRate());
104             if (calculatedPerDiem.compareTo(nonEmployeeTravel.getDisbVchrPerdiemCalculatedAmt()) != 0) {
105                 errors.putErrorWithoutFullErrorPath(OLEConstants.GENERAL_NONEMPLOYEE_TAB_ERRORS, OLEKeyConstants.ERROR_DV_PER_DIEM_CALC_CHANGE);
106                 isValid = false;
107             }
108         }
109 
110         // validate the tax amount
111         isValid &= validateTravelAmount(document);
112 
113         /* make sure mileage fields have not changed since the mileage amount calculation */
114         if (personalVehicleSectionComplete) {
115             KualiDecimal currentCalcAmt = document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt();
116             KualiDecimal currentActualAmt = document.getDvNonEmployeeTravel().getDisbVchrPersonalCarAmount();
117             if (ObjectUtils.isNotNull(currentCalcAmt) && ObjectUtils.isNotNull(currentActualAmt)) {
118                 KualiDecimal calculatedMileageAmount = getDisbursementVoucherTravelService().calculateMileageAmount(document.getDvNonEmployeeTravel().getDvPersonalCarMileageAmount(), document.getDvNonEmployeeTravel().getDvPerdiemStartDttmStamp());
119                 if (calculatedMileageAmount.compareTo(document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt()) != 0) {
120                     errors.putErrorWithoutFullErrorPath(OLEConstants.GENERAL_NONEMPLOYEE_TAB_ERRORS, OLEKeyConstants.ERROR_DV_MILEAGE_CALC_CHANGE);
121                     isValid = false;
122                 }
123 
124                 // determine if the rule is flagged off in the parm setting
125                 boolean performTravelMileageLimitInd = parameterService.getParameterValueAsBoolean(DisbursementVoucherDocument.class, DisbursementVoucherConstants.NONEMPLOYEE_TRAVEL_ACTUAL_MILEAGE_LIMIT_PARM_NM);
126                 if (performTravelMileageLimitInd) {
127                     // if actual amount is greater than calculated amount
128                     if (currentCalcAmt.subtract(currentActualAmt).isNegative()) {
129                         errors.putError(OLEPropertyConstants.DV_PERSONAL_CAR_AMOUNT, OLEKeyConstants.ERROR_DV_ACTUAL_MILEAGE_TOO_HIGH);
130                         isValid = false;
131                     }
132                 }
133             }
134         }
135 
136         errors.removeFromErrorPath(OLEPropertyConstants.DV_NON_EMPLOYEE_TRAVEL);
137         errors.removeFromErrorPath(OLEPropertyConstants.DOCUMENT);
138 
139         return isValid;
140     }
141 
142     /**
143      * Determines if the given document has an income for tax
144      * @param document document to check
145      * @return true if it does have non-reportable income, false otherwise
146      */
147     protected boolean hasIncomeClassCode(DisbursementVoucherDocument document) {
148         return StringUtils.isNotBlank(document.getDvNonResidentAlienTax().getIncomeClassCode());
149     }
150     
151     /**
152      * Determines if the tax on the document was gross up
153      * @param document the document to check
154      * @return true if the tax was gross up, false otherwise
155      */
156     protected boolean isGrossUp(DisbursementVoucherDocument document) {
157         return document.getDvNonResidentAlienTax().isIncomeTaxGrossUpCode();
158     }
159     
160     /**
161      * Determines if tax should be taken into consideration when checking the total travel amount, and validates that it matches the paid amount
162      * @param document the document to validate the non-employee total travel amount of
163      * @return true if the document validated perfectly, false otherwise
164      */
165     protected boolean validateTravelAmount(DisbursementVoucherDocument document) {
166         /* total on non-employee travel must equal Check Total */
167         KualiDecimal paidAmount = document.getDisbVchrCheckTotalAmount();
168         final boolean incomeClassCoded = hasIncomeClassCode(document);
169         final boolean grossUp = isGrossUp(document);
170         final KualiDecimal travelAmount = document.getDvNonEmployeeTravel().getTotalTravelAmount();
171         if (incomeClassCoded && !grossUp) {  // we're adding tax and not grossing up; we need to add the tax amount to the paid amount
172             paidAmount = paidAmount.add(getDisbursementVoucherTaxService().getNonResidentAlienTaxAmount(document));
173         }
174         if (paidAmount.compareTo(travelAmount) != 0) {
175             GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(OLEConstants.DV_CHECK_TRAVEL_TOTAL_ERROR, OLEKeyConstants.ERROR_DV_TRAVEL_CHECK_TOTAL);
176             return false;
177         }
178         return true;
179     }
180     
181     /**
182      * This method checks to see if the per diem section of the non employee travel tab contains any values. If this section
183      * contains any values, the section is validated to ensure that all the required fields for this section are populated.
184      * 
185      * @param document submitted disbursement voucher document
186      * @param errors map containing any generated errors 
187      * @return true if per diem section is used by user and that all fields contain values.
188      */
189     private boolean validatePerDiemSection(DisbursementVoucherDocument document, MessageMap errors) {
190         boolean perDiemSectionComplete = true;
191 
192         // Checks to see if any per diem fields are filled in
193         boolean perDiemUsed = StringUtils.isNotBlank(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCategoryName()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemRate()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCalculatedAmt()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemActualAmount());
194 
195         // If any per diem fields contain data, validates that all required per diem fields are filled in
196         if (perDiemUsed) {
197             if (StringUtils.isBlank(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCategoryName())) {
198                 errors.putError(OLEPropertyConstants.DISB_VCHR_PERDIEM_CATEGORY_NAME, OLEKeyConstants.ERROR_DV_PER_DIEM_CATEGORY);
199                 perDiemSectionComplete = false;
200             }
201             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemRate())) {
202                 errors.putError(OLEPropertyConstants.DISB_VCHR_PERDIEM_RATE, OLEKeyConstants.ERROR_DV_PER_DIEM_RATE);
203                 perDiemSectionComplete = false;
204             }
205             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemCalculatedAmt())) {
206                 errors.putError(OLEPropertyConstants.DISB_VCHR_PERDIEM_CALCULATED_AMT, OLEKeyConstants.ERROR_DV_PER_DIEM_CALC_AMT);
207                 perDiemSectionComplete = false;
208             }
209             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPerdiemActualAmount())) {
210                 errors.putError(OLEPropertyConstants.DISB_VCHR_PERDIEM_ACTUAL_AMOUNT, OLEKeyConstants.ERROR_DV_PER_DIEM_ACTUAL_AMT);
211                 perDiemSectionComplete = false;
212             }
213         }
214         perDiemSectionComplete = perDiemSectionComplete && perDiemUsed;
215         return perDiemSectionComplete;
216     }
217 
218     /**
219      * This method checks to see if the per diem section of the non employee travel tab contains any values. If this section
220      * contains any values, the section is validated to ensure that all the required fields for this section are populated.
221      * 
222      * @param document submitted disbursement voucher document
223      * @param errors map containing any generated errors 
224      * @return true if per diem section is used by user and that all fields contain values.
225      */
226     private boolean validatePersonalVehicleSection(DisbursementVoucherDocument document, MessageMap errors) {
227         boolean personalVehicleSectionComplete = true;
228 
229         // Checks to see if any per diem fields are filled in
230         boolean personalVehilcleUsed = ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromCityName()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromStateCode()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToCityName()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToStateCode()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDvPersonalCarMileageAmount()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt()) || ObjectUtils.isNotNull(document.getDvNonEmployeeTravel().getDisbVchrPersonalCarAmount());
231 
232 
233         // If any per diem fields contain data, validates that all required per diem fields are filled in
234         if (personalVehilcleUsed) {
235             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromCityName())) {
236                 errors.putError(OLEPropertyConstants.DISB_VCHR_AUTO_FROM_CITY_NAME, OLEKeyConstants.ERROR_DV_AUTO_FROM_CITY);
237                 personalVehicleSectionComplete = false;
238             }
239             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToCityName())) {
240                 errors.putError(OLEPropertyConstants.DISB_VCHR_AUTO_TO_CITY_NAME, OLEKeyConstants.ERROR_DV_AUTO_TO_CITY);
241                 personalVehicleSectionComplete = false;
242             }
243 
244             // are state fields required always or only for US travel?
245             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoFromStateCode())) {
246                 errors.putError(OLEPropertyConstants.DISB_VCHR_AUTO_FROM_STATE_CODE, OLEKeyConstants.ERROR_DV_AUTO_FROM_STATE);
247                 personalVehicleSectionComplete = false;
248             }
249             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrAutoToStateCode())) {
250                 errors.putError(OLEPropertyConstants.DISB_VCHR_AUTO_TO_STATE_CODE, OLEKeyConstants.ERROR_DV_AUTO_TO_STATE);
251                 personalVehicleSectionComplete = false;
252             }
253             // end state field validation
254 
255 
256             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDvPersonalCarMileageAmount())) {
257                 errors.putError(OLEPropertyConstants.DV_PERSONAL_CAR_MILEAGE_AMOUNT, OLEKeyConstants.ERROR_DV_MILEAGE_AMT);
258                 personalVehicleSectionComplete = false;
259             }
260             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrMileageCalculatedAmt())) {
261                 errors.putError(OLEPropertyConstants.DISB_VCHR_MILEAGE_CALCULATED_AMT, OLEKeyConstants.ERROR_DV_MILEAGE_CALC_AMT);
262                 personalVehicleSectionComplete = false;
263             }
264             if (ObjectUtils.isNull(document.getDvNonEmployeeTravel().getDisbVchrPersonalCarAmount())) {
265                 errors.putError(OLEPropertyConstants.DISB_VCHR_PERSONAL_CAR_AMOUNT, OLEKeyConstants.ERROR_DV_MILEAGE_ACTUAL_AMT);
266                 personalVehicleSectionComplete = false;
267             }
268         }
269         personalVehicleSectionComplete = personalVehicleSectionComplete && personalVehilcleUsed;
270         return personalVehicleSectionComplete;
271     }
272 
273     /**
274      * Returns whether the document's payment reason is for travel by a non-employee
275      * 
276      * @param disbursementVoucherDocument submitted disbursement voucher document
277      * @return true if payment reason is travel by a non-employee
278      * 
279      */
280     private boolean isTravelNonEmplPaymentReason(DisbursementVoucherDocument disbursementVoucherDocument) {
281         ParameterEvaluator travelNonEmplPaymentReasonEvaluator = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(DisbursementVoucherDocument.class, DisbursementVoucherConstants.NONEMPLOYEE_TRAVEL_PAY_REASONS_PARM_NM, disbursementVoucherDocument.getDvPayeeDetail().getDisbVchrPaymentReasonCode());
282         return travelNonEmplPaymentReasonEvaluator.evaluationSucceeds();
283     }
284 
285     /**
286      * Sets the accountingDocumentForValidation attribute value.
287      * 
288      * @param accountingDocumentForValidation The accountingDocumentForValidation to set.
289      */
290     public void setAccountingDocumentForValidation(AccountingDocument accountingDocumentForValidation) {
291         this.accountingDocumentForValidation = accountingDocumentForValidation;
292     }
293 
294     /**
295      * Sets the parameterService attribute value.
296      * @param parameterService The parameterService to set.
297      */
298     public void setParameterService(ParameterService parameterService) {
299         this.parameterService = parameterService;
300     }
301 
302     /**
303      * Gets the accountingDocumentForValidation attribute. 
304      * @return Returns the accountingDocumentForValidation.
305      */
306     public AccountingDocument getAccountingDocumentForValidation() {
307         return accountingDocumentForValidation;
308     }
309 
310     /**
311      * Gets the disbursementVoucherTaxService attribute. 
312      * @return Returns the disbursementVoucherTaxService.
313      */
314     public DisbursementVoucherTaxService getDisbursementVoucherTaxService() {
315         return disbursementVoucherTaxService;
316     }
317 
318     /**
319      * Sets the disbursementVoucherTaxService attribute value.
320      * @param disbursementVoucherTaxService The disbursementVoucherTaxService to set.
321      */
322     public void setDisbursementVoucherTaxService(DisbursementVoucherTaxService disbursementVoucherTaxService) {
323         this.disbursementVoucherTaxService = disbursementVoucherTaxService;
324     }
325 
326     /**
327      * Gets the disbursementVoucherTravelService attribute. 
328      * @return Returns the disbursementVoucherTravelService.
329      */
330     public DisbursementVoucherTravelService getDisbursementVoucherTravelService() {
331         return disbursementVoucherTravelService;
332     }
333 
334     /**
335      * Sets the disbursementVoucherTravelService attribute value.
336      * @param disbursementVoucherTravelService The disbursementVoucherTravelService to set.
337      */
338     public void setDisbursementVoucherTravelService(DisbursementVoucherTravelService disbursementVoucherTravelService) {
339         this.disbursementVoucherTravelService = disbursementVoucherTravelService;
340     }
341 
342     /**
343      * Gets the dictionaryValidationService attribute. 
344      * @return Returns the dictionaryValidationService.
345      */
346     public DictionaryValidationService getDictionaryValidationService() {
347         return dictionaryValidationService;
348     }
349 
350     /**
351      * Sets the dictionaryValidationService attribute value.
352      * @param dictionaryValidationService The dictionaryValidationService to set.
353      */
354     public void setDictionaryValidationService(DictionaryValidationService dictionaryValidationService) {
355         this.dictionaryValidationService = dictionaryValidationService;
356     }
357     
358 }