001 /**
002 * Copyright 2004-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.hr.time.timesummary.service;
017
018 import java.math.BigDecimal;
019 import java.sql.Timestamp;
020 import java.util.*;
021
022 import org.apache.commons.collections.CollectionUtils;
023 import org.apache.commons.lang.StringUtils;
024 import org.apache.commons.lang.time.DateUtils;
025 import org.apache.log4j.Logger;
026 import org.joda.time.DateTime;
027 import org.joda.time.DateTimeFieldType;
028 import org.joda.time.Interval;
029 import org.joda.time.LocalDateTime;
030 import org.kuali.hr.job.Job;
031 import org.kuali.hr.lm.LMConstants;
032 import org.kuali.hr.lm.accrual.AccrualCategoryRule;
033 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
034 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
035 import org.kuali.hr.lm.leaveblock.LeaveBlock;
036 import org.kuali.hr.lm.util.LeaveBlockAggregate;
037 import org.kuali.hr.time.assignment.Assignment;
038 import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
039 import org.kuali.hr.time.calendar.Calendar;
040 import org.kuali.hr.time.calendar.CalendarEntries;
041 import org.kuali.hr.time.earncode.EarnCode;
042 import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
043 import org.kuali.hr.time.flsa.FlsaDay;
044 import org.kuali.hr.time.flsa.FlsaWeek;
045 import org.kuali.hr.time.service.base.TkServiceLocator;
046 import org.kuali.hr.time.timeblock.TimeBlock;
047 import org.kuali.hr.time.timeblock.TimeHourDetail;
048 import org.kuali.hr.time.timesheet.TimesheetDocument;
049 import org.kuali.hr.time.timesummary.AssignmentColumn;
050 import org.kuali.hr.time.timesummary.AssignmentRow;
051 import org.kuali.hr.time.timesummary.EarnCodeSection;
052 import org.kuali.hr.time.timesummary.EarnGroupSection;
053 import org.kuali.hr.time.timesummary.TimeSummary;
054 import org.kuali.hr.time.util.TKUtils;
055 import org.kuali.hr.time.util.TkConstants;
056 import org.kuali.hr.time.util.TkTimeBlockAggregate;
057 import org.kuali.hr.time.workarea.WorkArea;
058
059 public class TimeSummaryServiceImpl implements TimeSummaryService {
060 private static final String OTHER_EARN_GROUP = "Other";
061 private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class);
062
063 @Override
064 public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) {
065 TimeSummary timeSummary = new TimeSummary();
066
067 if(timesheetDocument == null || timesheetDocument.getTimeBlocks() == null) {
068 return timeSummary;
069 }
070
071 List<Boolean> dayArrangements = new ArrayList<Boolean>();
072
073 timeSummary.setSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), dayArrangements));
074 TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), TkServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
075
076 List<Assignment> timeAssignments = timesheetDocument.getAssignments();
077 List<String> tAssignmentKeys = new ArrayList<String>();
078 Set<String> regularEarnCodes = new HashSet<String>();
079 for(Assignment assign : timeAssignments) {
080 tAssignmentKeys.add(assign.getAssignmentKey());
081 regularEarnCodes.add(assign.getJob().getPayTypeObj().getRegEarnCode());
082 }
083 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
084 timesheetDocument.getCalendarEntry().getBeginPeriodDate(), timesheetDocument.getCalendarEntry().getEndPeriodDate(), tAssignmentKeys);
085 LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, timesheetDocument.getCalendarEntry());
086 tkTimeBlockAggregate = TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
087
088 timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate, regularEarnCodes));
089
090 List<EarnGroupSection> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getSummaryHeader().size()+1,
091 dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate());
092 timeSummary.setSections(sortEarnGroupSections(earnGroupSections, regularEarnCodes));
093
094 try {
095 List<LeaveSummaryRow> maxedLeaveRows = getMaxedLeaveRows(timesheetDocument.getCalendarEntry(),timesheetDocument.getPrincipalId());
096 timeSummary.setMaxedLeaveRows(maxedLeaveRows);
097 } catch (Exception e) {
098 // TODO Auto-generated catch block
099 LOG.error("error retreiving maxed leave rows", e);
100 }
101
102 return timeSummary;
103 }
104
105 private List<EarnGroupSection> sortEarnGroupSections(List<EarnGroupSection> sections, Set<String> regularEarnCodes) {
106 List<EarnGroupSection> sortedList = new ArrayList<EarnGroupSection>();
107 //first sort by alpha
108 Collections.sort(sections, new Comparator<EarnGroupSection>() {
109 @Override
110 public int compare(EarnGroupSection egs1, EarnGroupSection egs2) {
111 if (egs1 == null ^ egs2 == null) {
112 return egs1 == null ? -1 : 1;
113 }
114 if (egs1 == null && egs2 == null) {
115 return 0;
116 }
117 //'other' earn group needs to be last.
118 boolean isOther1 = StringUtils.equals(egs1.getEarnGroup(), OTHER_EARN_GROUP);
119 boolean isOther2 = StringUtils.equals(egs2.getEarnGroup(), OTHER_EARN_GROUP);
120 if (isOther1 ^ isOther2) {
121 return isOther1 ? 1 : -1;
122 }
123 if (isOther1 && isOther2) {
124 return 0;
125 }
126 return egs1.getEarnGroup().compareTo(egs2.getEarnGroup());
127 }
128 });
129
130 List<EarnGroupSection> copy = new ArrayList<EarnGroupSection>(sections);
131 //loop through in reverse
132 for (EarnGroupSection egs : copy) {
133 if (!CollectionUtils.intersection(regularEarnCodes, egs.getEarnCodeToEarnCodeSectionMap().keySet()).isEmpty()) {
134 sortedList.add(egs);
135 sections.remove(egs);
136 }
137 }
138 sortedList.addAll(sections);
139 return sortedList;
140 }
141
142 private List<LeaveSummaryRow> getMaxedLeaveRows(
143 CalendarEntries calendarEntry, String principalId) throws Exception {
144 List<LeaveSummaryRow> maxedLeaveRows = new ArrayList<LeaveSummaryRow>();
145
146 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
147
148 Map<String,Set<LeaveBlock>> eligibilities = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry,principalId);
149 Set<LeaveBlock> onDemandTransfers = eligibilities.get(LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND);
150
151 Interval calendarEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
152
153 //use the current date if on the current calendar? yes -> no warning given until accrual is reached. If accrual occurs on last day of period or last day of service interval
154 //change, no warning given to the employee of balance limits being exceeded except on or after that day.
155
156 if(!onDemandTransfers.isEmpty()) {
157 for(LeaveBlock lb : onDemandTransfers) {
158 Date leaveDate = lb.getLeaveDate();
159 LeaveSummary summary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, new java.sql.Date(DateUtils.addDays(leaveDate, 1).getTime()));
160 LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCtgy(lb.getAccrualCategory());
161 if(row != null) {
162 //AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(row.getAccrualCategoryId());
163 //AccrualCategoryRule currentRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, asOfDate, pha.getServiceDate());
164 if(calendarEntryInterval.contains(leaveDate.getTime())) {
165 //do not allow the on-demand max balance action if the rule the action occurs under is no longer in effect,
166 //or if the infraction did not occur within this interval. ( if it occurred during the previous interval,
167 //the employee will have the option to take action in that interval up to & including the end date of that interval. )
168 row.setInfractingLeaveBlockId(lb.getAccrualCategoryRuleId());
169 AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
170
171 if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.TRANSFER))
172 row.setTransferable(true);
173 else if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.PAYOUT))
174 row.setPayoutable(true);
175
176 boolean exists = false;
177 for(LeaveSummaryRow maxedRow : maxedLeaveRows) {
178 if(StringUtils.equals(maxedRow.getAccrualCategoryId(),row.getAccrualCategoryId()))
179 exists = true;
180 }
181 if(!exists)
182 maxedLeaveRows.add(row);
183 }
184 }
185 }
186 }
187 }
188 return maxedLeaveRows;
189 }
190
191 /**
192 * Aggregates timeblocks into the appropriate earngroup-> earncode -> assignment rows
193 * @param tkTimeBlockAggregate
194 * @param numEntries
195 * @param dayArrangements
196 * @param asOfDate
197 * @return
198 */
199 public List<EarnGroupSection> getEarnGroupSections(TkTimeBlockAggregate tkTimeBlockAggregate, int numEntries, List<Boolean> dayArrangements, Date asOfDate , Date docEndDate){
200 List<EarnGroupSection> earnGroupSections = new ArrayList<EarnGroupSection>();
201 List<FlsaWeek> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback());
202 Map<String, EarnCodeSection> earnCodeToEarnCodeSection = new TreeMap<String, EarnCodeSection>();
203 Map<String, EarnGroupSection> earnGroupToEarnGroupSection = new HashMap<String, EarnGroupSection>();
204
205 int dayCount = 0;
206
207 //TODO remove this and correct the aggregate .. not sure what the down stream changes are
208 //so leaving this for initial release
209 List<FlsaWeek> trimmedFlsaWeeks = new ArrayList<FlsaWeek>();
210 for(FlsaWeek flsaWeek : flsaWeeks){
211 if(flsaWeek.getFlsaDays().size() > 0){
212 trimmedFlsaWeeks.add(flsaWeek);
213 }
214 }
215
216 //For every flsa week and day aggegate each time hour detail
217 // buckets it by earn code section first
218 for(FlsaWeek flsaWeek : trimmedFlsaWeeks){
219 int weekSize = 0;
220 List<FlsaDay> flsaDays = flsaWeek.getFlsaDays();
221 for(FlsaDay flsaDay : flsaDays){
222 Map<String, List<TimeBlock>> earnCodeToTimeBlocks = flsaDay.getEarnCodeToTimeBlocks();
223
224 for(List<TimeBlock> timeBlocks : earnCodeToTimeBlocks.values()){
225 for(TimeBlock timeBlock : timeBlocks){
226 for(TimeHourDetail thd : timeBlock.getTimeHourDetails()){
227 if(StringUtils.equals(TkConstants.LUNCH_EARN_CODE, thd.getEarnCode())){
228 continue;
229 }
230 EarnCodeSection earnCodeSection = earnCodeToEarnCodeSection.get(thd.getEarnCode());
231 if(earnCodeSection == null){
232 earnCodeSection = new EarnCodeSection();
233 earnCodeSection.setEarnCode(thd.getEarnCode());
234 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(thd.getEarnCode(), TKUtils.getTimelessDate(asOfDate));
235 earnCodeSection.setDescription(earnCodeObj != null ? earnCodeObj.getDescription() : null);
236 earnCodeSection.setIsAmountEarnCode(earnCodeObj != null ? TkConstants.EARN_CODE_AMOUNT.equalsIgnoreCase(earnCodeObj.getRecordMethod()) : false);
237 for(int i = 0;i<(numEntries-1);i++){
238 earnCodeSection.getTotals().add(BigDecimal.ZERO);
239 }
240
241 earnCodeToEarnCodeSection.put(thd.getEarnCode(), earnCodeSection);
242 }
243 String assignKey = timeBlock.getAssignmentKey();
244 AssignmentRow assignRow = earnCodeSection.getAssignKeyToAssignmentRowMap().get(assignKey);
245 if(assignRow == null){
246 assignRow = new AssignmentRow();
247 assignRow.setAssignmentKey(assignKey);
248 AssignmentDescriptionKey assignmentKey = TkServiceLocator.getAssignmentService().getAssignmentDescriptionKey(assignKey);
249 Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(asOfDate));
250 // some assignment may not be effective at the beginning of the pay period, use the end date of the period to find it
251 if(assignment == null) {
252 assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(docEndDate));
253 }
254 //TODO push this up to the assignment fetch/fully populated instead of like this
255 if(assignment != null){
256 if(assignment.getJob() == null){
257 Job aJob = TkServiceLocator.getJobService().getJob(assignment.getPrincipalId(),assignment.getJobNumber(),TKUtils.getTimelessDate(assignment.getEffectiveDate()));
258 assignment.setJob(aJob);
259 }
260 if(assignment.getWorkAreaObj() == null){
261 WorkArea aWorkArea = TkServiceLocator.getWorkAreaService().getWorkArea(assignment.getWorkArea(), TKUtils.getTimelessDate(assignment.getEffectiveDate()));
262 assignment.setWorkAreaObj(aWorkArea);
263 }
264 assignRow.setDescr(assignment.getAssignmentDescription());
265 }
266 assignRow.setEarnCodeSection(earnCodeSection);
267 for (int i = 0; i < numEntries - 1; i++) {
268 assignRow.addAssignmentColumn(new AssignmentColumn());
269 }
270
271 earnCodeSection.addAssignmentRow(assignRow);
272 }
273 assignRow.addToTotal(dayCount, thd.getHours());
274 assignRow.addToAmount(dayCount, thd.getAmount());
275 }
276 }
277 }
278 dayCount++;
279 weekSize++;
280 }
281 //end of flsa week accumulate weekly totals
282 for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){
283 earnCodeSection.addWeeklyTotal(dayCount, weekSize);
284 }
285 weekSize = 0;
286
287 dayCount++;
288 }
289
290 dayCount = 0;
291 //now create all teh earn group sections and aggregate accordingly
292 for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){
293 String earnCode = earnCodeSection.getEarnCode();
294 EarnCodeGroup earnGroupObj = TkServiceLocator.getEarnCodeGroupService().getEarnCodeGroupSummaryForEarnCode(earnCode, TKUtils.getTimelessDate(asOfDate));
295 String earnGroup = null;
296 if(earnGroupObj == null){
297 earnGroup = OTHER_EARN_GROUP;
298 } else{
299 earnGroup = earnGroupObj.getDescr();
300 }
301
302 EarnGroupSection earnGroupSection = earnGroupToEarnGroupSection.get(earnGroup);
303 if(earnGroupSection == null){
304 earnGroupSection = new EarnGroupSection();
305 earnGroupSection.setEarnGroup(earnGroup);
306 for(int i =0;i<(numEntries-1);i++){
307 earnGroupSection.getTotals().add(BigDecimal.ZERO);
308 }
309 earnGroupToEarnGroupSection.put(earnGroup, earnGroupSection);
310 }
311 earnGroupSection.addEarnCodeSection(earnCodeSection, dayArrangements);
312
313 }
314 for(EarnGroupSection earnGroupSection : earnGroupToEarnGroupSection.values()){
315 earnGroupSections.add(earnGroupSection);
316 }
317 return earnGroupSections;
318 }
319
320 /**
321 * Generate a list of string describing this pay calendar entry for the summary
322 * @param payCalEntry
323 * @return
324 */
325 protected List<String> getSummaryHeader(CalendarEntries payCalEntry){
326 List<String> summaryHeader = new ArrayList<String>();
327 int dayCount = 0;
328 Date beginDateTime = payCalEntry.getBeginPeriodDateTime();
329 Date endDateTime = payCalEntry.getEndPeriodDateTime();
330 boolean virtualDays = false;
331 LocalDateTime endDate = payCalEntry.getEndLocalDateTime();
332
333 if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
334 endDate.get(DateTimeFieldType.secondOfMinute()) != 0){
335 virtualDays = true;
336 }
337
338 Date currDateTime = beginDateTime;
339 java.util.Calendar cal = GregorianCalendar.getInstance();
340
341 while(currDateTime.before(endDateTime)){
342 LocalDateTime currDate = new LocalDateTime(currDateTime);
343 summaryHeader.add(makeHeaderDiplayString(currDate, virtualDays));
344
345 dayCount++;
346 if((dayCount % 7) == 0){
347 summaryHeader.add("Week "+ ((dayCount / 7)));
348 }
349 cal.setTime(currDateTime);
350 cal.add(java.util.Calendar.HOUR, 24);
351 currDateTime = cal.getTime();
352 }
353
354 summaryHeader.add("Period Total");
355 return summaryHeader;
356 }
357
358 /**
359 * Provides the number of hours worked for the pay period indicated in the
360 * aggregate.
361 *
362 * @param aggregate The aggregate we are summing
363 *
364 * @return A list of BigDecimals containing the number of hours worked.
365 * This list will line up with the header.
366 */
367 private List<BigDecimal> getWorkedHours(TkTimeBlockAggregate aggregate, Set<String> regularEarnCodes) {
368 List<BigDecimal> hours = new ArrayList<BigDecimal>();
369 BigDecimal periodTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO;
370 for (FlsaWeek week : aggregate.getFlsaWeeks(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback())) {
371 BigDecimal weeklyTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO;
372 for (FlsaDay day : week.getFlsaDays()) {
373 BigDecimal totalForDay = TkConstants.BIG_DECIMAL_SCALED_ZERO;
374 for (TimeBlock block : day.getAppliedTimeBlocks()) {
375 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(block.getEarnCode(), block.getEndDate());
376 if (ec != null
377 && (ec.getOvtEarnCode()
378 || regularEarnCodes.contains(ec.getEarnCode()))) {
379 totalForDay = totalForDay.add(block.getHours(), TkConstants.MATH_CONTEXT);
380 weeklyTotal = weeklyTotal.add(block.getHours(), TkConstants.MATH_CONTEXT);
381 periodTotal = periodTotal.add(block.getHours(), TkConstants.MATH_CONTEXT);
382 }
383 }
384 hours.add(totalForDay);
385 }
386 hours.add(weeklyTotal);
387 }
388 hours.add(periodTotal);
389
390 return hours;
391 }
392
393
394 /**
395 * Handles the generation of the display header for the time summary.
396 *
397 * @param cal The PayCalendarEntries object we are using to derive information.
398 * @param dayArrangements Container passed in to store the position of week / period aggregate sums
399 *
400 * @return An in-order string of days for this period that properly accounts
401 * for FLSA week boundaries in the pay period.
402 */
403 @Override
404 public List<String> getHeaderForSummary(CalendarEntries cal, List<Boolean> dayArrangements) {
405 List<String> header = new ArrayList<String>();
406 if (cal == null) {
407 return Collections.emptyList();
408 }
409 // Maps directly to joda time day of week constants.
410 int flsaBeginDay = this.getPayCalendarForEntry(cal).getFlsaBeginDayConstant();
411 boolean virtualDays = false;
412 LocalDateTime startDate = cal.getBeginLocalDateTime();
413 LocalDateTime endDate = cal.getEndLocalDateTime();
414
415 // Increment end date if we are on a virtual day calendar, so that the
416 // for loop can account for having the proper amount of days on the
417 // summary calendar.
418 if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
419 endDate.get(DateTimeFieldType.secondOfMinute()) != 0)
420 {
421 endDate = endDate.plusDays(1);
422 virtualDays = true;
423 }
424
425 boolean afterFirstDay = false;
426 int week = 1;
427 for (LocalDateTime currentDate = startDate; currentDate.compareTo(endDate) < 0; currentDate = currentDate.plusDays(1)) {
428
429 if (currentDate.getDayOfWeek() == flsaBeginDay && afterFirstDay) {
430 header.add("Week " + week);
431 dayArrangements.add(false);
432 week++;
433 }
434
435 header.add(makeHeaderDiplayString(currentDate, virtualDays));
436 dayArrangements.add(true);
437
438
439 afterFirstDay = true;
440 }
441
442 // We may have a very small final "week" on this pay period. For now
443 // we will mark it as a week, and if someone doesn't like it, it can
444 // be removed.
445 if (!header.get(header.size()-1).startsWith("Week")) {
446 dayArrangements.add(false);
447 header.add("Week " + week);
448 }
449
450
451 header.add("Period Total");
452 dayArrangements.add(false);
453 return header;
454 }
455
456 /**
457 * Helper function to generate display text for the summary header.
458 * @param currentDate The date we are generating for.
459 * @param virtualDays Whether or not virtual days apply.
460 * @return A string appropriate for UI display.
461 */
462 private String makeHeaderDiplayString(LocalDateTime currentDate, boolean virtualDays) {
463 StringBuilder display = new StringBuilder();
464
465 display.append(currentDate.toString("E"));
466 if (virtualDays) {
467 LocalDateTime nextDate = currentDate.plusDays(1);
468 display.append(" - ");
469 display.append(nextDate.toString("E"));
470 }
471
472 display.append("<br />");
473
474 display.append(currentDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
475 if (virtualDays) {
476 LocalDateTime nextDate = currentDate.plusDays(1);
477 display.append(" - ");
478 display.append(nextDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
479 }
480
481 return display.toString();
482 }
483
484 /**
485 * @param calEntry Calendar entry we are using for lookup.
486 * @return The PayCalendar that owns the provided entry.
487 */
488 private Calendar getPayCalendarForEntry(CalendarEntries calEntry) {
489 Calendar cal = null;
490
491 if (calEntry != null) {
492 cal = TkServiceLocator.getCalendarService().getCalendar(calEntry.getHrCalendarId());
493 }
494
495 return cal;
496 }
497
498 }