1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.hr.time.timesheet.service;
17
18 import org.apache.commons.collections.CollectionUtils;
19 import org.apache.commons.lang.StringUtils;
20 import org.apache.log4j.Logger;
21 import org.joda.time.DateTime;
22 import org.kuali.hr.job.Job;
23 import org.kuali.hr.lm.LMConstants;
24 import org.kuali.hr.lm.leaveblock.LeaveBlock;
25 import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
26 import org.kuali.hr.time.assignment.Assignment;
27 import org.kuali.hr.time.calendar.CalendarEntries;
28 import org.kuali.hr.time.earncode.EarnCode;
29 import org.kuali.hr.time.principal.PrincipalHRAttributes;
30 import org.kuali.hr.time.service.base.TkServiceLocator;
31 import org.kuali.hr.time.timeblock.TimeBlock;
32 import org.kuali.hr.time.timesheet.TimesheetDocument;
33 import org.kuali.hr.time.util.TKContext;
34 import org.kuali.hr.time.util.TKUser;
35 import org.kuali.hr.time.util.TKUtils;
36 import org.kuali.hr.time.util.TkConstants;
37 import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
38 import org.kuali.rice.core.api.config.property.ConfigContext;
39 import org.kuali.rice.kew.api.KewApiServiceLocator;
40 import org.kuali.rice.kew.api.WorkflowDocument;
41 import org.kuali.rice.kew.api.WorkflowDocumentFactory;
42 import org.kuali.rice.kew.api.exception.WorkflowException;
43 import org.kuali.rice.kew.api.note.Note;
44 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
45 import org.kuali.rice.kim.api.identity.principal.Principal;
46 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
47
48 import java.math.BigDecimal;
49 import java.sql.Timestamp;
50 import java.util.ArrayList;
51 import java.util.Date;
52 import java.util.LinkedList;
53 import java.util.List;
54
55 public class TimesheetServiceImpl implements TimesheetService {
56
57 @SuppressWarnings("unused")
58 private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class);
59
60 @Override
61 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) {
62 routeTimesheet(principalId, timesheetDocument, TkConstants.DOCUMENT_ACTIONS.ROUTE);
63 }
64
65 @Override
66 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
67 timesheetAction(action, principalId, timesheetDocument);
68 }
69
70 @Override
71 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
72 timesheetAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument);
73 }
74
75 @Override
76 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
77 timesheetAction(action, principalId, timesheetDocument);
78 }
79
80 @Override
81 public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
82 timesheetAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument);
83 }
84
85 protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) {
86 WorkflowDocument wd = null;
87 if (timesheetDocument != null) {
88 String rhid = timesheetDocument.getDocumentId();
89 wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
90
91 if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
92 wd.route("Routing for Approval");
93 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
94 Note.Builder builder = Note.Builder.create(rhid, principalId);
95 builder.setCreateDate(new DateTime());
96 builder.setText("Routed via Employee Approval batch job");
97 KewApiServiceLocator.getNoteService().createNote(builder.build());
98
99 wd.route("Batch job routing timesheet");
100 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) {
101 if (TKUser.getCurrentTargetRoles().isSystemAdmin() &&
102 !TKUser.getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
103 wd.superUserBlanketApprove("Superuser approving timesheet.");
104 } else {
105 wd.approve("Approving timesheet.");
106 }
107 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
108 Note.Builder builder = Note.Builder.create(rhid, principalId);
109 builder.setCreateDate(new DateTime());
110 builder.setText("Approved via Supervisor Approval batch job");
111 KewApiServiceLocator.getNoteService().createNote(builder.build());
112
113 wd.superUserBlanketApprove("Batch job approving timesheet.");
114 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
115 if (TKUser.getCurrentTargetRoles().isSystemAdmin()
116 && !TKUser.getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
117 wd.superUserDisapprove("Superuser disapproving timesheet.");
118 } else {
119 wd.disapprove("Disapproving timesheet.");
120 }
121 }
122 }
123 }
124
125 @Override
126 public TimesheetDocument openTimesheetDocument(String principalId, CalendarEntries calendarDates) throws WorkflowException {
127 TimesheetDocument timesheetDocument = null;
128
129 Date begin = calendarDates.getBeginPeriodDateTime();
130 Date end = calendarDates.getEndPeriodDateTime();
131
132 TimesheetDocumentHeader header = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, begin, end);
133
134 if (header == null) {
135 List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, calendarDates);
136
137 if (activeAssignments.size() == 0) {
138 LOG.warn("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
139 return null;
140
141 }
142
143 EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
144 String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : StringUtils.EMPTY;
145 String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime()));
146 String timesheetDocumentTitle = TimesheetDocument.TIMESHEET_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + endDateString;
147
148 timesheetDocument = this.initiateWorkflowDocument(principalId, begin, end, calendarDates, TimesheetDocument.TIMESHEET_DOCUMENT_TYPE, timesheetDocumentTitle);
149
150
151
152
153 } else {
154 timesheetDocument = this.getTimesheetDocument(header.getDocumentId());
155 if (timesheetDocument != null) {
156 timesheetDocument.setCalendarEntry(calendarDates);
157 }
158 }
159
160 if (timesheetDocument != null) {
161 timesheetDocument.setTimeSummary(TkServiceLocator.getTimeSummaryService().getTimeSummary(timesheetDocument));
162 }
163 return timesheetDocument;
164 }
165
166 public void loadHolidaysOnTimesheet(TimesheetDocument timesheetDocument, String principalId, Date beginDate, Date endDate) {
167 PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, new java.sql.Date(beginDate.getTime()));
168 if (principalCalendar != null && StringUtils.isNotEmpty(principalCalendar.getLeavePlan())) {
169 List<SystemScheduledTimeOff> sstoList = TkServiceLocator.getSysSchTimeOffService()
170 .getSystemScheduledTimeOffForPayPeriod(principalCalendar.getLeavePlan(), beginDate, endDate);
171 Assignment sstoAssign = TkServiceLocator.getAssignmentService().getAssignmentToApplyScheduledTimeOff(timesheetDocument, TKUtils.getTimelessDate(endDate));
172 if (sstoAssign != null) {
173 for(SystemScheduledTimeOff ssto : sstoList) {
174 BigDecimal sstoCalcHours = TkServiceLocator.getSysSchTimeOffService().calculateSysSchTimeOffHours(sstoAssign.getJob(), ssto.getAmountofTime());
175 TimeBlock timeBlock = TkServiceLocator.getTimeBlockService().createTimeBlock(timesheetDocument, new Timestamp(ssto.getScheduledTimeOffDate().getTime()),
176 new Timestamp(ssto.getScheduledTimeOffDate().getTime()), sstoAssign, TkConstants.HOLIDAY_EARN_CODE, sstoCalcHours, BigDecimal.ZERO, false, false, TKContext.getPrincipalId());
177 timesheetDocument.getTimeBlocks().add(timeBlock);
178 }
179
180 if (CollectionUtils.isNotEmpty(sstoList)) {
181 TkServiceLocator.getTimeBlockService().saveTimeBlocks(new LinkedList<TimeBlock>(), timesheetDocument.getTimeBlocks(), TKContext.getPrincipalId());
182 }
183 }
184 }
185 }
186
187 protected TimesheetDocument initiateWorkflowDocument(String principalId, Date payBeginDate, Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException {
188 TimesheetDocument timesheetDocument = null;
189 WorkflowDocument workflowDocument = null;
190
191 workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title);
192
193 String status = workflowDocument.getStatus().getCode();
194 TimesheetDocumentHeader documentHeader = new TimesheetDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status);
195
196 documentHeader.setDocumentId(workflowDocument.getDocumentId().toString());
197 documentHeader.setDocumentStatus("I");
198
199 TkServiceLocator.getTimesheetDocumentHeaderService().saveOrUpdate(documentHeader);
200 timesheetDocument = new TimesheetDocument(documentHeader);
201 timesheetDocument.setCalendarEntry(calendarEntries);
202 loadTimesheetDocumentData(timesheetDocument, principalId, calendarEntries);
203 TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, payEndDate);
204
205 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
206 deleteNonApprovedLeaveBlocks(principalId, calendarEntries.getBeginPeriodDate(), calendarEntries.getEndPeriodDate());
207 }
208
209 return timesheetDocument;
210 }
211
212 private void deleteNonApprovedLeaveBlocks(String principalId, Date beginDate, Date endDate) {
213 String batchUserPrincipalId = getBatchUserPrincipalId();
214
215 if (batchUserPrincipalId != null) {
216 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
217
218 for (LeaveBlock leaveBlock : leaveBlocks) {
219 if (!StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.APPROVED)) {
220 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
221 }
222 }
223 } else {
224 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
225 LOG.error("Could not delete leave request blocks due to missing batch user " + principalName);
226 }
227 }
228
229 private String getBatchUserPrincipalId() {
230 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
231 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName);
232 return principal == null ? null : principal.getPrincipalId();
233 }
234
235 public List<TimeBlock> getPrevDocumentTimeBlocks(String principalId, Date payBeginDate) {
236 TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, payBeginDate);
237 if (prevTdh == null) {
238 return new ArrayList<TimeBlock>();
239 }
240 return TkServiceLocator.getTimeBlockService().getTimeBlocks(prevTdh.getDocumentId());
241 }
242
243 @Override
244 public TimesheetDocument getTimesheetDocument(String documentId) {
245 TimesheetDocument timesheetDocument = null;
246 TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
247
248 if (tdh != null) {
249 timesheetDocument = new TimesheetDocument(tdh);
250 CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
251 loadTimesheetDocumentData(timesheetDocument, tdh.getPrincipalId(), pce);
252
253 timesheetDocument.setCalendarEntry(pce);
254 } else {
255 LOG.error("Could not find TimesheetDocumentHeader for DocumentID: " + documentId);
256 return null;
257 }
258 return timesheetDocument;
259 }
260
261 protected void loadTimesheetDocumentData(TimesheetDocument tdoc, String principalId, CalendarEntries payCalEntry) {
262 tdoc.setAssignments(TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, payCalEntry));
263 if (payCalEntry != null) {
264 tdoc.setJobs(TkServiceLocator.getJobService().getJobs(principalId, TKUtils.getTimelessDate(payCalEntry.getEndPeriodDate())));
265 }
266 tdoc.setTimeBlocks(TkServiceLocator.getTimeBlockService().getTimeBlocks(tdoc.getDocumentHeader().getDocumentId()));
267 }
268
269 public boolean isSynchronousUser() {
270 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(TKUser.getCurrentTargetPersonId(), TKUtils.getCurrentDate());
271 boolean isSynchronousUser = true;
272 for (Assignment assignment : assignments) {
273 isSynchronousUser &= assignment.isSynchronous();
274 }
275 return isSynchronousUser;
276 }
277
278
279 public void deleteTimesheet(String documentId) {
280 TkServiceLocator.getTimeBlockService().deleteTimeBlocksAssociatedWithDocumentId(documentId);
281 TkServiceLocator.getTimesheetDocumentHeaderService().deleteTimesheetHeader(documentId);
282 }
283
284 public TimeBlock resetWorkedHours(TimeBlock timeBlock, Date asOfDate) {
285 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(timeBlock.getEarnCode(), new java.sql.Date(asOfDate.getTime()));
286
287 if (timeBlock.getBeginTime() != null && timeBlock.getEndTime() != null && StringUtils.equals(timeBlock.getEarnCodeType(), TkConstants.EARN_CODE_TIME)) {
288 BigDecimal hours = TKUtils.getHoursBetween(timeBlock.getBeginTime().getTime(), timeBlock.getEndTime().getTime());
289
290
291
292
293 if (earnCodeObj.getInflateMinHours() != null) {
294 if ((earnCodeObj.getInflateMinHours().compareTo(BigDecimal.ZERO) != 0) &&
295 earnCodeObj.getInflateMinHours().compareTo(hours) > 0) {
296 hours = earnCodeObj.getInflateMinHours();
297 }
298 }
299
300 if (earnCodeObj.getInflateFactor() != null) {
301 if ((earnCodeObj.getInflateFactor().compareTo(new BigDecimal(1.0)) != 0)
302 && (earnCodeObj.getInflateFactor().compareTo(BigDecimal.ZERO)!= 0) ) {
303 hours = earnCodeObj.getInflateFactor().multiply(hours, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE);
304 }
305 }
306
307 timeBlock.setHours(hours);
308 }
309 return timeBlock;
310 }
311
312 @Override
313 public void resetTimeBlock(List<TimeBlock> timeBlocks, Date asOfDate) {
314 for (TimeBlock tb : timeBlocks) {
315 resetWorkedHours(tb, asOfDate);
316 }
317 TkServiceLocator.getTimeBlockService().resetTimeHourDetail(timeBlocks);
318 }
319
320 @Override
321 public boolean isReadyToApprove(TimesheetDocument document) {
322 if (document == null) {
323 return false;
324 }
325 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
326 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
327 leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
328 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
329 for(LeaveBlock lb : leaveBlocks) {
330 if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) &&
331 !StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED))
332 return false;
333 }
334 return true;
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362 }
363
364 }