001 /**
002 * Copyright 2004-2012 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.timeblock.service;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.apache.log4j.Logger;
020 import org.joda.time.DateTime;
021 import org.joda.time.DateTimeConstants;
022 import org.joda.time.DateTimeZone;
023 import org.joda.time.Interval;
024 import org.kuali.hr.earncodesec.EarnCodeSecurity;
025 import org.kuali.hr.job.Job;
026 import org.kuali.hr.time.assignment.Assignment;
027 import org.kuali.hr.time.earncode.EarnCode;
028 import org.kuali.hr.time.paytype.PayType;
029 import org.kuali.hr.time.service.base.TkServiceLocator;
030 import org.kuali.hr.time.task.Task;
031 import org.kuali.hr.time.timeblock.TimeBlock;
032 import org.kuali.hr.time.timeblock.TimeBlockHistory;
033 import org.kuali.hr.time.timeblock.TimeHourDetail;
034 import org.kuali.hr.time.timeblock.dao.TimeBlockDao;
035 import org.kuali.hr.time.timesheet.TimesheetDocument;
036 import org.kuali.hr.time.util.TKContext;
037 import org.kuali.hr.time.util.TKUtils;
038 import org.kuali.hr.time.util.TkConstants;
039 import org.kuali.rice.krad.util.GlobalVariables;
040
041 import java.math.BigDecimal;
042 import java.sql.Date;
043 import java.sql.Timestamp;
044 import java.util.ArrayList;
045 import java.util.List;
046
047 public class TimeBlockServiceImpl implements TimeBlockService {
048
049 private static final Logger LOG = Logger.getLogger(TimeBlockServiceImpl.class);
050 private TimeBlockDao timeBlockDao;
051
052 public void setTimeBlockDao(TimeBlockDao timeBlockDao) {
053 this.timeBlockDao = timeBlockDao;
054 }
055
056 //This function is used to build timeblocks that span days
057 public List<TimeBlock> buildTimeBlocksSpanDates(Assignment assignment, String earnCode, TimesheetDocument timesheetDocument,
058 Timestamp beginTimestamp, Timestamp endTimestamp, BigDecimal hours, BigDecimal amount,
059 Boolean isClockLogCreated, Boolean isLunchDeleted, String spanningWeeks) {
060 DateTimeZone zone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
061 DateTime beginDt = new DateTime(beginTimestamp.getTime(), zone);
062 DateTime endDt = beginDt.toLocalDate().toDateTime((new DateTime(endTimestamp.getTime(), zone)).toLocalTime(), zone);
063 if (endDt.isBefore(beginDt)) endDt = endDt.plusDays(1);
064
065 List<Interval> dayInt = TKUtils.getDaySpanForCalendarEntry(timesheetDocument.getPayCalendarEntry());
066 TimeBlock firstTimeBlock = new TimeBlock();
067 List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>();
068 for (Interval dayIn : dayInt) {
069 if (dayIn.contains(beginDt)) {
070 if (dayIn.contains(endDt) || dayIn.getEnd().equals(endDt)) {
071 firstTimeBlock = createTimeBlock(timesheetDocument, beginTimestamp, new Timestamp(endDt.getMillis()), assignment, earnCode, hours, amount, false, isLunchDeleted);
072 lstTimeBlocks.add(firstTimeBlock);
073 } else {
074 //TODO move this to prerule validation
075 //throw validation error if this case met error
076 }
077 }
078 }
079
080 DateTime endTime = new DateTime(endTimestamp.getTime(), zone);
081 DateTime endOfFirstDay = new DateTime(firstTimeBlock.getEndTimestamp(), zone);
082 long diffInMillis = endOfFirstDay.minus(beginDt.getMillis()).getMillis();
083 DateTime currTime = beginDt.plusDays(1);
084 while (currTime.isBefore(endTime) || currTime.isEqual(endTime)) {
085 Timestamp begin = new Timestamp(currTime.getMillis());
086 Timestamp end = new Timestamp((currTime.plus(diffInMillis).getMillis()));
087 TimeBlock tb = createTimeBlock(timesheetDocument, begin, end, assignment, earnCode, hours, amount, false, isLunchDeleted);
088 lstTimeBlocks.add(tb);
089
090 currTime = currTime.plusDays(1);
091 }
092 return lstTimeBlocks;
093 }
094
095
096 public List<TimeBlock> buildTimeBlocks(Assignment assignment, String earnCode, TimesheetDocument timesheetDocument,
097 Timestamp beginTimestamp, Timestamp endTimestamp, BigDecimal hours, BigDecimal amount,
098 Boolean isClockLogCreated, Boolean isLunchDeleted) {
099
100 //Create 1 or many timeblocks if the span of timeblocks exceed more than one
101 //day that is determined by pay period day(24 hrs + period begin date)
102 Interval firstDay = null;
103 List<Interval> dayIntervals = TKUtils.getDaySpanForCalendarEntry(timesheetDocument.getPayCalendarEntry());
104 List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>();
105 Timestamp beginTemp = beginTimestamp;
106
107 for (Interval dayInt : dayIntervals) {
108 // the time period spans more than one day
109 if (firstDay != null) {
110 if(!dayInt.contains(endTimestamp.getTime())){
111 beginTemp = new Timestamp(dayInt.getStartMillis());
112 } else if((dayInt.getStartMillis() - endTimestamp.getTime()) != 0){
113 TimeBlock tb = createTimeBlock(timesheetDocument, new Timestamp(dayInt.getStartMillis()), endTimestamp, assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted);
114 lstTimeBlocks.add(tb);
115 break;
116 }
117 }
118 if (dayInt.contains(beginTemp.getTime())) {
119 firstDay = dayInt;
120 // KPME-361
121 // added a condition to handle the time block which ends at 12a, e.g. a 10p-12a timeblock
122 // this is the same fix as TkTimeBlockAggregate
123 if (dayInt.contains(endTimestamp.getTime()) || (endTimestamp.getTime() == dayInt.getEnd().getMillis())) {
124 //create one timeblock if contained in one day interval
125 TimeBlock tb = createTimeBlock(timesheetDocument, beginTemp, endTimestamp, assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted);
126 tb.setBeginTimestamp(beginTemp);
127 tb.setEndTimestamp(endTimestamp);
128 lstTimeBlocks.add(tb);
129 break;
130 } else {
131 // create a timeblock that wraps the 24 hr day
132 TimeBlock tb = createTimeBlock(timesheetDocument, beginTemp, new Timestamp(dayInt.getEndMillis()), assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted);
133 tb.setBeginTimestamp(beginTemp);
134 tb.setEndTimestamp(new Timestamp(firstDay.getEndMillis()));
135 lstTimeBlocks.add(tb);
136 }
137 }
138 }
139 return lstTimeBlocks;
140 }
141
142 public void saveTimeBlocks(List<TimeBlock> oldTimeBlocks, List<TimeBlock> newTimeBlocks) {
143 List<TimeBlock> alteredTimeBlocks = new ArrayList<TimeBlock>();
144 for (TimeBlock tb : newTimeBlocks) {
145 boolean persist = true;
146 for (TimeBlock tbOld : oldTimeBlocks) {
147 if (tb.equals(tbOld)) {
148 persist = false;
149 break;
150 }
151 }
152 if (persist) {
153 alteredTimeBlocks.add(tb);
154 }
155 }
156 for (TimeBlock tb : alteredTimeBlocks) {
157 TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(tb.getTkTimeBlockId());
158 // xichen, 11/01/11. KPME-744. set userPrincipalId with id which is logging in the sys.
159 tb.setUserPrincipalId(GlobalVariables.getUserSession().getPrincipalId());
160
161 timeBlockDao.saveOrUpdate(tb);
162 tb.setTimeBlockHistories(TkServiceLocator.getTimeBlockService().createTimeBlockHistories(tb, TkConstants.ACTIONS.ADD_TIME_BLOCK));
163 for(TimeBlockHistory tbh : tb.getTimeBlockHistories()){
164 TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
165 }
166
167 }
168
169 }
170
171 public void saveTimeBlocks(List<TimeBlock> tbList) {
172 for (TimeBlock tb : tbList) {
173 TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(tb.getTkTimeBlockId());
174 timeBlockDao.saveOrUpdate(tb);
175 for(TimeBlockHistory tbh : tb.getTimeBlockHistories()){
176 TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
177 }
178 }
179 }
180
181 public void updateTimeBlock(TimeBlock tb) {
182 timeBlockDao.saveOrUpdate(tb);
183 }
184
185
186 public TimeBlock createTimeBlock(TimesheetDocument timesheetDocument, Timestamp beginTime, Timestamp endTime, Assignment assignment, String earnCode, BigDecimal hours, BigDecimal amount, Boolean clockLogCreated, Boolean lunchDeleted) {
187 String tz = TkServiceLocator.getTimezoneService().getUserTimezone();
188 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(earnCode, timesheetDocument.getAsOfDate());
189
190 TimeBlock tb = new TimeBlock();
191 tb.setDocumentId(timesheetDocument.getDocumentHeader().getDocumentId());
192 tb.setPrincipalId(timesheetDocument.getPrincipalId());
193 tb.setJobNumber(assignment.getJobNumber());
194 tb.setWorkArea(assignment.getWorkArea());
195 tb.setTask(assignment.getTask());
196 tb.setTkWorkAreaId(assignment.getWorkAreaObj().getTkWorkAreaId());
197 tb.setHrJobId(assignment.getJob().getHrJobId());
198 String tkTaskId = "0";
199 for (Task task : assignment.getWorkAreaObj().getTasks()) {
200 if (task.getTask().compareTo(assignment.getTask()) == 0) {
201 tkTaskId = task.getTkTaskId();
202 break;
203 }
204 }
205 tb.setTkTaskId(tkTaskId);
206 tb.setEarnCode(earnCode);
207 tb.setBeginTimestamp(beginTime);
208 tb.setBeginTimestampTimezone(tz);
209 tb.setEndTimestamp(endTime);
210 tb.setEndTimestampTimezone(tz);
211 DateTimeZone dtz = DateTimeZone.forID(tz);
212 tb.setBeginTimeDisplay(new DateTime(tb.getBeginTimestamp(), dtz));
213 tb.setEndTimeDisplay(new DateTime(tb.getEndTimestamp(), dtz));
214 // only calculate the hours from the time fields if the passed in hour is zero
215 if(hours == null || hours.compareTo(BigDecimal.ZERO) == 0) {
216 hours = TKUtils.getHoursBetween(beginTime.getTime(), endTime.getTime());
217 }
218 tb.setAmount(amount);
219 //If earn code has an inflate min hours check if it is greater than zero
220 //and compare if the hours specified is less than min hours awarded for this
221 //earn code
222 if (earnCodeObj.getInflateMinHours() != null) {
223 if ((earnCodeObj.getInflateMinHours().compareTo(BigDecimal.ZERO) != 0) &&
224 earnCodeObj.getInflateMinHours().compareTo(hours) > 0) {
225 hours = earnCodeObj.getInflateMinHours();
226 }
227 }
228 //If earn code has an inflate factor multiple hours specified by the factor
229 if (earnCodeObj.getInflateFactor() != null) {
230 if ((earnCodeObj.getInflateFactor().compareTo(new BigDecimal(1.0)) != 0)
231 && (earnCodeObj.getInflateFactor().compareTo(BigDecimal.ZERO)!= 0) ) {
232 hours = earnCodeObj.getInflateFactor().multiply(hours, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE);
233 }
234 }
235
236 tb.setEarnCodeType(earnCodeObj.getEarnCodeType());
237 tb.setHours(hours);
238 tb.setClockLogCreated(clockLogCreated);
239 tb.setUserPrincipalId(TKContext.getPrincipalId());
240 tb.setTimestamp(new Timestamp(System.currentTimeMillis()));
241 tb.setLunchDeleted(lunchDeleted);
242
243 tb.setTimeHourDetails(this.createTimeHourDetails(tb.getEarnCode(), tb.getHours(), tb.getAmount(), tb.getTkTimeBlockId()));
244
245 return tb;
246 }
247
248 public TimeBlock getTimeBlock(String tkTimeBlockId) {
249 return timeBlockDao.getTimeBlock(tkTimeBlockId);
250 }
251
252 @Override
253 public void deleteTimeBlock(TimeBlock timeBlock) {
254 timeBlockDao.deleteTimeBlock(timeBlock);
255
256 }
257
258 public void resetTimeHourDetail(List<TimeBlock> origTimeBlocks) {
259 for (TimeBlock tb : origTimeBlocks) {
260 tb.setTimeHourDetails(createTimeHourDetails(tb.getEarnCode(), tb.getHours(), tb.getAmount(), tb.getTkTimeBlockId()));
261 //reset time block history details
262 for(TimeBlockHistory tbh : tb.getTimeBlockHistories()) {
263 TkServiceLocator.getTimeBlockHistoryService().addTimeBlockHistoryDetails(tbh,tb);
264 }
265 }
266 }
267
268 private List<TimeHourDetail> createTimeHourDetails(String earnCode, BigDecimal hours, BigDecimal amount, String timeBlockId) {
269 List<TimeHourDetail> timeHourDetails = new ArrayList<TimeHourDetail>();
270
271 TimeHourDetail timeHourDetail = new TimeHourDetail();
272 timeHourDetail.setEarnCode(earnCode);
273 timeHourDetail.setHours(hours);
274 timeHourDetail.setAmount(amount);
275 timeHourDetail.setTkTimeBlockId(timeBlockId);
276 timeHourDetails.add(timeHourDetail);
277
278 return timeHourDetails;
279 }
280
281 public List<TimeBlockHistory> createTimeBlockHistories(TimeBlock tb, String actionHistory) {
282 List<TimeBlockHistory> tbhs = new ArrayList<TimeBlockHistory>();
283
284 TimeBlockHistory tbh = new TimeBlockHistory(tb);
285 tbh.setActionHistory(actionHistory);
286 // add time block history details to this time block history
287 TkServiceLocator.getTimeBlockHistoryService().addTimeBlockHistoryDetails(tbh, tb);
288
289 tbhs.add(tbh);
290
291 return tbhs;
292 }
293
294 // This method now translates time based on timezone settings.
295 //
296 public List<TimeBlock> getTimeBlocks(String documentId) {
297 List<TimeBlock> timeBlocks = timeBlockDao.getTimeBlocks(documentId);
298 TkServiceLocator.getTimezoneService().translateForTimezone(timeBlocks);
299 for(TimeBlock tb : timeBlocks) {
300 String earnCodeType = TkServiceLocator.getEarnCodeService().getEarnCodeType(tb.getEarnCode(), new java.sql.Date(tb.getBeginTimestamp().getTime()));
301 tb.setEarnCodeType(earnCodeType);
302 }
303
304 return timeBlocks;
305 }
306
307 public List<TimeBlock> getTimeBlocksForAssignment(Assignment assign) {
308 List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
309 if(assign != null) {
310 timeBlocks = timeBlockDao.getTimeBlocksForAssignment(assign);
311 }
312 TkServiceLocator.getTimezoneService().translateForTimezone(timeBlocks);
313 for(TimeBlock tb : timeBlocks) {
314 String earnCodeType = TkServiceLocator.getEarnCodeService().getEarnCodeType(tb.getEarnCode(), new java.sql.Date(tb.getBeginTimestamp().getTime()));
315 tb.setEarnCodeType(earnCodeType);
316 }
317 return timeBlocks;
318 }
319
320
321 @Override
322 public void deleteTimeBlocksAssociatedWithDocumentId(String documentId) {
323 timeBlockDao.deleteTimeBlocksAssociatedWithDocumentId(documentId);
324 }
325
326 @Override
327 // figure out if the user has permission to edit/delete the time block
328 public Boolean isTimeBlockEditable(TimeBlock tb) {
329 String userId = GlobalVariables.getUserSession().getPrincipalId();
330
331 if(userId != null) {
332
333 if(TKContext.getUser().isSystemAdmin()) {
334 return true;
335 }
336
337 if(TKContext.getUser().isTimesheetApprover() && TKContext.getUser().getApproverWorkAreas().contains(tb.getWorkArea())
338 || TKContext.getUser().isTimesheetReviewer() && TKContext.getUser().getReviewerWorkAreas().contains(tb.getWorkArea())) {
339 Job job = TkServiceLocator.getJobService().getJob(TKContext.getTargetPrincipalId(),tb.getJobNumber(), tb.getEndDate());
340 PayType payType = TkServiceLocator.getPayTypeService().getPayType(job.getHrPayType(), tb.getEndDate());
341 if(StringUtils.equals(payType.getRegEarnCode(), tb.getEarnCode())){
342 return true;
343 }
344
345 List<EarnCodeSecurity> deptEarnCodes = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), tb.getEndDate());
346 for(EarnCodeSecurity dec : deptEarnCodes){
347 if(dec.isApprover() && StringUtils.equals(dec.getEarnCode(), tb.getEarnCode())){
348 return true;
349 }
350 }
351 }
352
353 if(userId.equals(TKContext.getTargetPrincipalId())) {
354 Job job = TkServiceLocator.getJobService().getJob(TKContext.getTargetPrincipalId(),tb.getJobNumber(), tb.getEndDate());
355 PayType payType = TkServiceLocator.getPayTypeService().getPayType(job.getHrPayType(), tb.getEndDate());
356 if(StringUtils.equals(payType.getRegEarnCode(), tb.getEarnCode())){
357 return true;
358 }
359
360 List<EarnCodeSecurity> deptEarnCodes = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), tb.getEndDate());
361 for(EarnCodeSecurity dec : deptEarnCodes){
362 if(dec.isEmployee() && StringUtils.equals(dec.getEarnCode(), tb.getEarnCode())){
363 return true;
364 }
365 }
366 // if the user is the creator of this time block
367 }
368
369
370 }
371 return false;
372 }
373
374 @Override
375 public List<TimeBlock> getTimeBlocksForClockLogEndId(String tkClockLogId) {
376 return timeBlockDao.getTimeBlocksForClockLogEndId(tkClockLogId);
377 }
378 @Override
379 public List<TimeBlock> getTimeBlocksForClockLogBeginId(String tkClockLogId) {
380 return timeBlockDao.getTimeBlocksForClockLogBeginId(tkClockLogId);
381 }
382
383 public List<TimeBlock> getTimeBlocks(){
384 return timeBlockDao.getTimeBlocks();
385 }
386
387 public List<TimeBlock> getLatestEndTimestamp(){
388 return timeBlockDao.getLatestEndTimestamp();
389 }
390
391 @Override
392 public List<TimeBlock> getOvernightTimeBlocks(String clockLogEndId) {
393 return timeBlockDao.getOvernightTimeBlocks(clockLogEndId);
394 }
395
396 @Override
397 public void deleteLunchDeduction(String tkTimeHourDetailId) {
398 TimeHourDetail thd = TkServiceLocator.getTimeHourDetailService().getTimeHourDetail(tkTimeHourDetailId);
399 TimeBlock tb = getTimeBlock(thd.getTkTimeBlockId());
400
401 // mark the lunch deleted as Y
402 tb.setLunchDeleted(true);
403 // save the change
404 timeBlockDao.saveOrUpdate(tb);
405 // remove the related time hour detail row with the lunch deduction
406 TkServiceLocator.getTimeHourDetailService().removeTimeHourDetail(thd.getTkTimeHourDetailId());
407 }
408 @Override
409 public List<TimeBlock> getTimeBlocksWithEarnCode(String earnCode, Date effDate) {
410 return timeBlockDao.getTimeBlocksWithEarnCode(earnCode, effDate);
411 }
412 }