1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.kuali.kfs.module.tem.document.service.impl;
20
21 import static org.kuali.kfs.module.tem.TemKeyConstants.ERROR_UPLOADPARSER_INVALID_NUMERIC_VALUE;
22 import static org.kuali.kfs.module.tem.TemKeyConstants.ERROR_UPLOADPARSER_LINE;
23 import static org.kuali.kfs.module.tem.TemKeyConstants.ERROR_UPLOADPARSER_PROPERTY;
24 import static org.kuali.kfs.module.tem.TemKeyConstants.ERROR_UPLOADPARSER_WRONG_PROPERTY_NUMBER;
25 import static org.kuali.kfs.module.tem.TemKeyConstants.MESSAGE_TR_LODGING_ALREADY_CLAIMED;
26 import static org.kuali.kfs.module.tem.TemKeyConstants.MESSAGE_TR_MEAL_ALREADY_CLAIMED;
27 import static org.kuali.kfs.module.tem.TemKeyConstants.MESSAGE_UPLOADPARSER_EXCEEDED_MAX_LENGTH;
28 import static org.kuali.kfs.module.tem.TemKeyConstants.MESSAGE_UPLOADPARSER_INVALID_VALUE;
29 import static org.kuali.kfs.module.tem.TemPropertyConstants.PER_DIEM_EXPENSE_DISABLED;
30
31 import java.io.BufferedReader;
32 import java.io.IOException;
33 import java.io.StringReader;
34 import java.lang.reflect.InvocationTargetException;
35 import java.math.BigDecimal;
36 import java.sql.Date;
37 import java.sql.Timestamp;
38 import java.text.MessageFormat;
39 import java.text.SimpleDateFormat;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Calendar;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.Comparator;
46 import java.util.GregorianCalendar;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.Iterator;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Set;
54 import java.util.TreeSet;
55
56 import org.apache.commons.lang.StringUtils;
57 import org.apache.commons.lang.builder.EqualsBuilder;
58 import org.apache.commons.lang.builder.HashCodeBuilder;
59 import org.apache.commons.lang.time.DateUtils;
60 import org.apache.log4j.Logger;
61 import org.kuali.kfs.integration.ar.AccountsReceivableCustomerInvoice;
62 import org.kuali.kfs.integration.ar.AccountsReceivableModuleService;
63 import org.kuali.kfs.integration.ar.AccountsReceivableOrganizationOptions;
64 import org.kuali.kfs.module.tem.TemConstants;
65 import org.kuali.kfs.module.tem.TemConstants.TravelAuthorizationParameters;
66 import org.kuali.kfs.module.tem.TemConstants.TravelAuthorizationStatusCodeKeys;
67 import org.kuali.kfs.module.tem.TemConstants.TravelDocTypes;
68 import org.kuali.kfs.module.tem.TemConstants.TravelParameters;
69 import org.kuali.kfs.module.tem.TemKeyConstants;
70 import org.kuali.kfs.module.tem.TemParameterConstants;
71 import org.kuali.kfs.module.tem.TemPropertyConstants;
72 import org.kuali.kfs.module.tem.TemWorkflowConstants;
73 import org.kuali.kfs.module.tem.businessobject.ActualExpense;
74 import org.kuali.kfs.module.tem.businessobject.ExpenseType;
75 import org.kuali.kfs.module.tem.businessobject.ExpenseTypeAware;
76 import org.kuali.kfs.module.tem.businessobject.GroupTraveler;
77 import org.kuali.kfs.module.tem.businessobject.GroupTravelerCsvRecord;
78 import org.kuali.kfs.module.tem.businessobject.HistoricalTravelExpense;
79 import org.kuali.kfs.module.tem.businessobject.ImportedExpense;
80 import org.kuali.kfs.module.tem.businessobject.MileageRate;
81 import org.kuali.kfs.module.tem.businessobject.PerDiem;
82 import org.kuali.kfs.module.tem.businessobject.PerDiemExpense;
83 import org.kuali.kfs.module.tem.businessobject.PrimaryDestination;
84 import org.kuali.kfs.module.tem.businessobject.SpecialCircumstances;
85 import org.kuali.kfs.module.tem.businessobject.SpecialCircumstancesQuestion;
86 import org.kuali.kfs.module.tem.businessobject.TemExpense;
87 import org.kuali.kfs.module.tem.businessobject.TemRegion;
88 import org.kuali.kfs.module.tem.businessobject.TemSourceAccountingLine;
89 import org.kuali.kfs.module.tem.businessobject.TransportationModeDetail;
90 import org.kuali.kfs.module.tem.businessobject.TravelAdvance;
91 import org.kuali.kfs.module.tem.businessobject.TripType;
92 import org.kuali.kfs.module.tem.dataaccess.TravelDocumentDao;
93 import org.kuali.kfs.module.tem.document.TEMReimbursementDocument;
94 import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument;
95 import org.kuali.kfs.module.tem.document.TravelDocument;
96 import org.kuali.kfs.module.tem.document.TravelEntertainmentDocument;
97 import org.kuali.kfs.module.tem.document.TravelReimbursementDocument;
98 import org.kuali.kfs.module.tem.document.TravelRelocationDocument;
99 import org.kuali.kfs.module.tem.document.service.AccountingDocumentRelationshipService;
100 import org.kuali.kfs.module.tem.document.service.MileageRateService;
101 import org.kuali.kfs.module.tem.document.service.TravelAuthorizationService;
102 import org.kuali.kfs.module.tem.document.service.TravelDocumentService;
103 import org.kuali.kfs.module.tem.document.web.struts.TravelFormBase;
104 import org.kuali.kfs.module.tem.exception.UploadParserException;
105 import org.kuali.kfs.module.tem.service.CsvRecordFactory;
106 import org.kuali.kfs.module.tem.service.PerDiemService;
107 import org.kuali.kfs.module.tem.service.TemRoleService;
108 import org.kuali.kfs.module.tem.service.TravelExpenseService;
109 import org.kuali.kfs.module.tem.service.TravelService;
110 import org.kuali.kfs.module.tem.util.ExpenseUtils;
111 import org.kuali.kfs.sys.KFSConstants;
112 import org.kuali.kfs.sys.KFSKeyConstants;
113 import org.kuali.kfs.sys.KFSPropertyConstants;
114 import org.kuali.kfs.sys.businessobject.AccountingLine;
115 import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
116 import org.kuali.kfs.sys.businessobject.PaymentDocumentationLocation;
117 import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
118 import org.kuali.kfs.sys.context.SpringContext;
119 import org.kuali.kfs.sys.exception.ParseException;
120 import org.kuali.kfs.sys.service.UniversityDateService;
121 import org.kuali.kfs.sys.util.KfsDateUtils;
122 import org.kuali.rice.core.api.config.property.ConfigurationService;
123 import org.kuali.rice.core.api.datetime.DateTimeService;
124 import org.kuali.rice.core.api.util.ConcreteKeyValue;
125 import org.kuali.rice.core.api.util.KeyValue;
126 import org.kuali.rice.core.api.util.type.KualiDecimal;
127 import org.kuali.rice.core.web.format.FormatException;
128 import org.kuali.rice.coreservice.framework.parameter.ParameterService;
129 import org.kuali.rice.kew.api.KewApiConstants;
130 import org.kuali.rice.kew.api.KewApiServiceLocator;
131 import org.kuali.rice.kew.api.WorkflowDocument;
132 import org.kuali.rice.kew.api.action.ActionRequestType;
133 import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue;
134 import org.kuali.rice.kew.api.exception.WorkflowException;
135 import org.kuali.rice.kim.api.identity.Person;
136 import org.kuali.rice.kim.api.identity.PersonService;
137 import org.kuali.rice.kim.api.identity.principal.Principal;
138 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
139 import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
140 import org.kuali.rice.kns.service.DocumentHelperService;
141 import org.kuali.rice.kns.util.KNSGlobalVariables;
142 import org.kuali.rice.krad.bo.AdHocRoutePerson;
143 import org.kuali.rice.krad.bo.Note;
144 import org.kuali.rice.krad.bo.PersistableBusinessObject;
145 import org.kuali.rice.krad.document.Document;
146 import org.kuali.rice.krad.exception.InfrastructureException;
147 import org.kuali.rice.krad.service.BusinessObjectService;
148 import org.kuali.rice.krad.service.DataDictionaryService;
149 import org.kuali.rice.krad.service.DocumentService;
150 import org.kuali.rice.krad.service.NoteService;
151 import org.kuali.rice.krad.service.SequenceAccessorService;
152 import org.kuali.rice.krad.uif.field.LinkField;
153 import org.kuali.rice.krad.util.GlobalVariables;
154 import org.kuali.rice.krad.util.KRADPropertyConstants;
155 import org.kuali.rice.krad.util.ObjectUtils;
156 import org.kuali.rice.location.api.state.State;
157 import org.kuali.rice.location.api.state.StateService;
158 import org.springframework.beans.BeanUtils;
159 import org.springframework.transaction.annotation.Transactional;
160
161 import au.com.bytecode.opencsv.CSVReader;
162
163
164
165
166
167 @Transactional
168 public class TravelDocumentServiceImpl implements TravelDocumentService {
169
170 protected static Logger LOG = Logger.getLogger(TravelDocumentServiceImpl.class);
171
172 protected DataDictionaryService dataDictionaryService;
173 protected DocumentService documentService;
174 protected BusinessObjectService businessObjectService;
175 protected TravelDocumentDao travelDocumentDao;
176 protected TravelAuthorizationService travelAuthorizationService;
177 protected DateTimeService dateTimeService;
178 protected ParameterService parameterService;
179 protected AccountingDocumentRelationshipService accountingDocumentRelationshipService;
180 protected TemRoleService temRoleService;
181 protected StateService stateService;
182 protected ConfigurationService configurationService;
183 protected UniversityDateService universityDateService;
184 protected List<String> defaultAcceptableFileExtensions;
185 protected CsvRecordFactory<GroupTravelerCsvRecord> csvRecordFactory;
186 protected List<String> groupTravelerColumns;
187 protected volatile AccountsReceivableModuleService accountsReceivableModuleService;
188 protected PerDiemService perDiemService;
189 protected TravelExpenseService travelExpenseService;
190 protected NoteService noteService;
191 protected TravelService travelService;
192 protected MileageRateService mileageRateService;
193
194
195
196
197
198
199
200
201 protected PerDiemExpense createPerDiemItem(final TravelDocument document, final PerDiem newPerDiem, final Timestamp ts, final boolean prorated, String mileageRateExpenseTypeCode) {
202 final PerDiemExpense expense = newPerDiemExpense();
203 expense.setPrimaryDestinationId(newPerDiem.getPrimaryDestinationId());
204 expense.setProrated(prorated);
205 expense.setMileageDate(ts);
206
207 expense.setPrimaryDestination(newPerDiem.getPrimaryDestination().getPrimaryDestinationName());
208 expense.setCountryState(newPerDiem.getPrimaryDestination().getRegion().getRegionName());
209 expense.setCounty(newPerDiem.getPrimaryDestination().getCounty());
210
211 setPerDiemMealsAndIncidentals(expense, newPerDiem, document.getTripType(), document.getTripEnd(), expense.isProrated());
212 final KualiDecimal lodgingAmount = getPerDiemService().isPerDiemHandlingLodging() && !KfsDateUtils.isSameDay(document.getTripEnd(), ts) ? newPerDiem.getLodging() : KualiDecimal.ZERO;
213 expense.setLodging(lodgingAmount);
214 expense.setMileageRateExpenseTypeCode(mileageRateExpenseTypeCode);
215 return expense;
216 }
217
218
219
220
221 protected PerDiemExpense newPerDiemExpense() {
222 return new PerDiemExpense();
223 }
224
225
226
227
228
229
230
231
232
233 @Override
234 public void setPerDiemMealsAndIncidentals(PerDiemExpense expense, PerDiem perDiem, TripType tripType, Timestamp tripEnd, boolean shouldProrate) {
235 String perDiemCalcMethod = null;
236 if (!ObjectUtils.isNull(tripType)) {
237 perDiemCalcMethod = tripType.getPerDiemCalcMethod();
238 }
239
240 expense.setBreakfastValue(perDiem.getBreakfast());
241 expense.setLunchValue(perDiem.getLunch());
242 expense.setDinnerValue(perDiem.getDinner());
243 expense.setIncidentalsValue(perDiem.getIncidentals());
244
245 if(shouldProrate){
246 Integer perDiemPercent = calculateProratePercentage(expense, perDiemCalcMethod, tripEnd);
247 expense.setDinnerValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(expense.getDinnerValue(), perDiemPercent));
248 expense.setLunchValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(expense.getLunchValue(), perDiemPercent));
249 expense.setBreakfastValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(expense.getBreakfastValue(), perDiemPercent));
250 expense.setIncidentalsValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(expense.getIncidentalsValue(), perDiemPercent));
251
252 correctProratedPerDiemExpense(expense, perDiemPercent, perDiem);
253 }
254 }
255
256
257
258
259
260
261
262
263 protected void correctProratedPerDiemExpense(PerDiemExpense expense, Integer perDiemPercent, PerDiem perDiem) {
264 final KualiDecimal mealAndIncidentalLimit = PerDiemExpense.calculateMealsAndIncidentalsProrated(perDiem.getMealsAndIncidentals(), perDiemPercent);
265 if (expense.getMealsAndIncidentals().isGreaterThan(mealAndIncidentalLimit)) {
266
267 final KualiDecimal delta = expense.getMealsAndIncidentals().subtract(mealAndIncidentalLimit);
268 expense.setBreakfastValue(expense.getBreakfastValue().subtract(delta));
269 }
270 }
271
272
273
274
275
276
277
278
279 protected Collection<Timestamp> dateRange(final Timestamp start, final Timestamp end) {
280 final Collection<Timestamp> retval = new ArrayList<Timestamp>();
281
282 if (start != null && end != null) {
283 final Calendar cal = getDateTimeService().getCurrentCalendar();
284 cal.setTime(start);
285
286 for (; !cal.getTime().after(end) || KfsDateUtils.isSameDay(cal.getTime(), end); cal.add(Calendar.DATE, 1)) {
287 if (KfsDateUtils.isSameDay(cal.getTime(), end)) {
288 retval.add(new Timestamp(end.getTime()));
289 }
290 else {
291 retval.add(new Timestamp(cal.getTime().getTime()));
292 }
293 }
294 }
295
296 return retval;
297 }
298
299
300
301
302 @SuppressWarnings("null")
303 @Override
304 public void updatePerDiemItemsFor(final TravelDocument document, final List<PerDiemExpense> perDiemExpenseList, final Integer perDiemId, final Timestamp start, final Timestamp end) {
305 final String mileageRateExpenseTypeCode = getParameterService().getParameterValueAsString(TemParameterConstants.TEM_DOCUMENT.class, TemConstants.TravelParameters.PER_DIEM_MILEAGE_RATE_EXPENSE_TYPE_CODE, KFSConstants.EMPTY_STRING);
306
307
308
309 boolean datesChanged = false;
310 if (perDiemExpenseList != null && !perDiemExpenseList.isEmpty()) {
311 Timestamp tempStart = perDiemExpenseList.get(0).getMileageDate();
312 Timestamp tempEnd = perDiemExpenseList.get(0).getMileageDate();
313
314 if (perDiemExpenseList.size() > 1) {
315 tempEnd = perDiemExpenseList.get(perDiemExpenseList.size()-1).getMileageDate();
316 }
317
318 if (!(tempStart.equals(start) && tempEnd.equals(end))) {
319
320 datesChanged = true;
321 }
322 }
323
324 List<PerDiem> perDiemList = new ArrayList<PerDiem>();
325
326
327 for (final Timestamp eachTimestamp : dateRange(start, end)) {
328 PerDiem perDiem = getPerDiemService().getPerDiem(document.getPrimaryDestinationId(), eachTimestamp, document.getEffectiveDateForPerDiem(eachTimestamp));
329 if (perDiem == null || perDiem.getPrimaryDestinationId() == TemConstants.CUSTOM_PRIMARY_DESTINATION_ID){
330 perDiem = new PerDiem();
331 perDiem.setPrimaryDestination(new PrimaryDestination());
332 perDiem.getPrimaryDestination().setRegion(new TemRegion());
333 perDiem.getPrimaryDestination().getRegion().setTripType(new TripType());
334 perDiem.setPrimaryDestinationId(TemConstants.CUSTOM_PRIMARY_DESTINATION_ID);
335 perDiem.getPrimaryDestination().getRegion().setRegionName(document.getPrimaryDestinationCountryState());
336 perDiem.getPrimaryDestination().setCounty(document.getPrimaryDestinationCounty());
337 perDiem.getPrimaryDestination().getRegion().setTripType(document.getTripType());
338 perDiem.getPrimaryDestination().getRegion().setTripTypeCode(document.getTripTypeCode());
339 perDiem.getPrimaryDestination().setPrimaryDestinationName(document.getPrimaryDestinationName());
340 }
341 perDiemList.add(perDiem);
342 }
343
344 final Map<Timestamp, PerDiemExpense> perDiemMapped = new HashMap<Timestamp, PerDiemExpense>();
345
346 int diffStartDays = 0;
347 if (perDiemExpenseList.size() > 0 && perDiemExpenseList.get(0).getMileageDate() != null && !datesChanged) {
348 diffStartDays = dateTimeService.dateDiff(start, perDiemExpenseList.get(0).getMileageDate(), false);
349 }
350
351 Calendar endCal = Calendar.getInstance();
352
353 if (end != null) {
354 endCal.setTime(end);
355 if (!datesChanged) {
356 for (final PerDiemExpense perDiemItem : perDiemExpenseList) {
357 if (diffStartDays != 0) {
358 Calendar cal = Calendar.getInstance();
359 cal.setTime(perDiemItem.getMileageDate());
360 cal.add(Calendar.DATE, -diffStartDays);
361 perDiemItem.setMileageDate(new Timestamp(cal.getTimeInMillis()));
362 }
363
364 if (perDiemItem.getMileageDate() != null) {
365 Calendar currCal = Calendar.getInstance();
366 currCal.setTime(perDiemItem.getMileageDate());
367 if (!endCal.before(currCal)) {
368 perDiemMapped.put(perDiemItem.getMileageDate(), perDiemItem);
369 }
370 }
371 }
372 }
373
374 LOG.debug("Iterating over date range from "+ start+ " to "+ end);
375 int counter = 0;
376 for (final Timestamp someDate : dateRange(start, end)) {
377
378 if (!perDiemMapped.containsKey(someDate)) {
379 final boolean prorated = shouldProrate(someDate, start, end);
380 PerDiemExpense perDiemExpense = createPerDiemItem(document,perDiemList.get(counter), someDate, prorated, mileageRateExpenseTypeCode);
381 perDiemExpense.setDocumentNumber(document.getDocumentNumber());
382 perDiemMapped.put(someDate, perDiemExpense);
383 }
384 counter++;
385 }
386 }
387
388
389 perDiemExpenseList.clear();
390 for (final Timestamp someDate : new TreeSet<Timestamp>(perDiemMapped.keySet())) {
391 LOG.debug("Adding "+ perDiemMapped.get(someDate)+ " to perdiem list");
392 perDiemExpenseList.add(perDiemMapped.get(someDate));
393 }
394 }
395
396
397
398
399
400
401
402
403 protected boolean shouldProrate(Timestamp perDiemDate, Timestamp tripBegin, Timestamp tripEnd) {
404 final boolean prorated = !KfsDateUtils.isSameDay(tripBegin, tripEnd) && (KfsDateUtils.isSameDay(perDiemDate, tripBegin) || KfsDateUtils.isSameDay(perDiemDate, tripEnd));
405 return prorated;
406 }
407
408
409
410
411 @Override
412 public List<KeyValue> getMileageRateKeyValues(Date searchDate) {
413 List<KeyValue> keyValues = new ArrayList<KeyValue>();
414
415 TravelDocument document = (TravelDocument) ((TravelFormBase)KNSGlobalVariables.getKualiForm()).getDocument();
416 String documentType = getDocumentType(document);
417 final String travelerType = ObjectUtils.isNull(document.getTraveler()) ? null : document.getTraveler().getTravelerTypeCode();
418
419 final Collection<ExpenseType> expenseTypes = getTravelExpenseService().getExpenseTypesForDocument(documentType, document.getTripTypeCode(), travelerType, false);
420
421 for (final ExpenseType expenseType : expenseTypes) {
422 if (TemConstants.ExpenseTypeMetaCategory.MILEAGE.getCode().equals(expenseType.getExpenseTypeMetaCategoryCode())) {
423 final MileageRate mileageRate = getMileageRateService().findMileageRateByExpenseTypeCodeAndDate(expenseType.getCode(), searchDate);
424 if (mileageRate != null) {
425 keyValues.add(new ConcreteKeyValue(expenseType.getCode(), expenseType.getCode()+" - "+mileageRate.getRate().toString()));
426 }
427 }
428 }
429
430
431 Comparator<KeyValue> labelComparator = new Comparator<KeyValue>() {
432 @Override
433 public int compare(KeyValue o1, KeyValue o2) {
434 return o1.getKey().compareTo(o2.getKey());
435 }
436 };
437
438 Collections.sort(keyValues, labelComparator);
439
440 return keyValues;
441 }
442
443
444
445
446 @Override
447 public void copyDownPerDiemExpense(TravelDocument travelDocument, int copyIndex, List<PerDiemExpense> perDiemExpenses) {
448
449 PerDiemExpense lineToCopy = perDiemExpenses.get(copyIndex);
450 PerDiemExpense restoredLine = getRestoredPerDiemForCopying(travelDocument, lineToCopy);
451
452 List<PerDiemExpense> tempPerDiemExpenses = new ArrayList<PerDiemExpense>();
453
454 if (copyIndex < perDiemExpenses.size()) {
455 for (int i = 0; i < perDiemExpenses.size(); i++) {
456 PerDiemExpense perDiemExpense = new PerDiemExpense();
457 if (perDiemExpenses != null && i < copyIndex) {
458
459 perDiemExpense = perDiemExpenses.get(i);
460 }
461 else if (i > copyIndex) {
462 perDiemExpense = copyPerDiemExpense(restoredLine);
463 perDiemExpense.setMileageDate(perDiemExpenses.get(i).getMileageDate());
464 if (shouldProrate(perDiemExpense.getMileageDate(), travelDocument.getTripBegin(), travelDocument.getTripEnd())) {
465
466 perDiemExpense.setProrated(true);
467 if (perDiemExpense.getPrimaryDestinationId() == TemConstants.CUSTOM_PRIMARY_DESTINATION_ID) {
468
469 final PerDiem perDiem = copyIntoPerDiem(restoredLine);
470 final Integer perDiemPercent = lookupProratePercentage(perDiemExpense, travelDocument.getTripType().getPerDiemCalcMethod(), travelDocument.getTripEnd());
471 perDiemExpense.setDinnerValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(perDiemExpense.getDinnerValue(), perDiemPercent));
472 perDiemExpense.setLunchValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(perDiemExpense.getLunchValue(), perDiemPercent));
473 perDiemExpense.setBreakfastValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(perDiemExpense.getBreakfastValue(), perDiemPercent));
474 perDiemExpense.setIncidentalsValue(PerDiemExpense.calculateMealsAndIncidentalsProrated(perDiemExpense.getIncidentalsValue(), perDiemPercent));
475 } else {
476 final PerDiem perDiem = getPerDiemService().getPerDiem(restoredLine.getPrimaryDestinationId(), perDiemExpense.getMileageDate(), travelDocument.getEffectiveDateForPerDiem(perDiemExpense));
477 setPerDiemMealsAndIncidentals(perDiemExpense, perDiem, travelDocument.getTripType(), travelDocument.getTripEnd(), true);
478 }
479 }
480 if (travelDocument.getTripEnd() != null && KfsDateUtils.isSameDay(travelDocument.getTripEnd(), perDiemExpense.getMileageDate())) {
481
482 perDiemExpense.setLodging(KualiDecimal.ZERO);
483 }
484 }
485 else {
486
487
488
489 perDiemExpense = lineToCopy;
490 }
491
492 tempPerDiemExpenses.add(perDiemExpense);
493
494 }
495 }
496
497 perDiemExpenses.clear();
498 perDiemExpenses.addAll(tempPerDiemExpenses);
499 }
500
501
502
503
504
505
506
507 protected PerDiemExpense getRestoredPerDiemForCopying(TravelDocument travelDocument, PerDiemExpense perDiemExpense) {
508 PerDiemExpense restoredExpense = copyPerDiemExpense(perDiemExpense);
509 if (travelDocument.getPrimaryDestinationId() == TemConstants.CUSTOM_PRIMARY_DESTINATION_ID && shouldProrate(perDiemExpense.getMileageDate(), travelDocument.getTripBegin(), travelDocument.getTripEnd())) {
510 final Integer perDiemPercentage = lookupProratePercentage(perDiemExpense, travelDocument.getTripType().getPerDiemCalcMethod(), travelDocument.getTripEnd());
511 if (perDiemPercentage != null) {
512 final KualiDecimal perDiemPercentageDecimal = new KualiDecimal((double)perDiemPercentage*0.01);
513 restoredExpense.setBreakfastValue(perDiemExpense.getBreakfastValue().divide(perDiemPercentageDecimal));
514 restoredExpense.setLunchValue(perDiemExpense.getLunchValue().divide(perDiemPercentageDecimal));
515 restoredExpense.setDinnerValue(perDiemExpense.getDinnerValue().divide(perDiemPercentageDecimal));
516 restoredExpense.setIncidentalsValue(perDiemExpense.getIncidentalsValue().divide(perDiemPercentageDecimal));
517 }
518 perDiemExpense.setProrated(false);
519 } else {
520
521 final PerDiem perDiem = getPerDiemService().getPerDiem(perDiemExpense.getPrimaryDestinationId(), perDiemExpense.getMileageDate(), travelDocument.getEffectiveDateForPerDiem(perDiemExpense));
522 setPerDiemMealsAndIncidentals(restoredExpense, perDiem, travelDocument.getTripType(), travelDocument.getTripEnd(), false);
523 }
524 return restoredExpense;
525 }
526
527
528
529
530
531
532 protected PerDiem copyIntoPerDiem(PerDiemExpense perDiemExpense) {
533 PerDiem perDiem = new PerDiem();
534 perDiem.setPrimaryDestinationId(perDiemExpense.getPrimaryDestinationId());
535 perDiem.setBreakfast(perDiemExpense.getBreakfastValue());
536 perDiem.setLunch(perDiemExpense.getLunchValue());
537 perDiem.setDinner(perDiemExpense.getDinnerValue());
538 perDiem.setIncidentals(perDiemExpense.getIncidentalsValue());
539 return perDiem;
540 }
541
542
543
544
545 @Override
546 public Map<String, List<Document>> getDocumentsRelatedTo(final TravelDocument document) throws WorkflowException {
547 return getDocumentsRelatedTo(document.getDocumentNumber());
548 }
549
550
551
552
553 @Override
554 public Map<String, List<Document>> getDocumentsRelatedTo(final String documentNumber) throws WorkflowException {
555 final Map<String, List<Document>> retval = new HashMap<String, List<Document>>();
556
557 Set<String> documentNumbers = accountingDocumentRelationshipService.getAllRelatedDocumentNumbers(documentNumber);
558 if (!documentNumbers.isEmpty()) {
559 for (String documentHeaderId : documentNumbers) {
560 Document doc = documentService.getByDocumentHeaderIdSessionless(documentHeaderId);
561 if (doc != null) {
562 Class<? extends Document> clazz = doc.getClass();
563
564 if (clazz != null) {
565 String docTypeName = getDataDictionaryService().getDocumentTypeNameByClass(clazz);
566
567 List<Document> docs = retval.get(docTypeName);
568 if (docs == null) {
569 docs = new ArrayList<Document>();
570 }
571 docs.add(doc);
572
573 retval.put(docTypeName, docs);
574 }
575 }
576 }
577 }
578
579 return retval;
580 }
581
582
583
584
585 @Override
586 public List<Document> getDocumentsRelatedTo(final TravelDocument document, String... documentTypeList){
587 List<Document> relatedDocumentList = new ArrayList<Document>();
588 Map<String, List<Document>> relatedDocumentMap;
589 try {
590 relatedDocumentMap = getDocumentsRelatedTo(document);
591 for (String documentType : documentTypeList){
592 if (relatedDocumentMap.containsKey(documentType)){
593 relatedDocumentList.addAll(relatedDocumentMap.get(documentType));
594 }
595 }
596 }
597 catch (WorkflowException ex) {
598 LOG.error(ex.getMessage(), ex);
599 throw new RuntimeException(ex);
600 }
601 return relatedDocumentList;
602 }
603
604 @Override
605 public List<SpecialCircumstances> findActiveSpecialCircumstances(String documentNumber, String documentType) {
606 List<SpecialCircumstances> retval = new ArrayList<SpecialCircumstances>();
607 Map<String, Object> criteria = new HashMap<String, Object>();
608 criteria.put(KFSPropertyConstants.ACTIVE, true);
609
610
611 Set<String> documentTypesToCheck = new HashSet<String>();
612 documentTypesToCheck.add(documentType);
613 final Set<String> parentDocTypes = getTravelService().getParentDocumentTypeNames(documentType);
614 documentTypesToCheck.addAll(parentDocTypes);
615 criteria.put(KFSPropertyConstants.DOCUMENT_TYPE, documentTypesToCheck);
616 retval.addAll(buildSpecialCircumstances(documentNumber, criteria));
617
618 return retval;
619 }
620
621
622 protected List<SpecialCircumstances> buildSpecialCircumstances(String documentNumber, Map<String, Object> criteria) {
623 List<SpecialCircumstances> retval = new ArrayList<SpecialCircumstances>();
624
625 Collection<SpecialCircumstancesQuestion> questions = getBusinessObjectService().findMatching(SpecialCircumstancesQuestion.class, criteria);
626 for (SpecialCircumstancesQuestion question : questions) {
627 SpecialCircumstances spc = new SpecialCircumstances();
628 spc.setDocumentNumber(documentNumber);
629 spc.setQuestionId(question.getId());
630 spc.setQuestion(question);
631 retval.add(spc);
632 }
633
634 return retval;
635 }
636
637
638
639
640 @Override
641 public List<TravelAuthorizationDocument> findAuthorizationDocuments(final String travelDocumentIdentifier){
642 final List<String> ids = findAuthorizationDocumentNumbers(travelDocumentIdentifier);
643
644 List<TravelAuthorizationDocument> resultDocumentLists = new ArrayList<TravelAuthorizationDocument>();
645
646 try {
647 if (!ids.isEmpty()) {
648 for (Document document : getDocumentService().getDocumentsByListOfDocumentHeaderIds(TravelAuthorizationDocument.class, ids)){
649 resultDocumentLists.add((TravelAuthorizationDocument)document);
650 }
651 }
652 }catch (WorkflowException wfe){
653 LOG.error(wfe.getMessage(), wfe);
654 }
655 return resultDocumentLists;
656 }
657
658
659
660
661
662 @Override
663 public List<String> findAuthorizationDocumentNumbers(final String travelDocumentIdentifier) {
664 final List<String> ids = getTravelDocumentDao().findDocumentNumbers(TravelAuthorizationDocument.class, travelDocumentIdentifier);
665 return ids;
666 }
667
668
669
670
671 @Override
672 public List<TravelReimbursementDocument> findReimbursementDocuments(final String travelDocumentIdentifier) {
673 final List<String> ids = getTravelDocumentDao().findDocumentNumbers(TravelReimbursementDocument.class, travelDocumentIdentifier);
674
675 List<TravelReimbursementDocument> resultDocumentLists = new ArrayList<TravelReimbursementDocument>();
676
677 try {
678 if (!ids.isEmpty()) {
679 for (Document document : getDocumentService().getDocumentsByListOfDocumentHeaderIds(TravelReimbursementDocument.class, ids)) {
680 resultDocumentLists.add((TravelReimbursementDocument) document);
681 }
682 }
683 }
684 catch (WorkflowException wfe) {
685 throw new RuntimeException(wfe);
686 }
687 return resultDocumentLists;
688 }
689
690
691
692
693
694
695 @Override
696 public void addAdHocFYIRecipient(final Document document) {
697 addAdHocFYIRecipient(document, document.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId());
698 }
699
700
701
702
703
704 @Override
705 public void addAdHocFYIRecipient(final Document document, String initiatorUserId) {
706 addAdHocRecipient(document, initiatorUserId, KewApiConstants.ACTION_REQUEST_FYI_REQ);
707 }
708
709
710
711
712 @Override
713 public void addAdHocRecipient(Document document, String initiatorUserId, String actionRequested) {
714 List<AdHocRoutePerson> adHocRoutePersons = document.getAdHocRoutePersons();
715 List<String> adHocRoutePersonIds = new ArrayList<String>();
716 if(!adHocRoutePersons.isEmpty()){
717 for(AdHocRoutePerson ahrp : adHocRoutePersons){
718 adHocRoutePersonIds.add(ahrp.getId());
719 }
720 }
721
722
723 if (!adHocRoutePersonIds.contains(initiatorUserId)) {
724 if (initiatorUserId != null) {
725 final Person finSysUser = SpringContext.getBean(PersonService.class).getPerson(initiatorUserId);
726 if (finSysUser != null) {
727 final AdHocRoutePerson recipient = buildAdHocRecipient(finSysUser.getPrincipalName(), actionRequested);
728 final DocumentAuthorizer documentAuthorizer = SpringContext.getBean(DocumentHelperService.class).getDocumentAuthorizer(document);
729 if (documentAuthorizer.canReceiveAdHoc(document, finSysUser, actionRequested)) {
730 adHocRoutePersons.add(recipient);
731 }
732 }
733 else {
734 LOG.warn("finSysUser is null.");
735 }
736 }
737 else {
738 LOG.warn("initiatorUserId is null.");
739 }
740 }
741 }
742
743
744
745
746
747
748
749 protected AdHocRoutePerson buildAdHocRecipient(String userId, String actionRequested) {
750 AdHocRoutePerson adHocRoutePerson = new AdHocRoutePerson();
751 adHocRoutePerson.setActionRequested(actionRequested);
752 adHocRoutePerson.setId(userId);
753 return adHocRoutePerson;
754 }
755
756
757
758
759 @Override
760 public List<Map<String, KualiDecimal>> calculateDailyTotals(List<PerDiemExpense> perDiemExpenses) {
761 List<Map<String, KualiDecimal>> tripTotals = new ArrayList<Map<String, KualiDecimal>>();
762
763 for (PerDiemExpense perDiemExpense : perDiemExpenses){
764 Map<String, KualiDecimal> dailyTotal = calculateDailyTotal(perDiemExpense);
765 tripTotals.add(dailyTotal);
766 }
767
768 return tripTotals;
769 }
770
771
772
773
774
775 @Override
776 public Map<String, KualiDecimal> calculateDailyTotal(PerDiemExpense perDiemExpense) {
777 Map<String, KualiDecimal> dailyTotals = new HashMap<String, KualiDecimal>();
778
779 dailyTotals.put(TemConstants.MILEAGE_TOTAL_ATTRIBUTE, perDiemExpense.getMileageTotal());
780 dailyTotals.put(TemConstants.LODGING_TOTAL_ATTRIBUTE, perDiemExpense.getLodgingTotal());
781 dailyTotals.put(TemConstants.MEALS_AND_INC_TOTAL_ATTRIBUTE, perDiemExpense.getMealsAndIncidentals());
782 dailyTotals.put(TemConstants.DAILY_TOTAL, perDiemExpense.getDailyTotal());
783
784 return dailyTotals;
785 }
786
787 @Override
788 public void routeToFiscalOfficer(final TravelDocument document, final String noteText) throws WorkflowException, Exception {
789
790 final Note newNote = getDocumentService().createNoteFromDocument(document, noteText);
791 document.addNote(newNote);
792 getNoteService().save(newNote);
793
794 final WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument();
795 workflowDocument.returnToPreviousNode(noteText, KFSConstants.RouteLevelNames.ACCOUNT);
796
797 final String messagePattern = configurationService.getPropertyValueAsString(TemKeyConstants.MESSAGE_DOCUMENT_TEM_RETURNED_TO_FISCAL_OFFICER);
798 final String annotation = MessageFormat.format(messagePattern, GlobalVariables.getUserSession().getPerson().getPrincipalName());
799
800 workflowDocument.adHocToPrincipal( ActionRequestType.FYI, KFSConstants.RouteLevelNames.ACCOUNT, annotation, workflowDocument.getInitiatorPrincipalId(), TemConstants.INITIATOR_RESPONSIBILITY, true);
801
802 document.refreshReferenceObject(KFSPropertyConstants.DOCUMENT_HEADER);
803
804 document.getFinancialSystemDocumentHeader().updateAndSaveAppDocStatus(TemConstants.TravelStatusCodeKeys.AWAIT_FISCAL);
805 }
806
807
808
809
810
811
812
813
814 @Override
815 public Integer calculateProratePercentage(PerDiemExpense perDiemExpense, String perDiemCalcMethod, Timestamp tripEnd) {
816 Integer perDiemPercent = 100;
817
818 if (perDiemExpense.isProrated()) {
819 perDiemPercent = lookupProratePercentage(perDiemExpense, perDiemCalcMethod, tripEnd);
820 }
821 return perDiemPercent;
822 }
823
824
825
826
827
828
829
830
831 protected Integer lookupProratePercentage(PerDiemExpense perDiemExpense, String perDiemCalcMethod, Timestamp tripEnd) {
832 if (perDiemCalcMethod != null && perDiemCalcMethod.equals(TemConstants.PERCENTAGE)) {
833 try {
834 final String perDiemPercentage = parameterService.getParameterValueAsString(TravelAuthorizationDocument.class, TravelAuthorizationParameters.FIRST_AND_LAST_DAY_PER_DIEM_PERCENTAGE, "100");
835 final Integer perDiemPercent = Integer.parseInt(perDiemPercentage);
836 return perDiemPercent;
837 }
838 catch (Exception e1) {
839 LOG.error("Failed to process prorate percentage for FIRST_AND_LAST_DAY_PER_DIEM_PERCENTAGE parameter.", e1);
840 }
841 }
842 else {
843 return calculatePerDiemPercentageFromTimestamp(perDiemExpense, tripEnd);
844 }
845 return 100;
846 }
847
848 @Override
849 public Integer calculatePerDiemPercentageFromTimestamp(PerDiemExpense perDiemExpense, Timestamp tripEnd) {
850 if (perDiemExpense.getMileageDate() != null) {
851 try {
852 Collection<String> quarterTimes = parameterService.getParameterValuesAsString(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.QUARTER_DAY_TIME_TABLE);
853
854
855 Calendar prorateDate = new GregorianCalendar();
856 prorateDate.setTime(perDiemExpense.getMileageDate());
857
858 int quadrantIndex = 4;
859 for (String qT : quarterTimes) {
860 String[] indexTime = qT.split("=");
861 String[] hourMinute = indexTime[1].split(":");
862
863 Calendar qtCal = new GregorianCalendar();
864 qtCal.setTime(perDiemExpense.getMileageDate());
865 qtCal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hourMinute[0]));
866 qtCal.set(Calendar.MINUTE, Integer.parseInt(hourMinute[1]));
867
868 if (prorateDate.equals(qtCal) || prorateDate.before(qtCal)) {
869 quadrantIndex = Integer.parseInt(indexTime[0]);
870 break;
871 }
872 }
873
874
875 if (tripEnd != null && !KfsDateUtils.isSameDay(prorateDate.getTime(), tripEnd)) {
876 return 100 - ((quadrantIndex - 1) * TemConstants.QUADRANT_PERCENT_VALUE);
877 }
878 else {
879 return quadrantIndex * TemConstants.QUADRANT_PERCENT_VALUE;
880 }
881 }
882 catch (IllegalArgumentException e2) {
883 LOG.error("IllegalArgumentException.", e2);
884 }
885 }
886
887 return 100;
888 }
889
890
891
892
893 @Override
894 public PerDiemExpense copyPerDiemExpense(PerDiemExpense perDiemExpense) {
895 final PerDiemExpense retval = new PerDiemExpense();
896 retval.setDocumentNumber(perDiemExpense.getDocumentNumber());
897
898 retval.setCountryState(perDiemExpense.getCountryState());
899 retval.setCounty(perDiemExpense.getCounty());
900 retval.setPrimaryDestination(perDiemExpense.getPrimaryDestination());
901 retval.setMileageDate(perDiemExpense.getMileageDate());
902 retval.setMiles(perDiemExpense.getMiles());
903 retval.setMileageRateExpenseTypeCode(perDiemExpense.getMileageRateExpenseTypeCode());
904 retval.setAccommodationTypeCode(perDiemExpense.getAccommodationTypeCode());
905 retval.setAccommodationName(perDiemExpense.getAccommodationName());
906 retval.setAccommodationPhoneNum(perDiemExpense.getAccommodationPhoneNum());
907 retval.setAccommodationAddress(perDiemExpense.getAccommodationAddress());
908 retval.setPrimaryDestinationId(perDiemExpense.getPrimaryDestinationId());
909
910 if (retval.getMiles() == null) {
911 retval.setMiles(0);
912 }
913
914 if (perDiemExpense.getLodging() == null || perDiemExpense.getLodging().isNegative()) {
915 retval.setLodging(KualiDecimal.ZERO);
916 }
917 else {
918 retval.setLodging(perDiemExpense.getLodging());
919 }
920
921 retval.setPersonal(perDiemExpense.getPersonal());
922 retval.setBreakfast(perDiemExpense.getBreakfast());
923 retval.setLunch(perDiemExpense.getLunch());
924 retval.setDinner(perDiemExpense.getDinner());
925
926 retval.setBreakfastValue(perDiemExpense.getBreakfastValue());
927 retval.setLunchValue(perDiemExpense.getLunchValue());
928 retval.setDinnerValue(perDiemExpense.getDinnerValue());
929 retval.setIncidentalsValue(perDiemExpense.getIncidentalsValue());
930
931 LOG.debug("estimated meals and incidentals "+ retval.getMealsAndIncidentals());
932
933 return retval;
934 }
935
936 @Override
937
938
939
940
941 public KualiDecimal calculateMileage(ActualExpense actualExpense) {
942 KualiDecimal mileageTotal = KualiDecimal.ZERO;
943 if (ObjectUtils.isNotNull(actualExpense.getExpenseTypeCode()) && actualExpense.isMileage()) {
944 mileageTotal = actualExpense.getMileageTotal();
945 }
946 return mileageTotal;
947 }
948
949
950 protected ActualExpense getParentActualExpense(final List<ActualExpense> actualExpenses, Long expenseId) {
951 if (ObjectUtils.isNotNull(actualExpenses) && ObjectUtils.isNotNull(expenseId)) {
952
953 for (final ActualExpense actualExpense : actualExpenses) {
954
955 if (actualExpense.getId().equals(expenseId)) {
956 return actualExpense;
957 }
958
959 }
960 }
961
962 return null;
963 }
964
965
966
967
968 @Override
969 public void handleNewActualExpense(final ActualExpense newActualExpenseLine) {
970 if (newActualExpenseLine.getExpenseAmount() != null) {
971 final BigDecimal rate = newActualExpenseLine.getCurrencyRate();
972 final KualiDecimal amount = newActualExpenseLine.getExpenseAmount();
973
974 newActualExpenseLine.setConvertedAmount(new KualiDecimal(amount.bigDecimalValue().multiply(rate)));
975 LOG.debug("Set converted amount for "+ newActualExpenseLine+ " to "+ newActualExpenseLine.getConvertedAmount());
976
977 if (isHostedMeal(newActualExpenseLine)) {
978 KNSGlobalVariables.getMessageList().add(TemKeyConstants.MESSAGE_HOSTED_MEAL_ADDED,
979 new SimpleDateFormat("MM/dd/yyyy").format(newActualExpenseLine.getExpenseDate()),
980 newActualExpenseLine.getExpenseTypeObjectCode().getExpenseType().getName());
981 newActualExpenseLine.setNonReimbursable(true);
982 }
983 }
984 }
985
986
987
988
989
990
991
992
993
994 @Override
995 public boolean isHostedMeal(final ExpenseTypeAware havingExpenseType) {
996 if (ObjectUtils.isNull(havingExpenseType) || StringUtils.isBlank(havingExpenseType.getExpenseTypeCode())) {
997 return false;
998 }
999
1000 if (havingExpenseType instanceof PersistableBusinessObject) {
1001 ((PersistableBusinessObject)havingExpenseType).refreshReferenceObject(TemPropertyConstants.EXPENSE_TYPE);
1002 }
1003 if (ObjectUtils.isNull(havingExpenseType.getExpenseType())) {
1004 return false;
1005 }
1006 return havingExpenseType.getExpenseType().isHosted();
1007 }
1008
1009
1010
1011
1012 @Override
1013 public boolean isTravelManager(final Person user) {
1014 return getTemRoleService().isTravelManager(user);
1015 }
1016
1017
1018
1019
1020 @Override
1021 public String getMessageFrom(final String messageType, String... args) {
1022 String strTemp = getConfigurationService().getPropertyValueAsString(messageType);
1023 for(int i=0;i<args.length;i++){
1024 strTemp = strTemp.replaceAll("\\{"+i+"\\}", args[i]);
1025 }
1026 return strTemp;
1027 }
1028
1029
1030
1031
1032
1033
1034
1035 @Override
1036 public boolean isOpen(TravelDocument document) {
1037 return document.getAppDocStatus().equals(TemConstants.TravelAuthorizationStatusCodeKeys.OPEN_REIMB);
1038 }
1039
1040
1041
1042
1043
1044
1045
1046 @Override
1047 public boolean isFinal(TravelDocument document) {
1048 return document.getDocumentHeader().getWorkflowDocument().isFinal();
1049 }
1050
1051
1052
1053
1054
1055
1056 @Override
1057 public boolean isTravelAuthorizationProcessed(TravelAuthorizationDocument document){
1058 return isFinal(document) || isProcessed(document);
1059 }
1060
1061
1062
1063
1064
1065
1066 @Override
1067 public boolean isTravelAuthorizationOpened(TravelAuthorizationDocument document){
1068 return isTravelAuthorizationProcessed(document) && isOpen(document);
1069 }
1070
1071
1072
1073
1074
1075
1076
1077 @Override
1078 public boolean isProcessed(TravelDocument document) {
1079 return document.getDocumentHeader().getWorkflowDocument().isProcessed();
1080 }
1081
1082 @Override
1083 public KualiDecimal getAmountDueFromInvoice(String documentNumber, KualiDecimal requestedAmount) {
1084 try {
1085 AccountsReceivableCustomerInvoice doc = (AccountsReceivableCustomerInvoice) documentService.getByDocumentHeaderId(documentNumber);
1086 if (doc != null) {
1087 return doc.getOpenAmount();
1088 }
1089 }
1090 catch (WorkflowException we) {
1091 throw new RuntimeException(we);
1092 }
1093
1094 return requestedAmount;
1095 }
1096
1097
1098
1099
1100
1101
1102
1103
1104 @Override
1105 public TravelAuthorizationDocument findCurrentTravelAuthorization(TravelDocument document) {
1106
1107 TravelAuthorizationDocument travelDocument = null;
1108
1109 try {
1110 final Map<String, List<Document>> relatedDocuments = getDocumentsRelatedTo(document);
1111 List<Document> taDocs = relatedDocuments.get(TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT);
1112 List<Document> taaDocs = relatedDocuments.get(TravelDocTypes.TRAVEL_AUTHORIZATION_AMEND_DOCUMENT);
1113 List<Document> tacDocs = relatedDocuments.get(TravelDocTypes.TRAVEL_AUTHORIZATION_CLOSE_DOCUMENT);
1114
1115
1116 if (tacDocs != null && !tacDocs.isEmpty()) {
1117 travelDocument = (TravelAuthorizationDocument) tacDocs.get(0);
1118 }
1119
1120 else if (taaDocs != null && !taaDocs.isEmpty()){
1121 for (Document tempDocument : taaDocs){
1122
1123 if (isTravelAuthorizationOpened((TravelAuthorizationDocument)tempDocument)){
1124 travelDocument = (TravelAuthorizationDocument) tempDocument;
1125 }
1126 }
1127 }
1128
1129 if (travelDocument == null) {
1130
1131 taDocs = taDocs == null? new ArrayList<Document>() : taDocs;
1132
1133 if (taDocs.isEmpty()) {
1134
1135 final List<TravelAuthorizationDocument> tempTaDocs = findAuthorizationDocuments(document.getTravelDocumentIdentifier());
1136 if (!tempTaDocs.isEmpty()){
1137 travelDocument = tempTaDocs.get(0);
1138 }
1139 }else{
1140 travelDocument = (TravelAuthorizationDocument) taDocs.get(0);
1141 }
1142 }
1143 }
1144 catch (WorkflowException we) {
1145 final String docNum = (document != null && !StringUtils.isBlank(document.getDocumentNumber())) ? document.getDocumentNumber() : "???";
1146 throw new RuntimeException("Could not find documents related to document #"+docNum);
1147 }
1148 return travelDocument;
1149 }
1150
1151
1152
1153
1154
1155
1156
1157
1158 @Override
1159 public TravelDocument findRootForTravelReimbursement(String travelDocumentIdentifier) {
1160
1161 TravelDocument rootTravelDocument = null;
1162
1163 try {
1164
1165
1166
1167 final Collection<TravelAuthorizationDocument> tempTaDocs = getTravelAuthorizationService().find(travelDocumentIdentifier);
1168
1169 if (!tempTaDocs.isEmpty()) {
1170 TravelAuthorizationDocument taDoc = null;
1171 for(TravelAuthorizationDocument tempTaDoc : tempTaDocs) {
1172 taDoc = tempTaDoc;
1173 break;
1174 }
1175
1176
1177 rootTravelDocument = findCurrentTravelAuthorization(taDoc);
1178 }
1179
1180
1181 else {
1182 final List<TravelReimbursementDocument> tempTrDocs = findReimbursementDocuments(travelDocumentIdentifier);
1183
1184 if (tempTrDocs.isEmpty()) {
1185 LOG.debug("Did not find any authorizations or reimbursements for travelDocumentIndentifier: "+ travelDocumentIdentifier);
1186 return null;
1187 }
1188
1189
1190 if (tempTrDocs.size() == 1) {
1191 rootTravelDocument = tempTrDocs.get(0);
1192 }
1193 else {
1194
1195 String rootDocumentNumber = getAccountingDocumentRelationshipService().getRootDocumentNumber(tempTrDocs.get(0).getDocumentNumber());
1196 TravelDocument tempDoc = (TravelDocument)documentService.getByDocumentHeaderIdSessionless(rootDocumentNumber);
1197
1198 rootTravelDocument = tempDoc;
1199 }
1200 }
1201 }
1202 catch (WorkflowException we) {
1203 throw new RuntimeException("Could not find authorization or reimbursement documents related to trip id #"+travelDocumentIdentifier);
1204 }
1205
1206 return rootTravelDocument;
1207 }
1208
1209 @Override
1210 public KualiDecimal getTotalCumulativeReimbursements(TravelDocument document) {
1211 KualiDecimal trTotal = KualiDecimal.ZERO;
1212
1213 List<Document> relatedTravelReimbursementDocuments = getDocumentsRelatedTo(document, TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
1214 for(Document trDoc: relatedTravelReimbursementDocuments) {
1215 final TravelReimbursementDocument tr = (TravelReimbursementDocument)trDoc;
1216 if (!KFSConstants.DocumentStatusCodes.CANCELLED.equals(tr.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode()) && !KFSConstants.DocumentStatusCodes.DISAPPROVED.equals(tr.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode())) {
1217 List<AccountingLine> lines = tr.getSourceAccountingLines();
1218 for(AccountingLine line: lines) {
1219 trTotal = trTotal.add(line.getAmount());
1220 }
1221 }
1222 }
1223
1224 if (document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName().equals(TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT)){
1225 List<AccountingLine> lines = document.getSourceAccountingLines();
1226 for(AccountingLine line: lines) {
1227 trTotal = trTotal.add(line.getAmount());
1228 }
1229 }
1230
1231 return trTotal;
1232 }
1233
1234
1235
1236
1237 @Override
1238 public KualiDecimal getTotalAuthorizedEncumbrance(TravelDocument document) {
1239 KualiDecimal taTotal = KualiDecimal.ZERO;
1240 TravelAuthorizationDocument taDoc = null;
1241 taDoc = findCurrentTravelAuthorization(document);
1242 if(taDoc != null) {
1243 List<AccountingLine> lines = taDoc.getSourceAccountingLines();
1244 for(AccountingLine line: lines) {
1245 taTotal = taTotal.add(line.getAmount());
1246 }
1247 }
1248 return taTotal;
1249
1250 }
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260 @Override
1261 public boolean isResponsibleForAccountsOn(final TravelDocument document, String principalId) {
1262 final List<String> accounts = findAccountsResponsibleFor(document.getSourceAccountingLines(), principalId);
1263 return (accounts != null && accounts.size() > 0);
1264 }
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274 protected List<String> findAccountsResponsibleFor(final List<SourceAccountingLine> lines, String principalId) {
1275 final Set<String> accountList = new HashSet<String>();
1276 for (AccountingLine line : lines) {
1277 line.refreshReferenceObject(KFSPropertyConstants.ACCOUNT);
1278 if (line != null && !ObjectUtils.isNull(line.getAccount())) {
1279 Person accountFiscalOfficerUser = line.getAccount().getAccountFiscalOfficerUser();
1280 if (accountFiscalOfficerUser != null && accountFiscalOfficerUser.getPrincipalId().equals(principalId)) {
1281 accountList.add(line.getAccountNumber());
1282 }
1283 }
1284 }
1285 return new ArrayList<String>(accountList);
1286 }
1287
1288
1289
1290
1291
1292
1293 @Override
1294 public boolean checkNonEmployeeTravelerTypeCode(String travelerTypeCode) {
1295 boolean foundCode = false;
1296 if (getParameterService().getParameterValuesAsString(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.NON_EMPLOYEE_TRAVELER_TYPE_CODES).contains(travelerTypeCode)) {
1297 foundCode = true;
1298 }
1299 return foundCode;
1300 }
1301
1302
1303
1304
1305
1306
1307 @Override
1308 public String getAllStates(final String countryCode) {
1309
1310 final List<State> codes = getStateService().findAllStatesInCountry(countryCode);
1311
1312 final StringBuffer sb = new StringBuffer();
1313 sb.append(";");
1314 for (final State state : codes) {
1315 if (state.isActive()) {
1316 sb.append(state.getCode()).append(";");
1317 }
1318 }
1319
1320 return sb.toString();
1321 }
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335 @Override
1336 public <T> List<T> importFile(final String fileContents, final Class<T> c, final String[] attributeNames, final Map<String,List<String>> defaultValues,
1337 final Integer[] attributeMaxLength, final String tabErrorKey) {
1338 if(attributeMaxLength != null && attributeNames.length != attributeMaxLength.length){
1339 throw new UploadParserException("Invalid parser configuration, the number of attribute names and attribute max length should be the same");
1340 }
1341
1342 return importFile(fileContents, c, attributeNames, defaultValues, attributeMaxLength, tabErrorKey, getDefaultAcceptableFileExtensions());
1343 }
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355 public <T> List<T> importFile(final String fileContents, Class<T> c, String[] attributeNames,Map<String, List<String>> defaultValues, Integer[] attributeMaxLength, String tabErrorKey, List<String> fileExtensions) {
1356 final List<T> importedObjects = new ArrayList<T>();
1357
1358
1359 Integer lineNo = 0;
1360 boolean failed = false;
1361 for (final String line : fileContents.split("\n")) {
1362 lineNo++;
1363 try {
1364 final T o = parseLine(line, c, attributeNames, defaultValues, attributeMaxLength, lineNo, tabErrorKey);
1365 importedObjects.add(o);
1366 }
1367 catch (UploadParserException e) {
1368
1369
1370 failed = true;
1371 }
1372 }
1373
1374 if (failed) {
1375 throw new UploadParserException("errors in parsing lines in file ", ERROR_UPLOADPARSER_LINE);
1376 }
1377
1378 return importedObjects;
1379 }
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391 protected <T> T parseLine(String line, Class<T> c, String[] attributeNames,Map<String, List<String>> defaultValues, Integer[] attributeMaxLength, Integer lineNo, String tabErrorKey) {
1392 final Map<String, String> objectMap = retrieveObjectAttributes(line, attributeNames, defaultValues, attributeMaxLength, lineNo, tabErrorKey);
1393 final T obj = genObjectWithRetrievedAttributes(objectMap, c, lineNo, tabErrorKey);
1394 ((PersistableBusinessObject) obj).refresh();
1395 return obj;
1396 }
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407 protected <T> T genObjectWithRetrievedAttributes(final Map<String, String> objectMap,
1408 final Class<T> c, final Integer lineNo, final String tabErrorKey) {
1409 T object;
1410 try {
1411 object = c.newInstance();
1412 }
1413 catch (Exception e) {
1414 throw new InfrastructureException("unable to complete line population.", e);
1415 }
1416
1417 boolean failed = false;
1418 for (final Map.Entry<String, String> entry : objectMap.entrySet()) {
1419 try {
1420 try {
1421 ObjectUtils.setObjectProperty(object, entry.getKey(), entry.getValue());
1422 }
1423 catch (FormatException e) {
1424 String[] errorParams = { entry.getValue(), entry.getKey(), "" + lineNo };
1425 throw new UploadParserException("invalid numeric property value: "
1426 + entry.getKey() + " = " + entry.getValue() + " (line " + lineNo + ")", ERROR_UPLOADPARSER_INVALID_NUMERIC_VALUE, errorParams);
1427 }
1428 }
1429 catch (UploadParserException e) {
1430
1431 GlobalVariables.getMessageMap().putError(tabErrorKey, e.getErrorKey(), e.getErrorParameters());
1432 failed = true;
1433 }
1434 catch (NoSuchMethodException nsme) {
1435 throw new RuntimeException("Could not set property while parsing group travelers csv", nsme);
1436 }
1437 catch (InvocationTargetException ite) {
1438 throw new RuntimeException("Could not set property while parsing group travelers csv", ite);
1439 }
1440 catch (IllegalAccessException iae) {
1441 throw new RuntimeException("Could not set property while parsing group travelers csv", iae);
1442 }
1443 }
1444
1445 if (failed) {
1446 throw new UploadParserException("empty or invalid properties in line " + lineNo + ")", ERROR_UPLOADPARSER_PROPERTY, ""+lineNo);
1447 }
1448 return object;
1449 }
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460 protected Map<String, String> retrieveObjectAttributes(String line,
1461 String[] attributeNames,
1462 Map<String, List<String>> defaultValues,
1463 Integer[] attributeMaxLength,
1464 Integer lineNo, String tabErrorKey) {
1465 String[] attributeValues = StringUtils.splitPreserveAllTokens(line, ',');
1466 if (attributeNames.length != attributeValues.length) {
1467 String[] errorParams = { "" + attributeNames.length, "" + attributeValues.length, "" + lineNo };
1468 GlobalVariables.getMessageMap().putError(tabErrorKey, ERROR_UPLOADPARSER_WRONG_PROPERTY_NUMBER, errorParams);
1469 throw new UploadParserException("wrong number of properties: " + attributeValues.length + " exist, " + attributeNames.length + " expected (line " + lineNo + ")", ERROR_UPLOADPARSER_WRONG_PROPERTY_NUMBER, errorParams);
1470 }
1471
1472 for (int i = 0; i < attributeNames.length; i++) {
1473 if (defaultValues != null && defaultValues.get(attributeNames[i]) != null) {
1474 List<String> defaultValue = defaultValues.get(attributeNames[i]);
1475 boolean found = false;
1476 for (String value : defaultValue) {
1477 if (attributeValues[i].equalsIgnoreCase(value)) {
1478 found = true;
1479 }
1480 }
1481 if (!found) {
1482 GlobalVariables.getMessageMap().putWarning(tabErrorKey, MESSAGE_UPLOADPARSER_INVALID_VALUE, attributeNames[i], attributeValues[i], (" " + lineNo));
1483 throw new UploadParserException("Invalid value " + attributeValues[i] + " exist, " + "in line (" + lineNo + ")", ERROR_UPLOADPARSER_WRONG_PROPERTY_NUMBER);
1484 }
1485 }
1486
1487 if (attributeMaxLength != null) {
1488 if (attributeValues[i] != null && attributeValues[i].length() > attributeMaxLength[i]) {
1489 attributeValues[i] = attributeValues[i].substring(0, attributeMaxLength[i]);
1490 String[] errorParams = { "" + attributeNames[i], "" + attributeMaxLength[i], "" + lineNo };
1491 GlobalVariables.getMessageMap().putWarning(tabErrorKey, MESSAGE_UPLOADPARSER_EXCEEDED_MAX_LENGTH, errorParams);
1492 }
1493 }
1494 }
1495
1496 Map<String, String> objectMap = new HashMap<String, String>();
1497 for (int i = 0; i < attributeNames.length; i++) {
1498 objectMap.put(attributeNames[i], attributeValues[i]);
1499 }
1500
1501 return objectMap;
1502 }
1503
1504
1505
1506
1507
1508
1509
1510
1511 protected Map<String, List<Integer>> parseHeader(final String[] csvHeader) {
1512 final Map<String, List<Integer>> retval = new HashMap<String, List<Integer>>();
1513
1514 for (Integer i = 0; i < csvHeader.length; i++) {
1515
1516 if (StringUtils.isBlank(csvHeader[i].trim())) {
1517 final String formattedName = nextHeader(csvHeader, i);
1518 final Integer start = i;
1519 final Integer end = csvHeader.length > i ? nextBlankHeader(csvHeader, i) : i;
1520
1521 final List<Integer> indexes = new ArrayList<Integer>();
1522
1523 for (Integer y = start; y < end; y++) {
1524 indexes.add(y);
1525 }
1526 retval.put(formattedName, indexes);
1527 }
1528 else {
1529 final String formattedName = toCamelCase(csvHeader[i]);
1530
1531 if (StringUtils.isNotBlank(formattedName)) {
1532 retval.put(formattedName, Arrays.asList(new Integer[] { i }));
1533 }
1534 }
1535 }
1536 return retval;
1537 }
1538
1539 protected String nextHeader(final String[] headers, final int start) {
1540 for (int i = start + 1; i < headers.length; i++) {
1541 if (StringUtils.isNotBlank(headers[i])) {
1542 return toCamelCase(headers[i]);
1543 }
1544 }
1545 return "";
1546 }
1547
1548
1549 protected Integer nextBlankHeader(final String[] headers, final int start) {
1550 for (int i = start + 1; i < headers.length; i++) {
1551 if (StringUtils.isBlank(headers[i])) {
1552 return i;
1553 }
1554 }
1555 return -1;
1556 }
1557
1558 protected String toProperCase(final String s) {
1559 if (s == null || s.length() < 1) {
1560 return "";
1561 }
1562
1563 final char[] arr = s.toLowerCase().toCharArray();
1564 arr[0] = Character.toUpperCase(arr[0]);
1565
1566 return new String(arr);
1567 }
1568
1569 protected String toCamelCase(final String s) {
1570 final StringBuffer buffer = new StringBuffer();
1571
1572 final List<String> words = new LinkedList<String>(Arrays.asList(s.toLowerCase().trim().replace('_', ' ').split(" ")));
1573 buffer.append(words.remove(0));
1574
1575 for (final String word : words) {
1576 buffer.append(toProperCase(word));
1577 }
1578 return buffer.toString();
1579 }
1580
1581
1582
1583 @Override
1584 public List<GroupTraveler> importGroupTravelers(final TravelDocument document, final String csvData) throws Exception {
1585 final List<GroupTraveler> retval = new LinkedList<GroupTraveler>();
1586 final BufferedReader bufferedFileReader = new BufferedReader(new StringReader(csvData));
1587 final CSVReader csvReader = new CSVReader(bufferedFileReader);
1588
1589 final List<String[]> rows;
1590 try {
1591 rows = csvReader.readAll();
1592 }
1593 catch (IOException ex) {
1594 ex.printStackTrace();
1595 throw new ParseException("Could not parse CSV file data", ex);
1596 }
1597 finally {
1598 try {
1599 csvReader.close();
1600 }
1601 catch (Exception e) {}
1602 }
1603
1604 final Map<String,List<Integer>> header = getGroupTravelerHeaders();
1605
1606 for (final String[] row : rows) {
1607 final GroupTravelerCsvRecord record = createGroupTravelerCsvRecord(header, row);
1608 final GroupTraveler traveler = new GroupTraveler();
1609 traveler.setGroupTravelerEmpId(record.getGroupTravelerEmpId());
1610 traveler.setName(record.getName());
1611 traveler.setGroupTravelerType(record.getGroupTravelerType());
1612 retval.add(traveler);
1613 }
1614
1615 return retval;
1616 }
1617
1618 protected GroupTravelerCsvRecord createGroupTravelerCsvRecord(final Map<String, List<Integer>> header, final String[] record) throws Exception {
1619 return getCsvRecordFactory().newInstance(header, record);
1620 }
1621
1622 @Override
1623 public boolean isUnsuccessful(TravelDocument document) {
1624 String status = document.getDocumentHeader().getWorkflowDocument().getStatus().getCode();
1625 List<String> unsuccessful = KewApiConstants.DOCUMENT_STATUS_PARENT_TYPES.get(KewApiConstants.DOCUMENT_STATUS_PARENT_TYPE_UNSUCCESSFUL);
1626 for (String tempStatus : unsuccessful){
1627 if (status.equals(tempStatus)){
1628 return true;
1629 }
1630 }
1631
1632 return false;
1633 }
1634
1635
1636
1637
1638
1639 protected Map<String,List<Integer>> getGroupTravelerHeaders() {
1640 Map<String, List<Integer>> headers = new HashMap<String, List<Integer>>();
1641 if (getGroupTravelerColumns() != null && !getGroupTravelerColumns().isEmpty()) {
1642 int count = 0;
1643 while (count < getGroupTravelerColumns().size()) {
1644 List<Integer> countArray = new ArrayList<Integer>(2);
1645 countArray.add(new Integer(count));
1646 final String columnName = getGroupTravelerColumns().get(count);
1647 headers.put(columnName, countArray);
1648 count += 1;
1649 }
1650 }
1651 return headers;
1652 }
1653
1654
1655
1656
1657
1658 @Override
1659 public List<GroupTraveler> copyGroupTravelers(List<GroupTraveler> groupTravelers, String documentNumber) {
1660 List<GroupTraveler> newGroupTravelers = new ArrayList<GroupTraveler>();
1661 if (groupTravelers != null) {
1662 for (GroupTraveler groupTraveler : groupTravelers) {
1663 GroupTraveler newGroupTraveler = new GroupTraveler();
1664 BeanUtils.copyProperties(groupTraveler, newGroupTraveler);
1665 newGroupTraveler.setDocumentNumber(documentNumber);
1666 newGroupTraveler.setVersionNumber(new Long(1));
1667 newGroupTraveler.setObjectId(null);
1668 newGroupTraveler.setId(null);
1669 newGroupTravelers.add(newGroupTraveler);
1670 }
1671 }
1672 return newGroupTravelers;
1673 }
1674
1675
1676
1677
1678
1679 @Override
1680 public List<? extends TemExpense> copyActualExpenses(List<? extends TemExpense> actualExpenses, String documentNumber) {
1681 List<ActualExpense> newActualExpenses = new ArrayList<ActualExpense>();
1682
1683 if (actualExpenses != null) {
1684 for (TemExpense expense : actualExpenses) {
1685 ActualExpense actualExpense = (ActualExpense)expense;
1686 ActualExpense newActualExpense = new ActualExpense();
1687 boolean nullCheck = false;
1688 if (actualExpense.getExpenseDate() == null) {
1689 nullCheck = true;
1690 actualExpense.setExpenseDate(new Date(0));
1691 }
1692 BeanUtils.copyProperties(actualExpense, newActualExpense);
1693 if (nullCheck) {
1694 actualExpense.setExpenseDate(null);
1695 newActualExpense.setExpenseDate(null);
1696 }
1697
1698 List<TemExpense> newDetails = (List<TemExpense>) this.copyActualExpenses(actualExpense.getExpenseDetails(), documentNumber);
1699 newActualExpense.setExpenseDetails(newDetails);
1700 newActualExpense.setDocumentNumber(documentNumber);
1701 newActualExpense.setVersionNumber(new Long(1));
1702 newActualExpense.setId(null);
1703 newActualExpense.setObjectId(null);
1704 newActualExpenses.add(newActualExpense);
1705 }
1706 }
1707 return newActualExpenses;
1708 }
1709
1710 private Long getNextActualExpenseId(){
1711 return SpringContext.getBean(SequenceAccessorService.class).getNextAvailableSequenceNumber(TemConstants.TEM_ACTUAL_EXPENSE_SEQ_NAME);
1712 }
1713
1714
1715
1716
1717
1718 @Override
1719 public List<PerDiemExpense> copyPerDiemExpenses(List<PerDiemExpense> perDiemExpenses, String documentNumber) {
1720 List<PerDiemExpense> newPerDiemExpenses = new ArrayList<PerDiemExpense>();
1721 if (perDiemExpenses != null) {
1722 for (PerDiemExpense expense : perDiemExpenses){
1723 PerDiemExpense newExpense = new PerDiemExpense();
1724 BeanUtils.copyProperties(expense, newExpense);
1725 newExpense.setBreakfastValue(expense.getBreakfastValue());
1726 newExpense.setLunchValue(expense.getLunchValue());
1727 newExpense.setDinnerValue(expense.getDinnerValue());
1728 newExpense.setIncidentalsValue(expense.getIncidentalsValue());
1729 newExpense.setDocumentNumber(documentNumber);
1730 newExpense.setVersionNumber(new Long(1));
1731 newExpense.setObjectId(null);
1732 newExpense.setId(null);
1733 newPerDiemExpenses.add(newExpense);
1734 }
1735 }
1736 return newPerDiemExpenses;
1737 }
1738
1739
1740
1741
1742
1743 @Override
1744 public List<TravelAdvance> copyTravelAdvances(List<TravelAdvance> travelAdvances, String documentNumber) {
1745 List<TravelAdvance> newTravelAdvances = new ArrayList<TravelAdvance>();
1746 if (travelAdvances != null) {
1747 for (TravelAdvance travelAdvance : travelAdvances){
1748 TravelAdvance newTravelAdvance = (TravelAdvance) ObjectUtils.deepCopy(travelAdvance);
1749 newTravelAdvance.setDocumentNumber(documentNumber);
1750 newTravelAdvance.setVersionNumber(new Long(1));
1751 newTravelAdvance.setObjectId(null);
1752 newTravelAdvance.setTravelDocumentIdentifier(travelAdvance.getTravelDocumentIdentifier());
1753 newTravelAdvances.add(newTravelAdvance);
1754 }
1755 }
1756 return newTravelAdvances;
1757 }
1758
1759
1760
1761
1762
1763 @Override
1764 public List<SpecialCircumstances> copySpecialCircumstances(List<SpecialCircumstances> specialCircumstancesList, String documentNumber) {
1765 List<SpecialCircumstances> newSpecialCircumstancesList = new ArrayList<SpecialCircumstances>();
1766 if (specialCircumstancesList != null) {
1767 for (SpecialCircumstances specialCircumstances : specialCircumstancesList){
1768 SpecialCircumstances newSpecialCircumstances = new SpecialCircumstances();
1769 BeanUtils.copyProperties(specialCircumstances, newSpecialCircumstances);
1770 newSpecialCircumstances.setDocumentNumber(documentNumber);
1771 newSpecialCircumstances.setVersionNumber(new Long(1));
1772 newSpecialCircumstances.setObjectId(null);
1773 newSpecialCircumstances.setId(null);
1774 newSpecialCircumstancesList.add(newSpecialCircumstances);
1775 }
1776 }
1777 return newSpecialCircumstancesList;
1778 }
1779
1780
1781
1782
1783 @Override
1784 public List<TransportationModeDetail> copyTransportationModeDetails(List<TransportationModeDetail> transportationModeDetails, String documentNumber) {
1785 List<TransportationModeDetail> newTransportationModeDetails = new ArrayList<TransportationModeDetail>();
1786 if (transportationModeDetails != null) {
1787 for (TransportationModeDetail detail : transportationModeDetails){
1788 TransportationModeDetail newDetail = new TransportationModeDetail();
1789 BeanUtils.copyProperties(detail, newDetail);
1790 newDetail.setDocumentNumber(documentNumber);
1791 newDetail.setVersionNumber(new Long(1));
1792 newDetail.setObjectId(null);
1793 newTransportationModeDetails.add(newDetail);
1794 }
1795 }
1796 return newTransportationModeDetails;
1797 }
1798
1799
1800
1801
1802 @Override
1803 public void showNoTravelAuthorizationError(TravelReimbursementDocument document){
1804 if (document.getTripType() != null && document.getTripType().getTravelAuthorizationRequired()){
1805 TravelAuthorizationDocument authorization = findCurrentTravelAuthorization(document);
1806 if (authorization == null){
1807 GlobalVariables.getMessageMap().putError(KRADPropertyConstants.DOCUMENT + "." + TemPropertyConstants.TRIP_TYPE_CODE, TemKeyConstants.ERROR_TRIP_TYPE_TA_REQUIRED, document.getTripType().getName());
1808 }
1809 }
1810 }
1811
1812
1813
1814
1815 @Override
1816 public KualiDecimal getAdvancesTotalFor(TravelDocument travelDocument) {
1817 KualiDecimal retval = KualiDecimal.ZERO;
1818 if (ObjectUtils.isNull(travelDocument)) {
1819 return retval;
1820 }
1821
1822 LOG.debug("Looking for travel advances for travel: "+ travelDocument.getDocumentNumber());
1823
1824 TravelAuthorizationDocument authorization = null;
1825 authorization = findCurrentTravelAuthorization(travelDocument);
1826
1827 if (authorization == null) {
1828 return retval;
1829 }
1830 authorization.refreshReferenceObject(TemPropertyConstants.TRVL_ADV);
1831
1832 if (authorization.shouldProcessAdvanceForDocument()) {
1833 retval = retval.add(authorization.getTravelAdvance().getTravelAdvanceRequested());
1834 }
1835 return retval;
1836 }
1837
1838
1839
1840
1841 @Override
1842 public String retrieveAddressFromLocationCode(String locationCode) {
1843 PaymentDocumentationLocation dvDocumentLocation = businessObjectService.findBySinglePrimaryKey(PaymentDocumentationLocation.class, locationCode);
1844 String address = ObjectUtils.isNotNull(dvDocumentLocation)? dvDocumentLocation.getPaymentDocumentationLocationAddress() : "";
1845 return address;
1846 }
1847
1848 @Override
1849 public boolean validateSourceAccountingLines(TravelDocument travelDocument, boolean addToErrorPath) {
1850 boolean success = true;
1851 Map<String,Object> fieldValues = new HashMap<String,Object>();
1852 fieldValues.put(KRADPropertyConstants.DOCUMENT_NUMBER, travelDocument.getDocumentNumber());
1853 fieldValues.put(KFSPropertyConstants.FINANCIAL_DOCUMENT_LINE_TYPE_CODE, KFSConstants.SOURCE_ACCT_LINE_TYPE_CODE);
1854
1855 List<TemSourceAccountingLine> currentLines = (List<TemSourceAccountingLine>) getBusinessObjectService().findMatchingOrderBy(TemSourceAccountingLine.class, fieldValues,KFSPropertyConstants.SEQUENCE_NUMBER, true);
1856
1857 final boolean canUpdate = isAtTravelNode(travelDocument.getDocumentHeader().getWorkflowDocument());
1858
1859
1860 for (int i=0;i<travelDocument.getSourceAccountingLines().size();i++){
1861 AccountingLine line = (AccountingLine) travelDocument.getSourceAccountingLines().get(i);
1862 if (addToErrorPath){
1863 GlobalVariables.getMessageMap().getErrorPath().add("document." + TemPropertyConstants.SOURCE_ACCOUNTING_LINE + "[" + i + "]");
1864 }
1865 if(StringUtils.isBlank(line.getAccountNumber())){
1866 success = false;
1867 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_REQUIRED, "Account Number");
1868 }
1869 else{
1870 if ((!travelDocument.getAppDocStatus().equalsIgnoreCase("Initiated"))
1871 && (!travelDocument.getAppDocStatus().equalsIgnoreCase(TemConstants.TravelAuthorizationStatusCodeKeys.IN_PROCESS))
1872 && (!travelDocument.getAppDocStatus().equalsIgnoreCase(TemConstants.TravelAuthorizationStatusCodeKeys.CHANGE_IN_PROCESS))){
1873 if ((i < currentLines.size()) && (!(currentLines.get(i)).getAccountNumber().equals(line.getAccountNumber()))
1874 || (i >= currentLines.size())){
1875 try{
1876 if (!line.getAccount().getAccountFiscalOfficerUser().getPrincipalId().equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())
1877 && !canUpdate){
1878 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, TemKeyConstants.ERROR_TA_FISCAL_OFFICER_ACCOUNT, line.getAccountNumber());
1879 success = false;
1880 }
1881 }
1882 catch(Exception e){
1883
1884 }
1885 }
1886 }
1887 }
1888 if(StringUtils.isBlank(line.getChartOfAccountsCode())){
1889 success = false;
1890 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, KFSKeyConstants.ERROR_REQUIRED, "Chart");
1891 }
1892 if (addToErrorPath){
1893 GlobalVariables.getMessageMap().getErrorPath().remove(GlobalVariables.getMessageMap().getErrorPath().size()-1);
1894 }
1895 }
1896
1897 return success;
1898 }
1899
1900
1901
1902
1903
1904
1905 private boolean showPerDiem(List<String> perDiemCats, String perDiemType) {
1906 for (String category : perDiemCats) {
1907 String[] pair = category.split("=");
1908 if (pair[0].equalsIgnoreCase(perDiemType)) {
1909 return pair[1].equalsIgnoreCase(TemConstants.YES);
1910 }
1911 if (pair[0].equalsIgnoreCase(perDiemType)) {
1912 return pair[1].equalsIgnoreCase(TemConstants.YES);
1913 }
1914 if (pair[0].equalsIgnoreCase(perDiemType)) {
1915 return pair[1].equalsIgnoreCase(TemConstants.YES);
1916 }
1917 }
1918
1919 return false;
1920 }
1921
1922
1923
1924
1925 @Override
1926 public List<TravelAdvance> getOutstandingTravelAdvanceByInvoice(Set<String> arInvoiceDocNumbers) {
1927 return travelDocumentDao.getOutstandingTravelAdvanceByInvoice(arInvoiceDocNumbers);
1928 }
1929
1930
1931
1932
1933 @Override
1934 public Date findLatestTaxableRamificationNotificationDate() {
1935 Object[] returnResult = travelDocumentDao.findLatestTaxableRamificationNotificationDate();
1936 Date date = null;
1937 try {
1938 date = ObjectUtils.isNotNull(returnResult[0])? dateTimeService.convertToSqlDate((Timestamp)returnResult[0]): null;
1939 }catch (java.text.ParseException ex) {
1940 LOG.error("Invalid latest taxable ramification notification date " + returnResult[0]);
1941 }
1942
1943 return date;
1944 }
1945
1946 @Override
1947 public void detachImportedExpenses(TravelDocument document) {
1948 for (ImportedExpense importedExpense : document.getImportedExpenses()){
1949 ExpenseUtils.assignExpense(importedExpense.getHistoricalTravelExpenseId(), null, null, null, false);
1950 }
1951 document.setImportedExpenses(new ArrayList<ImportedExpense>());
1952 document.setHistoricalTravelExpenses(new ArrayList<HistoricalTravelExpense>());
1953 }
1954
1955 @Override
1956 public void attachImportedExpenses(TravelDocument document) {
1957 for (ImportedExpense importedExpense : document.getImportedExpenses()){
1958 ExpenseUtils.assignExpense(importedExpense.getHistoricalTravelExpenseId(), document.getTravelDocumentIdentifier(),document.getDocumentNumber(), document.getFinancialDocumentTypeCode(), true);
1959 }
1960 }
1961
1962
1963
1964
1965
1966
1967
1968 @Override
1969 public boolean checkHoldGLPEs(TravelDocument document) {
1970 if(getParameterService().getParameterValueAsBoolean(TravelAuthorizationDocument.class, TemConstants.TravelAuthorizationParameters.HOLD_NEW_FISCAL_YEAR_ENCUMBRANCES_IND)) {
1971
1972 java.util.Date endDate = getUniversityDateService().getLastDateOfFiscalYear(getUniversityDateService().getCurrentFiscalYear());
1973 if (ObjectUtils.isNotNull(document.getTripBegin()) && document.getTripBegin().after(endDate)) {
1974 return true;
1975 }
1976
1977 }
1978
1979 return false;
1980 }
1981
1982
1983
1984
1985 @Override
1986 public void revertOriginalDocument(TravelDocument travelDocument, String status) {
1987 final DocumentAttributeIndexingQueue documentAttributeIndexingQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue();
1988 List<Document> relatedDocumentList = getDocumentsRelatedTo(travelDocument, TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT,
1989 TravelDocTypes.TRAVEL_AUTHORIZATION_AMEND_DOCUMENT);
1990
1991 for (Document taDocument : relatedDocumentList) {
1992 if (taDocument.getDocumentHeader().getWorkflowDocument().getApplicationDocumentStatus().equals(TravelAuthorizationStatusCodeKeys.PEND_AMENDMENT)) {
1993 TravelAuthorizationDocument taDoc = (TravelAuthorizationDocument) taDocument;
1994 try {
1995 taDoc.updateAndSaveAppDocStatus(status);
1996 }
1997 catch (WorkflowException ex1) {
1998
1999 ex1.printStackTrace();
2000 }
2001
2002 try {
2003 Note cancelNote = getDocumentService().createNoteFromDocument(taDoc, "Amemdment Canceled");
2004 Principal systemUser = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(KFSConstants.SYSTEM_USER);
2005 cancelNote.setAuthorUniversalIdentifier(systemUser.getPrincipalId());
2006 taDoc.addNote(cancelNote);
2007 getNoteService().save(cancelNote);
2008 }
2009 catch (Exception ex) {
2010 ex.printStackTrace();
2011 }
2012 documentAttributeIndexingQueue.indexDocument(taDoc.getDocumentNumber());
2013 }
2014 }
2015 }
2016
2017
2018
2019
2020 @Override
2021 public String getDocumentType(TravelDocument document) {
2022 String documentType = null;
2023
2024 if (document != null) {
2025 if (document instanceof TravelAuthorizationDocument) {
2026 documentType = TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT;
2027 }
2028 else if (document instanceof TravelReimbursementDocument) {
2029 documentType = TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT;
2030 }
2031 else if (document instanceof TravelEntertainmentDocument) {
2032 documentType = TemConstants.TravelDocTypes.TRAVEL_ENTERTAINMENT_DOCUMENT;
2033 }
2034 else if (document instanceof TravelRelocationDocument) {
2035 documentType = TemConstants.TravelDocTypes.TRAVEL_RELOCATION_DOCUMENT;
2036 }
2037 }
2038
2039 return documentType;
2040 }
2041
2042
2043
2044
2045
2046
2047
2048
2049 protected boolean isAtTravelNode(WorkflowDocument workflowDocument) {
2050 Set<String> nodeNames = workflowDocument.getNodeNames();
2051 for (String nodeNamesNode : nodeNames) {
2052 if (TemWorkflowConstants.RouteNodeNames.AP_TRAVEL.equals(nodeNamesNode)) {
2053 return true;
2054 }
2055 }
2056 return false;
2057 }
2058
2059
2060
2061
2062
2063 @Override
2064 public List<TravelAdvance> getTravelAdvancesForTrip(String travelDocumentIdentifier) {
2065 Map<String, String> criteria = new HashMap<String, String>();
2066 criteria.put(TemPropertyConstants.TRAVEL_DOCUMENT_IDENTIFIER, travelDocumentIdentifier);
2067 List<TravelAdvance> advances = new ArrayList<TravelAdvance>();
2068 final Collection<TravelAdvance> foundAdvances = getBusinessObjectService().findMatchingOrderBy(TravelAdvance.class, criteria, KFSPropertyConstants.DOCUMENT_NUMBER, true);
2069 for (TravelAdvance foundAdvance: foundAdvances) {
2070 if (foundAdvance.isAtLeastPartiallyFilledIn() && isDocumentApprovedOrExtracted(foundAdvance.getDocumentNumber())) {
2071 advances.add(foundAdvance);
2072 }
2073 }
2074 return advances;
2075 }
2076
2077
2078
2079
2080
2081
2082
2083
2084 protected boolean isDocumentApprovedOrExtracted(String documentNumber) {
2085 final FinancialSystemDocumentHeader documentHeader = getBusinessObjectService().findBySinglePrimaryKey(FinancialSystemDocumentHeader.class, documentNumber);
2086 return KFSConstants.DocumentStatusCodes.APPROVED.equals(documentHeader.getFinancialDocumentStatusCode()) || KFSConstants.DocumentStatusCodes.Payments.EXTRACTED.equals(documentHeader.getFinancialDocumentStatusCode());
2087 }
2088
2089
2090
2091
2092
2093
2094 protected boolean isDocumentInitiatedOrEnroute(String documentNumber) {
2095 final FinancialSystemDocumentHeader documentHeader = getBusinessObjectService().findBySinglePrimaryKey(FinancialSystemDocumentHeader.class, documentNumber);
2096 return KFSConstants.DocumentStatusCodes.INITIATED.equals(documentHeader.getFinancialDocumentStatusCode()) || KFSConstants.DocumentStatusCodes.ENROUTE.equals(documentHeader.getFinancialDocumentStatusCode());
2097 }
2098
2099
2100
2101
2102
2103
2104
2105 @Override
2106 public AccountsReceivableOrganizationOptions getOrgOptions() {
2107 final String chartOfAccountsCode = parameterService.getParameterValueAsString(TravelAuthorizationDocument.class, TravelAuthorizationParameters.TRAVEL_ADVANCE_BILLING_CHART);
2108 final String organizationCode = parameterService.getParameterValueAsString(TravelAuthorizationDocument.class, TravelAuthorizationParameters.TRAVEL_ADVANCE_BILLING_ORGANIZATION);
2109
2110 return getAccountsReceivableModuleService().getOrgOptionsIfExists(chartOfAccountsCode, organizationCode);
2111 }
2112
2113
2114
2115
2116 @Override
2117 public void disableDuplicateExpenses(TravelDocument trDocument, ActualExpense actualExpense) {
2118 if (trDocument.getPerDiemExpenses() != null && !trDocument.getPerDiemExpenses().isEmpty()) {
2119 if (actualExpense.getExpenseDetails() != null && !actualExpense.getExpenseDetails().isEmpty()) {
2120 for (TemExpense detail : actualExpense.getExpenseDetails()) {
2121 checkActualExpenseAgainstPerDiems(trDocument, (ActualExpense)detail);
2122 }
2123 } else {
2124 checkActualExpenseAgainstPerDiems(trDocument, actualExpense);
2125 }
2126 }
2127 }
2128
2129
2130
2131
2132
2133
2134 protected void checkActualExpenseAgainstPerDiems(TravelDocument trDocument, ActualExpense actualExpense) {
2135 int i = 0;
2136 for (final PerDiemExpense perDiemExpense : trDocument.getPerDiemExpenses()) {
2137 List<DisabledPropertyMessage> messages = disableDuplicateExpenseForPerDiem(actualExpense, perDiemExpense, i);
2138 if (messages != null && !messages.isEmpty()) {
2139 for (DisabledPropertyMessage message : messages) {
2140 message.addToProperties(trDocument.getDisabledProperties());
2141 }
2142 }
2143 i+=1;
2144 }
2145 }
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155 protected List<DisabledPropertyMessage> disableDuplicateExpenseForPerDiem(ActualExpense actualExpense, PerDiemExpense perDiemExpense, int perDiemCount) {
2156 List<DisabledPropertyMessage> disabledPropertyMessages = new ArrayList<DisabledPropertyMessage>();
2157
2158 if (actualExpense.getExpenseDate() == null){
2159 return disabledPropertyMessages;
2160 }
2161 final String expenseDate = getDateTimeService().toDateString(actualExpense.getExpenseDate());
2162 String meal = "";
2163 boolean valid = true;
2164
2165 if (KfsDateUtils.isSameDay(perDiemExpense.getMileageDate(), actualExpense.getExpenseDate())) {
2166 if (perDiemExpense.getBreakfast() && actualExpense.isBreakfast() && (actualExpense.getExpenseType().isHosted() || actualExpense.getExpenseType().isGroupTravel())) {
2167 meal = TemConstants.HostedMeals.HOSTED_BREAKFAST;
2168 perDiemExpense.setBreakfast(false);
2169 perDiemExpense.setBreakfastValue(KualiDecimal.ZERO);
2170 valid = false;
2171 }
2172 else if (perDiemExpense.getLunch() && actualExpense.isLunch() && (actualExpense.getExpenseType().isHosted() || actualExpense.getExpenseType().isGroupTravel())) {
2173 meal = TemConstants.HostedMeals.HOSTED_LUNCH;
2174 perDiemExpense.setLunch(false);
2175 perDiemExpense.setLunchValue(KualiDecimal.ZERO);
2176 valid = false;
2177 }
2178 else if (perDiemExpense.getDinner() && actualExpense.isDinner() && (actualExpense.getExpenseType().isHosted() || actualExpense.getExpenseType().isGroupTravel())) {
2179 meal = TemConstants.HostedMeals.HOSTED_DINNER;
2180 perDiemExpense.setDinner(false);
2181 perDiemExpense.setDinnerValue(KualiDecimal.ZERO);
2182 valid = false;
2183 }
2184
2185 if (!valid) {
2186 String temp = String.format(PER_DIEM_EXPENSE_DISABLED, perDiemCount, meal);
2187 String message = getMessageFrom(MESSAGE_TR_MEAL_ALREADY_CLAIMED, expenseDate, meal);
2188 disabledPropertyMessages.add(new DisabledPropertyMessage(temp, message));
2189 }
2190
2191
2192 if (perDiemExpense.getLodging().isGreaterThan(KualiDecimal.ZERO) && !StringUtils.isBlank(actualExpense.getExpenseTypeCode()) && TemConstants.ExpenseTypes.LODGING.equals(actualExpense.getExpenseTypeCode())) {
2193 String temp = String.format(PER_DIEM_EXPENSE_DISABLED, perDiemCount, TemConstants.LODGING.toLowerCase());
2194 String message = getMessageFrom(MESSAGE_TR_LODGING_ALREADY_CLAIMED, expenseDate);
2195 perDiemExpense.setLodging(KualiDecimal.ZERO);
2196 disabledPropertyMessages.add(new DisabledPropertyMessage(temp, message));
2197 }
2198 }
2199 return disabledPropertyMessages;
2200 }
2201
2202
2203 @Override
2204 public List<String> findMatchingTrips(TravelDocument travelDocument) {
2205
2206 String travelDocumentIdentifier = travelDocument.getTravelDocumentIdentifier();
2207 Integer temProfileId = travelDocument.getTemProfileId();
2208 Timestamp earliestTripBeginDate = null;
2209 Timestamp greatestTripEndDate = null;
2210
2211 List<TravelReimbursementDocument> documents = findReimbursementDocuments(travelDocumentIdentifier);
2212 for (TravelReimbursementDocument document : documents) {
2213 Timestamp tripBegin = document.getTripBegin();
2214 Timestamp tripEnd = document.getTripEnd();
2215 if (ObjectUtils.isNull(earliestTripBeginDate) && ObjectUtils.isNull(greatestTripEndDate)) {
2216 earliestTripBeginDate = tripBegin;
2217 greatestTripEndDate = tripEnd;
2218 }
2219 else {
2220 earliestTripBeginDate = tripBegin.before(earliestTripBeginDate) ? tripBegin :earliestTripBeginDate;
2221 greatestTripEndDate = tripEnd.after(greatestTripEndDate)? tripEnd : greatestTripEndDate;
2222
2223 }
2224 }
2225
2226
2227 if(documents.isEmpty() && ObjectUtils.isNotNull(travelDocument.getTripBegin()) && ObjectUtils.isNotNull(travelDocument.getTripEnd())) {
2228 earliestTripBeginDate = getTripBeginDate(travelDocument.getTripBegin());
2229 greatestTripEndDate = getTripEndDate(travelDocument.getTripEnd());
2230 }
2231
2232 List<TravelReimbursementDocument> matchDocs = (List<TravelReimbursementDocument>) travelDocumentDao.findMatchingTrips(temProfileId ,earliestTripBeginDate, greatestTripEndDate);
2233 List<String> documentIds = new ArrayList<String>();
2234 for (TravelReimbursementDocument document : matchDocs) {
2235 if(!travelDocument.getDocumentNumber().equals(document.getDocumentNumber())) {
2236 documentIds.add(document.getDocumentNumber());
2237 }
2238 }
2239 return documentIds;
2240 }
2241
2242 private Integer getDuplicateTripDateRangeDays() {
2243 String tripDateRangeDays = parameterService.getParameterValueAsString(TravelAuthorizationDocument.class, TemConstants.TravelParameters.DUPLICATE_TRIP_DATE_RANGE_DAYS);
2244 Integer days = null;
2245 if (!StringUtils.isNumeric(tripDateRangeDays)) {
2246 days = TemConstants.DEFAULT_DUPLICATE_TRIP_DATE_RANGE_DAYS;
2247 }
2248
2249 days = Integer.parseInt(tripDateRangeDays);
2250 return days;
2251
2252 }
2253
2254 private Timestamp getTripBeginDate(Timestamp tripBeginDate) {
2255 Timestamp tripBegin = null;
2256 Integer days = getDuplicateTripDateRangeDays();
2257 try {
2258 tripBegin = dateTimeService.convertToSqlTimestamp(dateTimeService.toDateString(DateUtils.addDays(tripBeginDate, (days * -1))));
2259
2260 } catch (java.text.ParseException pe) {
2261 LOG.error("Exception while parsing trip begin date" + pe);
2262 }
2263
2264
2265 return tripBegin;
2266
2267 }
2268
2269 private Timestamp getTripEndDate(Timestamp tripEndDate) {
2270 Timestamp tripEnd = null;
2271 Integer days = getDuplicateTripDateRangeDays();
2272 try {
2273 tripEnd = dateTimeService.convertToSqlTimestamp(dateTimeService.toDateString((DateUtils.addDays(tripEndDate, days ))));
2274
2275 } catch (java.text.ParseException pe) {
2276 LOG.error("Exception while parsing trip end date" + pe);
2277 }
2278
2279 return tripEnd;
2280
2281 }
2282
2283
2284
2285
2286
2287 class DisabledPropertyMessage {
2288 private String key;
2289 private String message;
2290
2291 DisabledPropertyMessage(String key, String message) {
2292 this.key = key;
2293 this.message = message;
2294 }
2295
2296 void addToProperties(Map<String, String> messageMap) {
2297 messageMap.put(key, message);
2298 }
2299 }
2300
2301
2302
2303
2304
2305
2306
2307 @Override
2308 public TravelDocument getParentTravelDocument(String travelDocumentIdentifier) {
2309
2310 if (ObjectUtils.isNull(travelDocumentIdentifier) || StringUtils.equals(travelDocumentIdentifier,"")) {
2311 LOG.error("Received a null tripId/travelDocumentIdentifier; returning a null TravelDocument");
2312 return null;
2313 }
2314
2315 try {
2316 TravelDocument travelDocument = findRootForTravelReimbursement(travelDocumentIdentifier);
2317 if (ObjectUtils.isNotNull(travelDocument)) {
2318 LOG.debug("Found "+ travelDocument.getDocumentNumber() +" ("+ travelDocument.getDocumentTypeName() +") for travelDocumentIdentifier: "+ travelDocumentIdentifier);
2319 return travelDocument;
2320 }
2321
2322 } catch (Exception exception) {
2323 LOG.error("Exception occurred attempting to retrieve an authorization or remibursement travel document for travelDocumentIdentifier: "+ travelDocumentIdentifier, exception);
2324 return null;
2325 }
2326
2327 Map<String, Object> fieldValues = new HashMap<String, Object>();
2328 fieldValues.put(TemPropertyConstants.TRAVEL_DOCUMENT_IDENTIFIER, travelDocumentIdentifier);
2329 fieldValues.put(TemPropertyConstants.TRIP_PROGENITOR, Boolean.TRUE);
2330
2331 Collection<TravelEntertainmentDocument> entDocuments = getBusinessObjectService().findMatching(TravelEntertainmentDocument.class, fieldValues);
2332 if (entDocuments.iterator().hasNext()) {
2333 TravelDocument ent = entDocuments.iterator().next();
2334 LOG.debug("Found "+ ent.getDocumentNumber() +" ("+ ent.getDocumentTypeName() +") for travelDocumentIdentifier: "+ travelDocumentIdentifier);
2335 return ent;
2336 }
2337
2338 Collection<TravelRelocationDocument> reloDocuments = getBusinessObjectService().findMatching(TravelRelocationDocument.class, fieldValues);
2339 if (reloDocuments.iterator().hasNext()) {
2340 TravelDocument relo = reloDocuments.iterator().next();
2341 LOG.info("Found "+ relo.getDocumentNumber() +" ("+ relo.getDocumentTypeName() +") for travelDocumentIdentifier: "+ travelDocumentIdentifier);
2342 return relo;
2343 }
2344
2345 LOG.error("Unable to find any travel document for given Trip Id: "+ travelDocumentIdentifier);
2346 return null;
2347 }
2348
2349
2350
2351
2352
2353
2354 protected KualiDecimal getAccountingLineAmount(TravelDocument travelDoc) {
2355 KualiDecimal total = KualiDecimal.ZERO;
2356 if (travelDoc.getSourceAccountingLines() != null && !travelDoc.getSourceAccountingLines().isEmpty()) {
2357 for (TemSourceAccountingLine accountingLine : (List<TemSourceAccountingLine>)travelDoc.getSourceAccountingLines()) {
2358 total = total.add(accountingLine.getAmount());
2359 }
2360 }
2361 return total;
2362 }
2363
2364
2365
2366
2367
2368 @Override
2369 public Collection<String> getApprovedTravelDocumentNumbersByTrip(String travelDocumentIdentifier) {
2370 HashMap<String,String> documentNumbersToReturn = new HashMap<String,String>();
2371
2372 List<TravelDocument> travelDocuments = new ArrayList<TravelDocument>();
2373
2374 TravelDocument travelDocument = getParentTravelDocument(travelDocumentIdentifier);
2375 if (ObjectUtils.isNotNull(travelDocument)) {
2376 travelDocuments.add(travelDocument);
2377 }
2378
2379 travelDocuments.addAll(getTravelDocumentDao().findDocuments(TravelReimbursementDocument.class, travelDocumentIdentifier));
2380 travelDocuments.addAll(getTravelDocumentDao().findDocuments(TravelEntertainmentDocument.class, travelDocumentIdentifier));
2381 travelDocuments.addAll(getTravelDocumentDao().findDocuments(TravelRelocationDocument.class, travelDocumentIdentifier));
2382
2383 for(Iterator<TravelDocument> iter = travelDocuments.iterator(); iter.hasNext();) {
2384 TravelDocument document = iter.next();
2385 if (!documentNumbersToReturn.containsKey(document.getDocumentNumber()) && isDocumentStatusValidForReconcilingCharges(document)) {
2386 documentNumbersToReturn.put(document.getDocumentNumber(),document.getDocumentNumber());
2387 }
2388 }
2389
2390 return documentNumbersToReturn.values();
2391 }
2392
2393 @Override
2394 public boolean isDocumentStatusValidForReconcilingCharges(TravelDocument travelDocument) {
2395
2396 String documentNumber = travelDocument.getDocumentNumber();
2397
2398 if (isDocumentApprovedOrExtracted(documentNumber)) {
2399 return true;
2400 }
2401
2402 if (travelDocument instanceof TravelAuthorizationDocument) {
2403 boolean vendorPaymentAllowedBeforeFinalAuthorization = getParameterService().getParameterValueAsBoolean(TravelAuthorizationDocument.class, TemConstants.TravelAuthorizationParameters.VENDOR_PAYMENT_ALLOWED_BEFORE_FINAL_APPROVAL_IND);
2404
2405 if (vendorPaymentAllowedBeforeFinalAuthorization) {
2406 return isDocumentInitiatedOrEnroute(documentNumber);
2407 }
2408 }
2409
2410 if (travelDocument instanceof TravelReimbursementDocument) {
2411 boolean vendorPaymentAllowedBeforeFinalReimbursement = getParameterService().getParameterValueAsBoolean(TravelReimbursementDocument.class, TemConstants.TravelAuthorizationParameters.VENDOR_PAYMENT_ALLOWED_BEFORE_FINAL_APPROVAL_IND);
2412
2413 if (vendorPaymentAllowedBeforeFinalReimbursement) {
2414 return isDocumentInitiatedOrEnroute(documentNumber);
2415 }
2416 }
2417
2418 return false;
2419 }
2420
2421
2422
2423
2424
2425 @Override
2426 public void restorePerDiemProperty(TravelDocument document, String property) {
2427 try {
2428 final String[] perDiemPropertyParts = splitPerDiemProperty(property);
2429 PerDiemExpense perDiemExpense = (PerDiemExpense)ObjectUtils.getPropertyValue(document, perDiemPropertyParts[0]);
2430 final String mealName = perDiemPropertyParts[1];
2431 final boolean mealProperty = isMealProperty(mealName);
2432 final String mealSuffix = (mealProperty) ? "Value" : "";
2433 final String mealValueName = mealName+mealSuffix;
2434
2435 KualiDecimal currentMealValue = (KualiDecimal)ObjectUtils.getPropertyValue(perDiemExpense, mealValueName);
2436 if (currentMealValue != null && currentMealValue.equals(KualiDecimal.ZERO)) {
2437 final PerDiem perDiem = getPerDiemService().getPerDiem(perDiemExpense.getPrimaryDestinationId(), perDiemExpense.getMileageDate(), document.getEffectiveDateForPerDiem(perDiemExpense));
2438 final KualiDecimal mealAmount = (KualiDecimal)ObjectUtils.getPropertyValue(perDiem, mealName);
2439 final boolean prorated = mealProperty && !KfsDateUtils.isSameDay(document.getTripBegin(), document.getTripEnd()) && (KfsDateUtils.isSameDay(perDiemExpense.getMileageDate(), document.getTripBegin()) || KfsDateUtils.isSameDay(perDiemExpense.getMileageDate(), document.getTripEnd()));
2440 if (prorated && !ObjectUtils.isNull(document.getTripType())) {
2441 perDiemExpense.setProrated(true);
2442 final String perDiemCalcMethod = document.getTripType().getPerDiemCalcMethod();
2443 final Integer perDiemPercent = calculateProratePercentage(perDiemExpense, perDiemCalcMethod, document.getTripEnd());
2444 final KualiDecimal proratedAmount = PerDiemExpense.calculateMealsAndIncidentalsProrated(mealAmount, perDiemPercent);
2445 ObjectUtils.setObjectProperty(perDiemExpense, mealValueName, proratedAmount);
2446 } else {
2447 ObjectUtils.setObjectProperty(perDiemExpense, mealValueName, mealAmount);
2448 }
2449 if (mealProperty) {
2450 ObjectUtils.setObjectProperty(perDiemExpense, mealName, Boolean.TRUE);
2451 }
2452 }
2453 }
2454 catch (FormatException fe) {
2455 throw new RuntimeException("Could not set meal value on per diem expense", fe);
2456 }
2457 catch (IllegalAccessException iae) {
2458 throw new RuntimeException("Could not set meal value on per diem expense", iae);
2459 }
2460 catch (InvocationTargetException ite) {
2461 throw new RuntimeException("Could not set meal value on per diem expense", ite);
2462 }
2463 catch (NoSuchMethodException nsme) {
2464 throw new RuntimeException("Could not set meal value on per diem expense", nsme);
2465 }
2466 }
2467
2468
2469
2470
2471
2472
2473 protected boolean isMealProperty(String property) {
2474 return StringUtils.equals(property, TemPropertyConstants.BREAKFAST) || StringUtils.equals(property, TemPropertyConstants.LUNCH) || StringUtils.equals(property, TemPropertyConstants.DINNER) || StringUtils.equals(property, TemPropertyConstants.INCIDENTALS);
2475 }
2476
2477
2478
2479
2480
2481
2482 protected String[] splitPerDiemProperty(String property) {
2483 final String deDocumentedProperty = property.replace(KFSPropertyConstants.DOCUMENT+".", KFSConstants.EMPTY_STRING);
2484 final int lastDivider = deDocumentedProperty.lastIndexOf('.');
2485 final String perDiemPart = deDocumentedProperty.substring(0, lastDivider);
2486 final String mealPart = deDocumentedProperty.substring(lastDivider+1);
2487 return new String[] { perDiemPart, mealPart };
2488 }
2489
2490
2491
2492
2493
2494 @Override
2495 public TravelDocument getRootTravelDocumentWithoutWorkflowDocument(String travelDocumentIdentifier) {
2496 Map<String, Object> fieldValues = new HashMap<String, Object>();
2497 fieldValues.put(TemPropertyConstants.TRAVEL_DOCUMENT_IDENTIFIER, travelDocumentIdentifier);
2498 fieldValues.put(TemPropertyConstants.TRIP_PROGENITOR, new Boolean(true));
2499 for (String documentType : getTravelDocumentTypesToCheck()) {
2500 final Class<? extends TravelDocument> docClazz = getTravelDocumentForType(documentType);
2501 Collection<TravelDocument> matchingDocs = (Collection<TravelDocument>)getBusinessObjectService().findMatching(docClazz, fieldValues);
2502 if (matchingDocs != null && !matchingDocs.isEmpty()) {
2503 List<TravelDocument> foundDocs = new ArrayList<TravelDocument>();
2504 foundDocs.addAll(matchingDocs);
2505 return foundDocs.get(0);
2506 }
2507 }
2508 return null;
2509 }
2510
2511
2512
2513
2514
2515
2516
2517
2518 protected List<String> getTravelDocumentTypesToCheck() {
2519 List<String> documentTypes = new ArrayList<String>();
2520 documentTypes.add(TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_DOCUMENT);
2521 documentTypes.add(TemConstants.TravelDocTypes.TRAVEL_ENTERTAINMENT_DOCUMENT);
2522 documentTypes.add(TemConstants.TravelDocTypes.TRAVEL_RELOCATION_DOCUMENT);
2523 documentTypes.add(TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
2524 return documentTypes;
2525 }
2526
2527
2528
2529
2530
2531
2532 protected Class<? extends TravelDocument> getTravelDocumentForType(String documentType) {
2533 return (Class<TravelDocument>)getDataDictionaryService().getDocumentClassByTypeName(documentType);
2534 }
2535
2536
2537
2538
2539
2540
2541
2542 @Override
2543 public List<TemSourceAccountingLine> smooshAccountingLinesToSubAccount(List<TemSourceAccountingLine> originalAccountingLines) {
2544 final Map<SmooshLineKey, KualiDecimal> smooshLines = smooshLinesToMap(originalAccountingLines);
2545 final List<TemSourceAccountingLine> unsmooshedLines = raiseMapToLines(smooshLines);
2546 return unsmooshedLines;
2547 }
2548
2549
2550
2551
2552
2553
2554 protected Map<SmooshLineKey, KualiDecimal> smooshLinesToMap(List<TemSourceAccountingLine> accountingLines) {
2555 Map<SmooshLineKey, KualiDecimal> smooshLines = new HashMap<SmooshLineKey, KualiDecimal>();
2556 for (TemSourceAccountingLine line : accountingLines) {
2557 final SmooshLineKey key = new SmooshLineKey(line);
2558 if (smooshLines.containsKey(key)) {
2559 KualiDecimal currAmount = smooshLines.get(key);
2560 KualiDecimal newAmount = currAmount.add(line.getAmount());
2561 smooshLines.put(key, newAmount);
2562 } else {
2563 smooshLines.put(key, line.getAmount());
2564 }
2565 }
2566 return smooshLines;
2567 }
2568
2569
2570
2571
2572
2573
2574 protected List<TemSourceAccountingLine> raiseMapToLines(Map<SmooshLineKey, KualiDecimal> smooshLineMap) {
2575 List<TemSourceAccountingLine> raisedLines = new ArrayList<TemSourceAccountingLine>();
2576 for (SmooshLineKey key : smooshLineMap.keySet()) {
2577 final TemSourceAccountingLine line = convertKeyAndAmountToLine(key, smooshLineMap.get(key));
2578 raisedLines.add(line);
2579 }
2580 return raisedLines;
2581 }
2582
2583
2584
2585
2586
2587
2588
2589 protected TemSourceAccountingLine convertKeyAndAmountToLine(SmooshLineKey key, KualiDecimal amount) {
2590 TemSourceAccountingLine line = new TemSourceAccountingLine();
2591 line.setChartOfAccountsCode(key.getChartOfAccountsCode());
2592 line.setAccountNumber(key.getAccountNumber());
2593 line.setSubAccountNumber(key.getSubAccountNumber());
2594 line.setAmount(amount);
2595 return line;
2596 }
2597
2598
2599
2600
2601 protected class SmooshLineKey {
2602 protected String chartOfAccountsCode;
2603 protected String accountNumber;
2604 protected String subAccountNumber;
2605
2606 public SmooshLineKey(TemSourceAccountingLine accountingLine) {
2607 this.chartOfAccountsCode = accountingLine.getChartOfAccountsCode();
2608 this.accountNumber = accountingLine.getAccountNumber();
2609 this.subAccountNumber = accountingLine.getSubAccountNumber();
2610 }
2611
2612 public String getChartOfAccountsCode() {
2613 return chartOfAccountsCode;
2614 }
2615
2616 public String getAccountNumber() {
2617 return accountNumber;
2618 }
2619
2620 public String getSubAccountNumber() {
2621 return subAccountNumber;
2622 }
2623
2624 @Override
2625 public int hashCode() {
2626 HashCodeBuilder hcb = new HashCodeBuilder();
2627 hcb.append(getChartOfAccountsCode());
2628 hcb.append(getAccountNumber());
2629 hcb.append(getSubAccountNumber());
2630 return hcb.toHashCode();
2631 }
2632
2633 @Override
2634 public boolean equals(Object obj) {
2635 if (!(obj instanceof SmooshLineKey) || obj == null) {
2636 return false;
2637 }
2638 final SmooshLineKey golyadkin = (SmooshLineKey)obj;
2639 EqualsBuilder eb = new EqualsBuilder();
2640 eb.append(getChartOfAccountsCode(), golyadkin.getChartOfAccountsCode());
2641 eb.append(getAccountNumber(), golyadkin.getAccountNumber());
2642 eb.append(getSubAccountNumber(), golyadkin.getSubAccountNumber());
2643 return eb.isEquals();
2644 }
2645 }
2646
2647
2648
2649
2650
2651 @Override
2652 public List<LinkField> getAgencyLinks(TravelDocument travelDocument) {
2653 List<LinkField> agencyLinks = new ArrayList<LinkField>();
2654 if (getConfigurationService().getPropertyValueAsBoolean(TemKeyConstants.ENABLE_AGENCY_SITES_URL)) {
2655 final String agencySitesURL = getConfigurationService().getPropertyValueAsString(TemKeyConstants.AGENCY_SITES_URL);
2656 final String target = "_blank";
2657 if(!StringUtils.isEmpty(agencySitesURL)){
2658 String[] sites = agencySitesURL.split(";");
2659 for (String site : sites){
2660 String[] siteInfo = site.split("=");
2661 String url = customizeAgencyLink(travelDocument, siteInfo[0], siteInfo[1]);
2662 final String prefixedUrl = prefixUrl(url);
2663 LinkField link = new LinkField();
2664 link.setHrefText(prefixedUrl);
2665 link.setTarget(target);
2666 link.setLinkLabel(siteInfo[0]);
2667 agencyLinks.add(link);
2668 }
2669 }
2670 }
2671 return agencyLinks;
2672 }
2673
2674
2675
2676
2677
2678
2679 @Override
2680 public String customizeAgencyLink(TravelDocument travelDocument, String agencyName, String link) {
2681 final boolean passTrip = getConfigurationService().getPropertyValueAsBoolean(TemKeyConstants.PASS_TRIP_ID_TO_AGENCY_SITES);
2682 if (!passTrip || StringUtils.isBlank(travelDocument.getTravelDocumentIdentifier())) {
2683 return link;
2684 }
2685
2686 if (travelDocument instanceof TravelAuthorizationDocument) {
2687 final boolean vendorPaymentAllowedBeforeFinal = getParameterService().getParameterValueAsBoolean(TravelAuthorizationDocument.class, TemConstants.TravelAuthorizationParameters.VENDOR_PAYMENT_ALLOWED_BEFORE_FINAL_APPROVAL_IND);
2688 if (!vendorPaymentAllowedBeforeFinal) {
2689 return link;
2690 }
2691 }
2692 final String linkWithTripId = link+"?tripId="+travelDocument.getTravelDocumentIdentifier();
2693 return linkWithTripId;
2694 }
2695
2696
2697
2698
2699
2700
2701 protected String prefixUrl(String url) {
2702 String prefixedUrl = url;
2703 if (!prefixedUrl.startsWith("http")) {
2704 prefixedUrl = "https://"+prefixedUrl;
2705 }
2706 return prefixedUrl;
2707 }
2708
2709
2710
2711
2712 @Override
2713 public boolean isInitiatorTraveler(TravelDocument travelDoc) {
2714 String initiatorId = travelDoc.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
2715 String travelerId = travelDoc.getTraveler().getPrincipalId();
2716 boolean is = travelerId != null && initiatorId.equals(travelerId);
2717 return is;
2718 }
2719
2720
2721
2722
2723 @Override
2724 public boolean requiresTravelerApproval(TravelAuthorizationDocument taDoc) {
2725
2726 boolean require = taDoc.requiresTravelAdvanceReviewRouting();
2727 require &= !taDoc.getTravelAdvance().getTravelAdvancePolicy();
2728
2729 return require;
2730 }
2731
2732
2733
2734
2735 @Override
2736 public boolean requiresTravelerApproval(TEMReimbursementDocument trDoc) {
2737 String travelerTypeCode = trDoc.getTraveler().getTravelerTypeCode();
2738 if (parameterService.getParameterValuesAsString(TemParameterConstants.TEM_DOCUMENT.class, TravelParameters.NON_EMPLOYEE_TRAVELER_TYPE_CODES).contains(travelerTypeCode)) {
2739 return false;
2740 }
2741
2742
2743 return !isInitiatorTraveler(trDoc);
2744 }
2745
2746
2747
2748
2749 public AccountsReceivableModuleService getAccountsReceivableModuleService() {
2750 if (accountsReceivableModuleService == null) {
2751 accountsReceivableModuleService = SpringContext.getBean(AccountsReceivableModuleService.class);
2752 }
2753 return accountsReceivableModuleService;
2754 }
2755
2756 public TravelAuthorizationService getTravelAuthorizationService() {
2757 return travelAuthorizationService;
2758 }
2759
2760 public void setTravelAuthorizationService(TravelAuthorizationService travelAuthorizationService) {
2761 this.travelAuthorizationService = travelAuthorizationService;
2762 }
2763
2764 public PerDiemService getPerDiemService() {
2765 return perDiemService;
2766 }
2767
2768 public void setPerDiemService(PerDiemService perDiemService) {
2769 this.perDiemService = perDiemService;
2770 }
2771
2772 public List<String> getGroupTravelerColumns() {
2773 return groupTravelerColumns;
2774 }
2775
2776 public void setGroupTravelerColumns(List<String> groupTravelerColumns) {
2777 this.groupTravelerColumns = groupTravelerColumns;
2778 }
2779
2780 public TravelExpenseService getTravelExpenseService() {
2781 return travelExpenseService;
2782 }
2783
2784 public void setTravelExpenseService(TravelExpenseService travelExpenseService) {
2785 this.travelExpenseService = travelExpenseService;
2786 }
2787
2788 public NoteService getNoteService() {
2789 return noteService;
2790 }
2791
2792 public void setNoteService(NoteService noteService) {
2793 this.noteService = noteService;
2794 }
2795
2796 public TravelService getTravelService() {
2797 return travelService;
2798 }
2799
2800 public void setTravelService(TravelService travelService) {
2801 this.travelService = travelService;
2802 }
2803
2804 public MileageRateService getMileageRateService() {
2805 return mileageRateService;
2806 }
2807
2808 public void setMileageRateService(MileageRateService mileageRateService) {
2809 this.mileageRateService = mileageRateService;
2810 }
2811
2812 public void setDocumentService(DocumentService documentService) {
2813 this.documentService = documentService;
2814 }
2815
2816 protected DocumentService getDocumentService() {
2817 return documentService;
2818 }
2819
2820 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
2821 this.dataDictionaryService = dataDictionaryService;
2822 }
2823
2824 protected DataDictionaryService getDataDictionaryService() {
2825 return dataDictionaryService;
2826 }
2827
2828 public void setDateTimeService(final DateTimeService dateTimeService) {
2829 this.dateTimeService = dateTimeService;
2830 }
2831
2832 protected DateTimeService getDateTimeService() {
2833 return dateTimeService;
2834 }
2835
2836 public void setTravelDocumentDao(final TravelDocumentDao travelDocumentDao) {
2837 this.travelDocumentDao = travelDocumentDao;
2838 }
2839
2840 protected TravelDocumentDao getTravelDocumentDao() {
2841 return travelDocumentDao;
2842 }
2843
2844 public void setBusinessObjectService(BusinessObjectService businessObjectService) {
2845 this.businessObjectService = businessObjectService;
2846 }
2847
2848 protected BusinessObjectService getBusinessObjectService() {
2849 return businessObjectService;
2850 }
2851
2852 public ParameterService getParameterService() {
2853 return parameterService;
2854 }
2855
2856 public void setParameterService(ParameterService parameterService) {
2857 this.parameterService = parameterService;
2858 }
2859
2860 public AccountingDocumentRelationshipService getAccountingDocumentRelationshipService() {
2861 return accountingDocumentRelationshipService;
2862 }
2863
2864 public void setAccountingDocumentRelationshipService(AccountingDocumentRelationshipService accountingDocumentRelationshipService) {
2865 this.accountingDocumentRelationshipService = accountingDocumentRelationshipService;
2866 }
2867
2868 public TemRoleService getTemRoleService() {
2869 return temRoleService;
2870 }
2871
2872 public void setTemRoleService(TemRoleService temRoleService) {
2873 this.temRoleService = temRoleService;
2874 }
2875
2876 protected ConfigurationService getConfigurationService() {
2877 return configurationService;
2878 }
2879
2880 public void setConfigurationService(ConfigurationService configurationService) {
2881 this.configurationService = configurationService;
2882 }
2883
2884 public StateService getStateService() {
2885 return stateService;
2886 }
2887
2888 public void setStateService(StateService stateService) {
2889 this.stateService = stateService;
2890 }
2891
2892
2893
2894
2895
2896 public UniversityDateService getUniversityDateService() {
2897 return universityDateService;
2898 }
2899
2900
2901
2902
2903
2904 public void setUniversityDateService(UniversityDateService universityDateService) {
2905 this.universityDateService = universityDateService;
2906 }
2907
2908 public List<String> getDefaultAcceptableFileExtensions() {
2909 return defaultAcceptableFileExtensions;
2910 }
2911
2912 public void setDefaultAcceptableFileExtensions(final List<String> defaultAcceptableFileExtensions) {
2913 this.defaultAcceptableFileExtensions = defaultAcceptableFileExtensions;
2914 }
2915
2916 public void setCsvRecordFactory(final CsvRecordFactory<GroupTravelerCsvRecord> recordFactory) {
2917 this.csvRecordFactory = recordFactory;
2918 }
2919
2920 public CsvRecordFactory<GroupTravelerCsvRecord> getCsvRecordFactory() {
2921 return this.csvRecordFactory;
2922 }
2923 }