1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.kpme.tklm.time.rules.overtime.daily.service;
17
18 import java.math.BigDecimal;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.HashMap;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27
28 import org.apache.log4j.Logger;
29 import org.joda.time.LocalDate;
30 import org.kuali.kpme.core.api.assignment.Assignment;
31 import org.kuali.kpme.core.api.department.Department;
32 import org.kuali.kpme.core.api.job.JobContract;
33 import org.kuali.kpme.core.api.namespace.KPMENamespace;
34 import org.kuali.kpme.core.api.permission.KPMEPermissionTemplate;
35 import org.kuali.kpme.core.role.KPMERoleMemberAttribute;
36 import org.kuali.kpme.core.service.HrServiceLocator;
37 import org.kuali.kpme.core.util.HrConstants;
38 import org.kuali.kpme.core.util.TKUtils;
39 import org.kuali.kpme.tklm.api.time.timeblock.TimeBlock;
40 import org.kuali.kpme.tklm.api.time.timeblock.TimeBlockContract;
41 import org.kuali.kpme.tklm.time.rules.overtime.daily.DailyOvertimeRule;
42 import org.kuali.kpme.tklm.time.rules.overtime.daily.dao.DailyOvertimeRuleDao;
43 import org.kuali.kpme.tklm.time.timeblock.TimeBlockBo;
44 import org.kuali.kpme.tklm.time.timehourdetail.TimeHourDetailBo;
45 import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
46 import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
47 import org.kuali.rice.core.api.mo.ModelObjectUtils;
48 import org.kuali.rice.kim.api.KimConstants;
49 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
50
51 public class DailyOvertimeRuleServiceImpl implements DailyOvertimeRuleService {
52
53 private static final Logger LOG = Logger.getLogger(DailyOvertimeRuleServiceImpl.class);
54
55 private DailyOvertimeRuleDao dailyOvertimeRuleDao = null;
56 private static final ModelObjectUtils.Transformer<TimeBlock, TimeBlock.Builder> toTimeBlockBuilder =
57 new ModelObjectUtils.Transformer<TimeBlock, TimeBlock.Builder>() {
58 public TimeBlock.Builder transform(TimeBlock input) {
59 return TimeBlock.Builder.create(input);
60 };
61 };
62 private static final ModelObjectUtils.Transformer<TimeBlockBo, TimeBlock> toTimeBlock =
63 new ModelObjectUtils.Transformer<TimeBlockBo, TimeBlock>() {
64 public TimeBlock transform(TimeBlockBo input) {
65 return TimeBlockBo.to(input);
66 };
67 };
68 private static final ModelObjectUtils.Transformer<TimeBlock, TimeBlockBo> toTimeBlockBo =
69 new ModelObjectUtils.Transformer<TimeBlock, TimeBlockBo>() {
70 public TimeBlockBo transform(TimeBlock input) {
71 return TimeBlockBo.from(input);
72 };
73 };
74
75 public void saveOrUpdate(DailyOvertimeRule dailyOvertimeRule) {
76 dailyOvertimeRuleDao.saveOrUpdate(dailyOvertimeRule);
77 }
78
79 public void saveOrUpdate(List<DailyOvertimeRule> dailyOvertimeRules) {
80 dailyOvertimeRuleDao.saveOrUpdate(dailyOvertimeRules);
81 }
82
83 @Override
84
85
86
87
88
89
90
91
92
93 public DailyOvertimeRule getDailyOvertimeRule(String groupKeyCode, String paytype, String dept, Long workArea, LocalDate asOfDate) {
94 DailyOvertimeRule dailyOvertimeRule = null;
95
96
97 if (dailyOvertimeRule == null)
98 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, paytype, dept, workArea, asOfDate);
99
100
101 if (dailyOvertimeRule == null)
102 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, paytype, dept, -1L, asOfDate);
103
104
105 if (dailyOvertimeRule == null)
106 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, paytype, "%", workArea, asOfDate);
107
108
109 if (dailyOvertimeRule == null)
110 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, paytype, "%", -1L, asOfDate);
111
112
113 if (dailyOvertimeRule == null)
114 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, "%", dept, workArea, asOfDate);
115
116
117 if (dailyOvertimeRule == null)
118 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, "%", dept, -1L, asOfDate);
119
120
121 if (dailyOvertimeRule == null)
122 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, "%", "%", workArea, asOfDate);
123
124
125 if (dailyOvertimeRule == null)
126 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule(groupKeyCode, "%", "%", -1L, asOfDate);
127
128
129 if (dailyOvertimeRule == null)
130 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", paytype, dept, workArea, asOfDate);
131
132
133 if (dailyOvertimeRule == null)
134 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", paytype, dept, -1L, asOfDate);
135
136
137 if (dailyOvertimeRule == null)
138 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", paytype, "%", workArea, asOfDate);
139
140
141 if (dailyOvertimeRule == null)
142 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", paytype, "%", -1L, asOfDate);
143
144
145 if (dailyOvertimeRule == null)
146 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", "%", dept, workArea, asOfDate);
147
148
149 if (dailyOvertimeRule == null)
150 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", "%", dept, -1L, asOfDate);
151
152
153 if (dailyOvertimeRule == null)
154 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", "%", "%", workArea, asOfDate);
155
156
157 if (dailyOvertimeRule == null)
158 dailyOvertimeRule = dailyOvertimeRuleDao.findDailyOvertimeRule("%", "%", "%", -1L, asOfDate);
159
160 return dailyOvertimeRule;
161 }
162
163
164
165 public void setDailyOvertimeRuleDao(DailyOvertimeRuleDao dailyOvertimeRuleDao) {
166 this.dailyOvertimeRuleDao = dailyOvertimeRuleDao;
167 }
168
169 private Assignment getIdentifyingKey(TimeBlockContract block, LocalDate asOfDate, String principalId) {
170 List<Assignment> lstAssign = HrServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate);
171
172 for(Assignment assign : lstAssign){
173 if((assign.getJobNumber().compareTo(block.getJobNumber()) == 0) && (assign.getWorkArea().compareTo(block.getWorkArea()) == 0)){
174 return assign;
175 }
176 }
177 return null;
178 }
179
180
181 public void processDailyOvertimeRules(TimesheetDocument timesheetDocument, TkTimeBlockAggregate timeBlockAggregate){
182 Map<DailyOvertimeRule, List<Assignment>> mapDailyOvtRulesToAssignment = new HashMap<DailyOvertimeRule, List<Assignment>>();
183
184 for(Assignment assignment : timesheetDocument.getAllAssignments()) {
185 JobContract job = assignment.getJob();
186 DailyOvertimeRule dailyOvertimeRule = getDailyOvertimeRule(job.getGroupKey().getGroupKeyCode(), job.getHrPayType(), job.getDept(), assignment.getWorkArea(), timesheetDocument.getDocEndDate());
187
188 if(dailyOvertimeRule !=null) {
189 if(mapDailyOvtRulesToAssignment.containsKey(dailyOvertimeRule)){
190 List<Assignment> lstAssign = mapDailyOvtRulesToAssignment.get(dailyOvertimeRule);
191 lstAssign.add(assignment);
192 mapDailyOvtRulesToAssignment.put(dailyOvertimeRule, lstAssign);
193 } else {
194 List<Assignment> lstAssign = new ArrayList<Assignment>();
195 lstAssign.add(assignment);
196 mapDailyOvtRulesToAssignment.put(dailyOvertimeRule, lstAssign);
197 }
198 }
199 }
200
201
202 if(mapDailyOvtRulesToAssignment.isEmpty()){
203 return;
204 }
205
206 List<List<TimeBlock>> newTimeBlocks = new ArrayList<List<TimeBlock>>();
207 List<List<TimeBlockBo>> aggregateTimeBlockBos = new ArrayList<List<TimeBlockBo>>();
208 for (List<TimeBlock> imBlocks : timeBlockAggregate.getDayTimeBlockList()) {
209 aggregateTimeBlockBos.add(ModelObjectUtils.transform(imBlocks, toTimeBlockBo));
210 }
211
212 for(List<TimeBlockBo> dayTimeBlocks : aggregateTimeBlockBos){
213
214 if (dayTimeBlocks.size() == 0) {
215 continue;
216 }
217
218
219 Map<DailyOvertimeRule,List<TimeBlockBo>> dailyOvtRuleToDayTotals = new HashMap<DailyOvertimeRule,List<TimeBlockBo>>();
220 for(TimeBlockBo timeBlock : dayTimeBlocks) {
221 Assignment assign = this.getIdentifyingKey(timeBlock, timesheetDocument.getAsOfDate(), timesheetDocument.getPrincipalId());
222 for(Map.Entry<DailyOvertimeRule, List<Assignment>> entry : mapDailyOvtRulesToAssignment.entrySet()){
223 List<Assignment> lstAssign = entry.getValue();
224
225
226
227 if(lstAssign.contains(assign)){
228
229
230 if(dailyOvtRuleToDayTotals.get(entry.getKey()) != null){
231 List<TimeBlockBo> lstTimeBlock = dailyOvtRuleToDayTotals.get(entry.getKey());
232 lstTimeBlock.add(timeBlock);
233 dailyOvtRuleToDayTotals.put(entry.getKey(), lstTimeBlock);
234 } else {
235 List<TimeBlockBo> lstTimeBlock = new ArrayList<TimeBlockBo>();
236 lstTimeBlock.add(timeBlock);
237 dailyOvtRuleToDayTotals.put(entry.getKey(), lstTimeBlock);
238 }
239 }
240 }
241 }
242
243 for(DailyOvertimeRule dr : mapDailyOvtRulesToAssignment.keySet() ){
244 Set<String> fromEarnGroup = HrServiceLocator.getEarnCodeGroupService().getEarnCodeListForEarnCodeGroup(dr.getFromEarnGroup(), timesheetDocument.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate());
245 List<TimeBlockBo> blocksForRule = dailyOvtRuleToDayTotals.get(dr);
246 if (blocksForRule == null || blocksForRule.size() == 0) {
247 continue;
248 }
249 sortTimeBlocksNatural(blocksForRule);
250
251
252 BigDecimal hours = BigDecimal.ZERO;
253 BigDecimal minutes = BigDecimal.ZERO;
254 List<TimeBlockBo> applicationList = new LinkedList<TimeBlockBo>();
255 TimeBlockBo previous = null;
256 for (TimeBlockBo block : blocksForRule) {
257 if (exceedsMaxGap(previous, block, dr.getMaxGap())) {
258 apply(hours, minutes, applicationList, dr, fromEarnGroup);
259 applicationList.clear();
260 hours = BigDecimal.ZERO;
261 minutes = BigDecimal.ZERO;
262 previous = null;
263 } else {
264 previous = block;
265 }
266 applicationList.add(block);
267 for (TimeHourDetailBo thd : block.getTimeHourDetails()) {
268 if (fromEarnGroup.contains(thd.getEarnCode())) {
269 hours = hours.add(thd.getHours(), HrConstants.MATH_CONTEXT);
270 minutes = minutes.add(thd.getTotalMinutes());
271 }
272 }
273 }
274
275 apply(hours, minutes, applicationList, dr, fromEarnGroup);
276 }
277 }
278
279 for (List<TimeBlockBo> bos : aggregateTimeBlockBos) {
280 newTimeBlocks.add(ModelObjectUtils.transform(bos, toTimeBlock));
281 }
282 timeBlockAggregate.setDayTimeBlockList(newTimeBlocks);
283 }
284
285
286
287
288
289
290
291
292
293
294 private void apply(BigDecimal hours, BigDecimal minutes, List<TimeBlockBo> blocks, DailyOvertimeRule rule, Set<String> earnGroup) {
295 sortTimeBlocksInverse(blocks);
296 if (blocks != null && blocks.size() > 0)
297 if (hours.compareTo(rule.getMinHours()) >= 0) {
298 BigDecimal remaining = hours.subtract(rule.getMinHours(), HrConstants.MATH_CONTEXT);
299 BigDecimal minutesRemaining = minutes.subtract(TKUtils.convertMillisToMinutes(TKUtils.convertHoursToMillis(rule.getMinHours())));
300 for (TimeBlockBo block : blocks) {
301 remaining = applyOvertimeToTimeBlock(block, rule.getEarnCode(), earnGroup, remaining, minutesRemaining);
302 }
303 if (remaining.compareTo(BigDecimal.ZERO) > 0) {
304 LOG.warn("Hours remaining that were unapplied in DailyOvertimeRule.");
305 }
306 }
307 }
308
309
310
311
312
313
314
315
316
317
318
319
320
321 private BigDecimal applyOvertimeToTimeBlock(TimeBlockBo block, String otEarnCode, Set<String> convertFromEarnCodes, BigDecimal otHours, BigDecimal otMinutes) {
322 BigDecimal applied = BigDecimal.ZERO;
323 BigDecimal appliedMinutes = BigDecimal.ZERO;
324
325 if (otHours.compareTo(BigDecimal.ZERO) <= 0) {
326 return BigDecimal.ZERO;
327 }
328
329 if (otMinutes.compareTo(BigDecimal.ZERO) <= 0) {
330 return BigDecimal.ZERO;
331 }
332
333 List<TimeHourDetailBo> details = block.getTimeHourDetails();
334 List<TimeHourDetailBo> addDetails = new LinkedList<TimeHourDetailBo>();
335 for (TimeHourDetailBo detail : details) {
336 if (convertFromEarnCodes.contains(detail.getEarnCode())) {
337
338 BigDecimal n = detail.getHours().subtract(otHours, HrConstants.MATH_CONTEXT);
339 BigDecimal m = detail.getTotalMinutes().subtract(otMinutes);
340
341
342 if (n.compareTo(BigDecimal.ZERO) >= 0) {
343
344 applied = otHours;
345 appliedMinutes = otMinutes;
346 } else {
347 applied = detail.getHours();
348 appliedMinutes = detail.getTotalMinutes();
349 }
350
351
352 TimeHourDetailBo timeHourDetail = new TimeHourDetailBo();
353 timeHourDetail.setHours(applied);
354 timeHourDetail.setTotalMinutes(appliedMinutes);
355 timeHourDetail.setEarnCode(otEarnCode);
356 timeHourDetail.setTkTimeBlockId(block.getTkTimeBlockId());
357
358
359 detail.setHours(detail.getHours().subtract(applied, HrConstants.MATH_CONTEXT));
360 detail.setTotalMinutes(detail.getTotalMinutes().subtract(appliedMinutes));
361 addDetails.add(timeHourDetail);
362 }
363 }
364
365 if (addDetails.size() > 0) {
366 details.addAll(addDetails);
367 block.setTimeHourDetails(details);
368 }
369
370 return otHours.subtract(applied);
371 }
372
373
374
375
376 private void sortTimeBlocksInverse(List<? extends TimeBlockContract> blocks) {
377 Collections.sort(blocks, new Comparator<TimeBlockContract>() {
378 public int compare(TimeBlockContract tb1, TimeBlockContract tb2) {
379 if (tb1 != null && tb2 != null)
380 return -1 * tb1.getBeginDateTime().compareTo(tb2.getBeginDateTime());
381 return 0;
382 }
383 });
384 }
385
386
387 private void sortTimeBlocksNatural(List<? extends TimeBlockContract> blocks) {
388 Collections.sort(blocks, new Comparator<TimeBlockContract>() {
389 public int compare(TimeBlockContract tb1, TimeBlockContract tb2) {
390 if (tb1 != null && tb2 != null)
391 return tb1.getBeginDateTime().compareTo(tb2.getBeginDateTime());
392 return 0;
393 }
394 });
395 }
396
397
398
399
400
401
402
403
404
405
406 boolean exceedsMaxGap(TimeBlockContract previous, TimeBlockContract current, BigDecimal maxGap) {
407 if (previous == null)
408 return false;
409
410 long difference = current.getBeginDateTime().getMillis() - previous.getEndDateTime().getMillis();
411 BigDecimal gapHours = TKUtils.convertMillisToHours(difference);
412 BigDecimal cmpGapHrs = TKUtils.convertMinutesToHours(maxGap);
413 return (gapHours.compareTo(cmpGapHrs) > 0);
414 }
415
416 @Override
417 public DailyOvertimeRule getDailyOvertimeRule(String tkDailyOvertimeRuleId) {
418 return dailyOvertimeRuleDao.getDailyOvertimeRule(tkDailyOvertimeRuleId);
419 }
420
421 public List<DailyOvertimeRule> getDailyOvertimeRules(String userPrincipalId, List <DailyOvertimeRule> dailyOvertimeRuleObjs){
422 List<DailyOvertimeRule> results = new ArrayList<DailyOvertimeRule>();
423
424 if ( dailyOvertimeRuleObjs != null ){
425 for (DailyOvertimeRule dailyOvertimeRuleObj : dailyOvertimeRuleObjs) {
426 String department = dailyOvertimeRuleObj.getDept();
427 String groupKeyCd = dailyOvertimeRuleObj.getGroupKeyCode();
428 Department departmentObj = HrServiceLocator.getDepartmentService().getDepartment(department, groupKeyCd, dailyOvertimeRuleObj.getEffectiveLocalDate());
429 String loc = departmentObj != null ? departmentObj.getGroupKey().getLocationId() : null;
430
431 Map<String, String> roleQualification = new HashMap<String, String>();
432 roleQualification.put(KimConstants.AttributeConstants.PRINCIPAL_ID, userPrincipalId);
433 roleQualification.put(KPMERoleMemberAttribute.DEPARTMENT.getRoleMemberAttributeName(), department);
434 roleQualification.put(KPMERoleMemberAttribute.GROUP_KEY_CODE.getRoleMemberAttributeName(), groupKeyCd);
435 roleQualification.put(KPMERoleMemberAttribute.LOCATION.getRoleMemberAttributeName(), loc);
436
437 if (!KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KPMENamespace.KPME_WKFLW.getNamespaceCode(),
438 KPMEPermissionTemplate.VIEW_KPME_RECORD.getPermissionTemplateName(), new HashMap<String, String>())
439 || KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(userPrincipalId, KPMENamespace.KPME_WKFLW.getNamespaceCode(),
440 KPMEPermissionTemplate.VIEW_KPME_RECORD.getPermissionTemplateName(), new HashMap<String, String>(), roleQualification)) {
441 results.add(dailyOvertimeRuleObj);
442 }
443 }
444 }
445
446 return results;
447 }
448 }