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.Date;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.GregorianCalendar;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.TreeMap;
30
31 import org.apache.commons.collections.CollectionUtils;
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.commons.lang.time.DateUtils;
34 import org.apache.log4j.Logger;
35 import org.joda.time.DateTimeFieldType;
36 import org.joda.time.Interval;
37 import org.joda.time.LocalDateTime;
38 import org.kuali.hr.job.Job;
39 import org.kuali.hr.lm.LMConstants;
40 import org.kuali.hr.lm.accrual.AccrualCategoryRule;
41 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
42 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
43 import org.kuali.hr.lm.leaveblock.LeaveBlock;
44 import org.kuali.hr.lm.util.LeaveBlockAggregate;
45 import org.kuali.hr.time.assignment.Assignment;
46 import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
47 import org.kuali.hr.time.calendar.Calendar;
48 import org.kuali.hr.time.calendar.CalendarEntries;
49 import org.kuali.hr.time.earncode.EarnCode;
50 import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
51 import org.kuali.hr.time.flsa.FlsaDay;
52 import org.kuali.hr.time.flsa.FlsaWeek;
53 import org.kuali.hr.time.service.base.TkServiceLocator;
54 import org.kuali.hr.time.timeblock.TimeBlock;
55 import org.kuali.hr.time.timeblock.TimeHourDetail;
56 import org.kuali.hr.time.timesheet.TimesheetDocument;
57 import org.kuali.hr.time.timesummary.AssignmentColumn;
58 import org.kuali.hr.time.timesummary.AssignmentRow;
59 import org.kuali.hr.time.timesummary.EarnCodeSection;
60 import org.kuali.hr.time.timesummary.EarnGroupSection;
61 import org.kuali.hr.time.timesummary.TimeSummary;
62 import org.kuali.hr.time.util.TKUtils;
63 import org.kuali.hr.time.util.TkConstants;
64 import org.kuali.hr.time.util.TkTimeBlockAggregate;
65 import org.kuali.hr.time.workarea.WorkArea;
66
67 public class TimeSummaryServiceImpl implements TimeSummaryService {
68 private static final String OTHER_EARN_GROUP = "Other";
69 private static final Logger LOG = Logger.getLogger(TimeSummaryServiceImpl.class);
70
71 @Override
72 public TimeSummary getTimeSummary(TimesheetDocument timesheetDocument) {
73 TimeSummary timeSummary = new TimeSummary();
74
75 if(timesheetDocument == null || timesheetDocument.getTimeBlocks() == null) {
76 return timeSummary;
77 }
78
79 List<Boolean> dayArrangements = new ArrayList<Boolean>();
80
81 timeSummary.setSummaryHeader(getHeaderForSummary(timesheetDocument.getCalendarEntry(), dayArrangements));
82 TkTimeBlockAggregate tkTimeBlockAggregate = new TkTimeBlockAggregate(timesheetDocument.getTimeBlocks(), timesheetDocument.getCalendarEntry(), TkServiceLocator.getCalendarService().getCalendar(timesheetDocument.getCalendarEntry().getHrCalendarId()), true);
83
84 List<Assignment> timeAssignments = timesheetDocument.getAssignments();
85 List<String> tAssignmentKeys = new ArrayList<String>();
86 Set<String> regularEarnCodes = new HashSet<String>();
87 for(Assignment assign : timeAssignments) {
88 tAssignmentKeys.add(assign.getAssignmentKey());
89 regularEarnCodes.add(assign.getJob().getPayTypeObj().getRegEarnCode());
90 }
91
92 Date endDate = timesheetDocument.getCalendarEntry().getEndPeriodDate();
93 LocalDateTime tempLocalDate = timesheetDocument.getCalendarEntry().getEndLocalDateTime();
94
95 if(tempLocalDate.getHourOfDay() == 0 && tempLocalDate.getMinuteOfHour() == 0 && tempLocalDate.getMillisOfSecond() == 0) {
96 endDate = new java.sql.Date(tempLocalDate.minusDays(1).toDate().getTime());
97 }
98 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(timesheetDocument.getPrincipalId(),
99 timesheetDocument.getCalendarEntry().getBeginPeriodDate(), endDate, tAssignmentKeys);
100 LeaveBlockAggregate leaveBlockAggregate = new LeaveBlockAggregate(leaveBlocks, timesheetDocument.getCalendarEntry());
101 tkTimeBlockAggregate = TkTimeBlockAggregate.combineTimeAndLeaveAggregates(tkTimeBlockAggregate, leaveBlockAggregate);
102
103 timeSummary.setWorkedHours(getWorkedHours(tkTimeBlockAggregate, regularEarnCodes));
104
105 List<EarnGroupSection> earnGroupSections = getEarnGroupSections(tkTimeBlockAggregate, timeSummary.getSummaryHeader().size()+1,
106 dayArrangements, timesheetDocument.getAsOfDate(), timesheetDocument.getDocEndDate());
107 timeSummary.setSections(sortEarnGroupSections(earnGroupSections, regularEarnCodes));
108
109 try {
110 List<LeaveSummaryRow> maxedLeaveRows = getMaxedLeaveRows(timesheetDocument.getCalendarEntry(),timesheetDocument.getPrincipalId());
111 timeSummary.setMaxedLeaveRows(maxedLeaveRows);
112 } catch (Exception e) {
113
114 LOG.error("error retreiving maxed leave rows", e);
115 }
116
117 return timeSummary;
118 }
119
120 private List<EarnGroupSection> sortEarnGroupSections(List<EarnGroupSection> sections, Set<String> regularEarnCodes) {
121 List<EarnGroupSection> sortedList = new ArrayList<EarnGroupSection>();
122
123 Collections.sort(sections, new Comparator<EarnGroupSection>() {
124 @Override
125 public int compare(EarnGroupSection egs1, EarnGroupSection egs2) {
126 if (egs1 == null ^ egs2 == null) {
127 return egs1 == null ? -1 : 1;
128 }
129 if (egs1 == null && egs2 == null) {
130 return 0;
131 }
132
133 boolean isOther1 = StringUtils.equals(egs1.getEarnGroup(), OTHER_EARN_GROUP);
134 boolean isOther2 = StringUtils.equals(egs2.getEarnGroup(), OTHER_EARN_GROUP);
135 if (isOther1 ^ isOther2) {
136 return isOther1 ? 1 : -1;
137 }
138 if (isOther1 && isOther2) {
139 return 0;
140 }
141 return egs1.getEarnGroup().compareTo(egs2.getEarnGroup());
142 }
143 });
144
145 List<EarnGroupSection> copy = new ArrayList<EarnGroupSection>(sections);
146
147 for (EarnGroupSection egs : copy) {
148 if (!CollectionUtils.intersection(regularEarnCodes, egs.getEarnCodeToEarnCodeSectionMap().keySet()).isEmpty()) {
149 sortedList.add(egs);
150 sections.remove(egs);
151 }
152 }
153 sortedList.addAll(sections);
154 return sortedList;
155 }
156
157 private List<LeaveSummaryRow> getMaxedLeaveRows(
158 CalendarEntries calendarEntry, String principalId) throws Exception {
159 List<LeaveSummaryRow> maxedLeaveRows = new ArrayList<LeaveSummaryRow>();
160
161 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
162
163 Map<String,Set<LeaveBlock>> eligibilities = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry,principalId);
164 Set<LeaveBlock> onDemandTransfers = eligibilities.get(LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND);
165
166 Interval calendarEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
167
168
169
170
171 if(!onDemandTransfers.isEmpty()) {
172 for(LeaveBlock lb : onDemandTransfers) {
173 Date leaveDate = lb.getLeaveDate();
174 LeaveSummary summary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, new java.sql.Date(DateUtils.addDays(leaveDate, 1).getTime()));
175 LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCtgy(lb.getAccrualCategory());
176 if(row != null) {
177
178
179 if(calendarEntryInterval.contains(leaveDate.getTime())) {
180
181
182
183 row.setInfractingLeaveBlockId(lb.getAccrualCategoryRuleId());
184 AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
185
186 if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.TRANSFER))
187 row.setTransferable(true);
188 else if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.PAYOUT))
189 row.setPayoutable(true);
190
191 boolean exists = false;
192 for(LeaveSummaryRow maxedRow : maxedLeaveRows) {
193 if(StringUtils.equals(maxedRow.getAccrualCategoryId(),row.getAccrualCategoryId()))
194 exists = true;
195 }
196 if(!exists)
197 maxedLeaveRows.add(row);
198 }
199 }
200 }
201 }
202 }
203 return maxedLeaveRows;
204 }
205
206
207
208
209
210
211
212
213
214 public List<EarnGroupSection> getEarnGroupSections(TkTimeBlockAggregate tkTimeBlockAggregate, int numEntries, List<Boolean> dayArrangements, Date asOfDate , Date docEndDate){
215 List<EarnGroupSection> earnGroupSections = new ArrayList<EarnGroupSection>();
216
217 List<FlsaWeek> flsaWeeks = tkTimeBlockAggregate.getFlsaWeeks(TKUtils.getSystemDateTimeZone());
218 Map<String, EarnCodeSection> earnCodeToEarnCodeSection = new TreeMap<String, EarnCodeSection>();
219 Map<String, EarnGroupSection> earnGroupToEarnGroupSection = new HashMap<String, EarnGroupSection>();
220
221 int dayCount = 0;
222
223
224
225 List<FlsaWeek> trimmedFlsaWeeks = new ArrayList<FlsaWeek>();
226 for(FlsaWeek flsaWeek : flsaWeeks){
227 if(flsaWeek.getFlsaDays().size() > 0){
228 trimmedFlsaWeeks.add(flsaWeek);
229 }
230 }
231
232
233
234 for(FlsaWeek flsaWeek : trimmedFlsaWeeks){
235 int weekSize = 0;
236 List<FlsaDay> flsaDays = flsaWeek.getFlsaDays();
237 for(FlsaDay flsaDay : flsaDays){
238 Map<String, List<TimeBlock>> earnCodeToTimeBlocks = flsaDay.getEarnCodeToTimeBlocks();
239
240 for(List<TimeBlock> timeBlocks : earnCodeToTimeBlocks.values()){
241 for(TimeBlock timeBlock : timeBlocks){
242 for(TimeHourDetail thd : timeBlock.getTimeHourDetails()){
243 if(StringUtils.equals(TkConstants.LUNCH_EARN_CODE, thd.getEarnCode())){
244 continue;
245 }
246 EarnCodeSection earnCodeSection = earnCodeToEarnCodeSection.get(thd.getEarnCode());
247 if(earnCodeSection == null){
248 earnCodeSection = new EarnCodeSection();
249 earnCodeSection.setEarnCode(thd.getEarnCode());
250 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(thd.getEarnCode(), TKUtils.getTimelessDate(asOfDate));
251 earnCodeSection.setDescription(earnCodeObj != null ? earnCodeObj.getDescription() : null);
252 earnCodeSection.setIsAmountEarnCode(earnCodeObj != null ? TkConstants.EARN_CODE_AMOUNT.equalsIgnoreCase(earnCodeObj.getRecordMethod()) : false);
253 for(int i = 0;i<(numEntries-1);i++){
254 earnCodeSection.getTotals().add(BigDecimal.ZERO);
255 }
256
257 earnCodeToEarnCodeSection.put(thd.getEarnCode(), earnCodeSection);
258 }
259 String assignKey = timeBlock.getAssignmentKey();
260 AssignmentRow assignRow = earnCodeSection.getAssignKeyToAssignmentRowMap().get(assignKey);
261 if(assignRow == null){
262 assignRow = new AssignmentRow();
263 assignRow.setAssignmentKey(assignKey);
264 AssignmentDescriptionKey assignmentKey = TkServiceLocator.getAssignmentService().getAssignmentDescriptionKey(assignKey);
265 Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(asOfDate));
266
267 if(assignment == null) {
268 assignment = TkServiceLocator.getAssignmentService().getAssignment(timeBlock.getPrincipalId(), assignmentKey, TKUtils.getTimelessDate(docEndDate));
269 }
270
271 if(assignment != null){
272 if(assignment.getJob() == null){
273 Job aJob = TkServiceLocator.getJobService().getJob(assignment.getPrincipalId(),assignment.getJobNumber(),TKUtils.getTimelessDate(assignment.getEffectiveDate()));
274 assignment.setJob(aJob);
275 }
276 if(assignment.getWorkAreaObj() == null){
277 WorkArea aWorkArea = TkServiceLocator.getWorkAreaService().getWorkArea(assignment.getWorkArea(), TKUtils.getTimelessDate(assignment.getEffectiveDate()));
278 assignment.setWorkAreaObj(aWorkArea);
279 }
280 assignRow.setDescr(assignment.getAssignmentDescription());
281 }
282 assignRow.setEarnCodeSection(earnCodeSection);
283 for (int i = 0; i < numEntries - 1; i++) {
284 assignRow.addAssignmentColumn(new AssignmentColumn());
285 }
286
287 earnCodeSection.addAssignmentRow(assignRow);
288 }
289 assignRow.addToTotal(dayCount, thd.getHours());
290 assignRow.addToAmount(dayCount, thd.getAmount());
291 }
292 }
293 }
294 dayCount++;
295 weekSize++;
296 }
297
298 for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){
299 earnCodeSection.addWeeklyTotal(dayCount, weekSize);
300 }
301 weekSize = 0;
302
303 dayCount++;
304 }
305
306 dayCount = 0;
307
308 for(EarnCodeSection earnCodeSection : earnCodeToEarnCodeSection.values()){
309 String earnCode = earnCodeSection.getEarnCode();
310 EarnCodeGroup earnGroupObj = TkServiceLocator.getEarnCodeGroupService().getEarnCodeGroupSummaryForEarnCode(earnCode, TKUtils.getTimelessDate(asOfDate));
311 String earnGroup = null;
312 if(earnGroupObj == null){
313 earnGroup = OTHER_EARN_GROUP;
314 } else{
315 earnGroup = earnGroupObj.getDescr();
316 }
317
318 EarnGroupSection earnGroupSection = earnGroupToEarnGroupSection.get(earnGroup);
319 if(earnGroupSection == null){
320 earnGroupSection = new EarnGroupSection();
321 earnGroupSection.setEarnGroup(earnGroup);
322 for(int i =0;i<(numEntries-1);i++){
323 earnGroupSection.getTotals().add(BigDecimal.ZERO);
324 }
325 earnGroupToEarnGroupSection.put(earnGroup, earnGroupSection);
326 }
327 earnGroupSection.addEarnCodeSection(earnCodeSection, dayArrangements);
328
329 }
330 for(EarnGroupSection earnGroupSection : earnGroupToEarnGroupSection.values()){
331 earnGroupSections.add(earnGroupSection);
332 }
333 return earnGroupSections;
334 }
335
336
337
338
339
340
341 protected List<String> getSummaryHeader(CalendarEntries payCalEntry){
342 List<String> summaryHeader = new ArrayList<String>();
343 int dayCount = 0;
344 java.util.Date beginDateTime = payCalEntry.getBeginPeriodDateTime();
345 java.util.Date endDateTime = payCalEntry.getEndPeriodDateTime();
346 boolean virtualDays = false;
347 LocalDateTime endDate = payCalEntry.getEndLocalDateTime();
348
349 if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
350 endDate.get(DateTimeFieldType.secondOfMinute()) != 0){
351 virtualDays = true;
352 }
353
354 java.util.Date currDateTime = beginDateTime;
355 java.util.Calendar cal = GregorianCalendar.getInstance();
356
357 while(currDateTime.before(endDateTime)){
358 LocalDateTime currDate = new LocalDateTime(currDateTime);
359 summaryHeader.add(makeHeaderDiplayString(currDate, virtualDays));
360
361 dayCount++;
362 if((dayCount % 7) == 0){
363 summaryHeader.add("Week "+ ((dayCount / 7)));
364 }
365 cal.setTime(currDateTime);
366 cal.add(java.util.Calendar.HOUR, 24);
367 currDateTime = cal.getTime();
368 }
369
370 summaryHeader.add("Period Total");
371 return summaryHeader;
372 }
373
374
375
376
377
378
379
380
381
382
383 private List<BigDecimal> getWorkedHours(TkTimeBlockAggregate aggregate, Set<String> regularEarnCodes) {
384 List<BigDecimal> hours = new ArrayList<BigDecimal>();
385 BigDecimal periodTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO;
386 List<FlsaWeek> weekList = aggregate.getFlsaWeeks(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback());
387
388 for (FlsaWeek week : weekList) {
389 BigDecimal weeklyTotal = TkConstants.BIG_DECIMAL_SCALED_ZERO;
390 for (FlsaDay day : week.getFlsaDays()) {
391 BigDecimal totalForDay = TkConstants.BIG_DECIMAL_SCALED_ZERO;
392 for (TimeBlock block : day.getAppliedTimeBlocks()) {
393 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(block.getEarnCode(), block.getEndDate());
394 if (ec != null
395 && (ec.getOvtEarnCode()
396 || regularEarnCodes.contains(ec.getEarnCode()))) {
397 totalForDay = totalForDay.add(block.getHours(), TkConstants.MATH_CONTEXT);
398 weeklyTotal = weeklyTotal.add(block.getHours(), TkConstants.MATH_CONTEXT);
399 periodTotal = periodTotal.add(block.getHours(), TkConstants.MATH_CONTEXT);
400 }
401 }
402 hours.add(totalForDay);
403 }
404 hours.add(weeklyTotal);
405 }
406 hours.add(periodTotal);
407
408 return hours;
409 }
410
411
412
413
414
415
416
417
418
419
420
421 @Override
422 public List<String> getHeaderForSummary(CalendarEntries cal, List<Boolean> dayArrangements) {
423 List<String> header = new ArrayList<String>();
424 if (cal == null) {
425 return Collections.emptyList();
426 }
427
428 int flsaBeginDay = this.getPayCalendarForEntry(cal).getFlsaBeginDayConstant();
429 boolean virtualDays = false;
430 LocalDateTime startDate = cal.getBeginLocalDateTime();
431 LocalDateTime endDate = cal.getEndLocalDateTime();
432
433
434
435
436 if (endDate.get(DateTimeFieldType.hourOfDay()) != 0 || endDate.get(DateTimeFieldType.minuteOfHour()) != 0 ||
437 endDate.get(DateTimeFieldType.secondOfMinute()) != 0)
438 {
439 endDate = endDate.plusDays(1);
440 virtualDays = true;
441 }
442
443 boolean afterFirstDay = false;
444 int week = 1;
445 for (LocalDateTime currentDate = startDate; currentDate.compareTo(endDate) < 0; currentDate = currentDate.plusDays(1)) {
446
447 if (currentDate.getDayOfWeek() == flsaBeginDay && afterFirstDay) {
448 header.add("Week " + week);
449 dayArrangements.add(false);
450 week++;
451 }
452
453 header.add(makeHeaderDiplayString(currentDate, virtualDays));
454 dayArrangements.add(true);
455
456
457 afterFirstDay = true;
458 }
459
460
461
462
463 if (!header.get(header.size()-1).startsWith("Week")) {
464 dayArrangements.add(false);
465 header.add("Week " + week);
466 }
467
468
469 header.add("Period Total");
470 dayArrangements.add(false);
471 return header;
472 }
473
474
475
476
477
478
479
480 private String makeHeaderDiplayString(LocalDateTime currentDate, boolean virtualDays) {
481 StringBuilder display = new StringBuilder();
482
483 display.append(currentDate.toString("E"));
484 if (virtualDays) {
485 LocalDateTime nextDate = currentDate.plusDays(1);
486 display.append(" - ");
487 display.append(nextDate.toString("E"));
488 }
489
490 display.append("<br />");
491
492 display.append(currentDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
493 if (virtualDays) {
494 LocalDateTime nextDate = currentDate.plusDays(1);
495 display.append(" - ");
496 display.append(nextDate.toString(TkConstants.DT_ABBREV_DATE_FORMAT));
497 }
498
499 return display.toString();
500 }
501
502
503
504
505
506 private Calendar getPayCalendarForEntry(CalendarEntries calEntry) {
507 Calendar cal = null;
508
509 if (calEntry != null) {
510 cal = TkServiceLocator.getCalendarService().getCalendar(calEntry.getHrCalendarId());
511 }
512
513 return cal;
514 }
515
516 }