1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.hr.time.timesummary.service;
17
18 import java.math.BigDecimal;
19 import java.sql.Timestamp;
20 import java.util.*;
21
22 import org.apache.commons.collections.CollectionUtils;
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.commons.lang.time.DateUtils;
25 import org.apache.log4j.Logger;
26 import org.joda.time.DateTime;
27 import org.joda.time.DateTimeFieldType;
28 import org.joda.time.Interval;
29 import org.joda.time.LocalDateTime;
30 import org.kuali.hr.job.Job;
31 import org.kuali.hr.lm.LMConstants;
32 import org.kuali.hr.lm.accrual.AccrualCategoryRule;
33 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
34 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
35 import org.kuali.hr.lm.leaveblock.LeaveBlock;
36 import org.kuali.hr.lm.util.LeaveBlockAggregate;
37 import org.kuali.hr.time.assignment.Assignment;
38 import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
39 import org.kuali.hr.time.calendar.Calendar;
40 import org.kuali.hr.time.calendar.CalendarEntries;
41 import org.kuali.hr.time.earncode.EarnCode;
42 import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
43 import org.kuali.hr.time.flsa.FlsaDay;
44 import org.kuali.hr.time.flsa.FlsaWeek;
45 import org.kuali.hr.time.service.base.TkServiceLocator;
46 import org.kuali.hr.time.timeblock.TimeBlock;
47 import org.kuali.hr.time.timeblock.TimeHourDetail;
48 import org.kuali.hr.time.timesheet.TimesheetDocument;
49 import org.kuali.hr.time.timesummary.AssignmentColumn;
50 import org.kuali.hr.time.timesummary.AssignmentRow;
51 import org.kuali.hr.time.timesummary.EarnCodeSection;
52 import org.kuali.hr.time.timesummary.EarnGroupSection;
53 import org.kuali.hr.time.timesummary.TimeSummary;
54 import org.kuali.hr.time.util.TKUtils;
55 import org.kuali.hr.time.util.TkConstants;
56 import org.kuali.hr.time.util.TkTimeBlockAggregate;
57 import org.kuali.hr.time.workarea.WorkArea;
58
59 public class TimeSummaryServiceImpl implements TimeSummaryService {
60 private static final String OTHER_EARN_GROUP = "Other";
61 private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class);
62
63 @Override
64 public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) {
65 TimeSummary timeSummary = new TimeSummary();
66
67 if(timesheetDocument == null || timesheetDocument.getTimeBlocks() == null) {
68 return timeSummary;
69 }
70
71 List<Boolean> dayArrangements = new ArrayList<Boolean>();
72
73 timeSummary.setSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), dayArrangements));
74 TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), TkServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
75
76 List<Assignment> timeAssignments = timesheetDocument.getAssignments();
77 List<String> tAssignmentKeys = new ArrayList<String>();
78 Set<String> regularEarnCodes = new HashSet<String>();
79 for(Assignment assign : timeAssignments) {
80 tAssignmentKeys.add(assign.getAssignmentKey());
81 regularEarnCodes.add(assign.getJob().getPayTypeObj().getRegEarnCode());
82 }
83 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
84 timesheetDocument.getCalendarEntry().getBeginPeriodDate(), timesheetDocument.getCalendarEntry().getEndPeriodDate(), tAssignmentKeys);
85 LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, timesheetDocument.getCalendarEntry());
86 tkTimeBlockAggregate = TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
87
88 timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate, regularEarnCodes));
89
90 List<EarnGroupSection> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getSummaryHeader().size()+1,
91 dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate());
92 timeSummary.setSections(sortEarnGroupSections(earnGroupSections, regularEarnCodes));
93
94 try {
95 List<LeaveSummaryRow> maxedLeaveRows = getMaxedLeaveRows(timesheetDocument.getCalendarEntry(),timesheetDocument.getPrincipalId());
96 timeSummary.setMaxedLeaveRows(maxedLeaveRows);
97 } catch (Exception e) {
98
99 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
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
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
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
154
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
163
164 if(calendarEntryInterval.contains(leaveDate.getTime())) {
165
166
167
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
193
194
195
196
197
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
208
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
217
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
251 if(assignment == null) {
252 assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(docEndDate));
253 }
254
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
282 for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){
283 earnCodeSection.addWeeklyTotal(dayCount, weekSize);
284 }
285 weekSize = 0;
286
287 dayCount++;
288 }
289
290 dayCount = 0;
291
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
322
323
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
360
361
362
363
364
365
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
396
397
398
399
400
401
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
410 int flsaBeginDay = this.getPayCalendarForEntry(cal).getFlsaBeginDayConstant();
411 boolean virtualDays = false;
412 LocalDateTime startDate = cal.getBeginLocalDateTime();
413 LocalDateTime endDate = cal.getEndLocalDateTime();
414
415
416
417
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
443
444
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
458
459
460
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
486
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 }