001 /**
002 * Copyright 2004-2013 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.timesheet.service;
017
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.apache.log4j.Logger;
021 import org.joda.time.DateTime;
022 import org.kuali.hr.job.Job;
023 import org.kuali.hr.lm.LMConstants;
024 import org.kuali.hr.lm.leaveblock.LeaveBlock;
025 import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
026 import org.kuali.hr.time.assignment.Assignment;
027 import org.kuali.hr.time.calendar.CalendarEntries;
028 import org.kuali.hr.time.principal.PrincipalHRAttributes;
029 import org.kuali.hr.time.service.base.TkServiceLocator;
030 import org.kuali.hr.time.timeblock.TimeBlock;
031 import org.kuali.hr.time.timesheet.TimesheetDocument;
032 import org.kuali.hr.time.util.TKContext;
033 import org.kuali.hr.time.util.TKUser;
034 import org.kuali.hr.time.util.TKUtils;
035 import org.kuali.hr.time.util.TkConstants;
036 import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
037 import org.kuali.rice.core.api.config.property.ConfigContext;
038 import org.kuali.rice.kew.api.KewApiServiceLocator;
039 import org.kuali.rice.kew.api.WorkflowDocument;
040 import org.kuali.rice.kew.api.WorkflowDocumentFactory;
041 import org.kuali.rice.kew.api.exception.WorkflowException;
042 import org.kuali.rice.kew.api.note.Note;
043 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
044 import org.kuali.rice.kim.api.identity.principal.Principal;
045 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
046
047 import java.math.BigDecimal;
048 import java.sql.Timestamp;
049 import java.util.ArrayList;
050 import java.util.Date;
051 import java.util.LinkedList;
052 import java.util.List;
053
054 public class TimesheetServiceImpl implements TimesheetService {
055
056 @SuppressWarnings("unused")
057 private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class);
058
059 @Override
060 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) {
061 routeTimesheet(principalId, timesheetDocument, TkConstants.DOCUMENT_ACTIONS.ROUTE);
062 }
063
064 @Override
065 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
066 timesheetAction(action, principalId, timesheetDocument);
067 }
068
069 @Override
070 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
071 timesheetAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument);
072 }
073
074 @Override
075 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
076 timesheetAction(action, principalId, timesheetDocument);
077 }
078
079 @Override
080 public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
081 timesheetAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument);
082 }
083
084 protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) {
085 WorkflowDocument wd = null;
086 if (timesheetDocument != null) {
087 String rhid = timesheetDocument.getDocumentId();
088 wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
089
090 if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
091 wd.route("Routing for Approval");
092 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
093 Note.Builder builder = Note.Builder.create(rhid, principalId);
094 builder.setCreateDate(new DateTime());
095 builder.setText("Routed via Employee Approval batch job");
096 KewApiServiceLocator.getNoteService().createNote(builder.build());
097
098 wd.route("Batch job routing timesheet");
099 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) {
100 if (TKUser.getCurrentTargetRoles().isSystemAdmin() &&
101 !TKUser.getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
102 wd.superUserBlanketApprove("Superuser approving timesheet.");
103 } else {
104 wd.approve("Approving timesheet.");
105 }
106 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
107 Note.Builder builder = Note.Builder.create(rhid, principalId);
108 builder.setCreateDate(new DateTime());
109 builder.setText("Approved via Supervisor Approval batch job");
110 KewApiServiceLocator.getNoteService().createNote(builder.build());
111
112 wd.superUserBlanketApprove("Batch job approving timesheet.");
113 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
114 if (TKUser.getCurrentTargetRoles().isSystemAdmin()
115 && !TKUser.getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
116 wd.superUserDisapprove("Superuser disapproving timesheet.");
117 } else {
118 wd.disapprove("Disapproving timesheet.");
119 }
120 }
121 }
122 }
123
124 @Override
125 public TimesheetDocument openTimesheetDocument(String principalId, CalendarEntries calendarDates) throws WorkflowException {
126 TimesheetDocument timesheetDocument = null;
127
128 Date begin = calendarDates.getBeginPeriodDateTime();
129 Date end = calendarDates.getEndPeriodDateTime();
130
131 TimesheetDocumentHeader header = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, begin, end);
132
133 if (header == null) {
134 List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, calendarDates);
135 //TkServiceLocator.getAssignmentService().getAssignments(principalId, TKUtils.getTimelessDate(payCalendarDates.getEndPeriodDate()));
136 if (activeAssignments.size() == 0) {
137 LOG.warn("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
138 return null;
139 //throw new RuntimeException("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
140 }
141
142 EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
143 String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : StringUtils.EMPTY;
144 String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime()));
145 String timesheetDocumentTitle = TimesheetDocument.TIMESHEET_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + endDateString;
146
147 timesheetDocument = this.initiateWorkflowDocument(principalId, begin, end, calendarDates, TimesheetDocument.TIMESHEET_DOCUMENT_TYPE, timesheetDocumentTitle);
148 //timesheetDocument.setPayCalendarEntry(calendarDates);
149 //this.loadTimesheetDocumentData(timesheetDocument, principalId, calendarDates);
150 //TODO switch this to scheduled time offs
151 //this.loadHolidaysOnTimesheet(timesheetDocument, principalId, begin, end);
152 } else {
153 timesheetDocument = this.getTimesheetDocument(header.getDocumentId());
154 timesheetDocument.setCalendarEntry(calendarDates);
155 }
156
157 timesheetDocument.setTimeSummary(TkServiceLocator.getTimeSummaryService().getTimeSummary(timesheetDocument));
158 return timesheetDocument;
159 }
160
161 public void loadHolidaysOnTimesheet(TimesheetDocument timesheetDocument, String principalId, Date beginDate, Date endDate) {
162 PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, new java.sql.Date(beginDate.getTime()));
163 if (principalCalendar != null && StringUtils.isNotEmpty(principalCalendar.getLeavePlan())) {
164 List<SystemScheduledTimeOff> sstoList = TkServiceLocator.getSysSchTimeOffService()
165 .getSystemScheduledTimeOffForPayPeriod(principalCalendar.getLeavePlan(), beginDate, endDate);
166 Assignment sstoAssign = TkServiceLocator.getAssignmentService().getAssignmentToApplyScheduledTimeOff(timesheetDocument, TKUtils.getTimelessDate(endDate));
167 if (sstoAssign != null) {
168 for(SystemScheduledTimeOff ssto : sstoList) {
169 BigDecimal sstoCalcHours = TkServiceLocator.getSysSchTimeOffService().calculateSysSchTimeOffHours(sstoAssign.getJob(), ssto.getAmountofTime());
170 TimeBlock timeBlock = TkServiceLocator.getTimeBlockService().createTimeBlock(timesheetDocument, new Timestamp(ssto.getScheduledTimeOffDate().getTime()),
171 new Timestamp(ssto.getScheduledTimeOffDate().getTime()), sstoAssign, TkConstants.HOLIDAY_EARN_CODE, sstoCalcHours, BigDecimal.ZERO, false, false, TKContext.getPrincipalId());
172 timesheetDocument.getTimeBlocks().add(timeBlock);
173 }
174 //If system scheduled time off are loaded will need to save them to the database
175 if (CollectionUtils.isNotEmpty(sstoList)) {
176 TkServiceLocator.getTimeBlockService().saveTimeBlocks(new LinkedList<TimeBlock>(), timesheetDocument.getTimeBlocks(), TKContext.getPrincipalId());
177 }
178 }
179 }
180 }
181
182 protected TimesheetDocument initiateWorkflowDocument(String principalId, Date payBeginDate, Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException {
183 TimesheetDocument timesheetDocument = null;
184 WorkflowDocument workflowDocument = null;
185
186 workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title);
187
188 String status = workflowDocument.getStatus().getCode();
189 TimesheetDocumentHeader documentHeader = new TimesheetDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status);
190
191 documentHeader.setDocumentId(workflowDocument.getDocumentId().toString());
192 documentHeader.setDocumentStatus("I");
193
194 TkServiceLocator.getTimesheetDocumentHeaderService().saveOrUpdate(documentHeader);
195 timesheetDocument = new TimesheetDocument(documentHeader);
196 timesheetDocument.setCalendarEntry(calendarEntries);
197 loadTimesheetDocumentData(timesheetDocument, principalId, calendarEntries);
198 TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, payEndDate);
199
200 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
201 deleteNonApprovedLeaveBlocks(principalId, calendarEntries.getBeginPeriodDate(), calendarEntries.getEndPeriodDate());
202 }
203
204 return timesheetDocument;
205 }
206
207 private void deleteNonApprovedLeaveBlocks(String principalId, Date beginDate, Date endDate) {
208 String batchUserPrincipalId = getBatchUserPrincipalId();
209
210 if (batchUserPrincipalId != null) {
211 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
212
213 for (LeaveBlock leaveBlock : leaveBlocks) {
214 if (!StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.APPROVED)) {
215 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
216 }
217 }
218 } else {
219 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
220 LOG.error("Could not delete leave request blocks due to missing batch user " + principalName);
221 }
222 }
223
224 private String getBatchUserPrincipalId() {
225 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
226 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName);
227 return principal == null ? null : principal.getPrincipalId();
228 }
229
230 public List<TimeBlock> getPrevDocumentTimeBlocks(String principalId, Date payBeginDate) {
231 TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, payBeginDate);
232 if (prevTdh == null) {
233 return new ArrayList<TimeBlock>();
234 }
235 return TkServiceLocator.getTimeBlockService().getTimeBlocks(prevTdh.getDocumentId());
236 }
237
238 @Override
239 public TimesheetDocument getTimesheetDocument(String documentId) {
240 TimesheetDocument timesheetDocument = null;
241 TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
242
243 if (tdh != null) {
244 timesheetDocument = new TimesheetDocument(tdh);
245 CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
246 loadTimesheetDocumentData(timesheetDocument, tdh.getPrincipalId(), pce);
247
248 timesheetDocument.setCalendarEntry(pce);
249 } else {
250 throw new RuntimeException("Could not find TimesheetDocumentHeader for DocumentID: " + documentId);
251 }
252 return timesheetDocument;
253 }
254
255 protected void loadTimesheetDocumentData(TimesheetDocument tdoc, String principalId, CalendarEntries payCalEntry) {
256 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, payCalEntry);
257 List<Job> jobs = TkServiceLocator.getJobService().getJobs(principalId, TKUtils.getTimelessDate(payCalEntry.getEndPeriodDate()));
258 List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(tdoc.getDocumentHeader().getDocumentId());
259
260 tdoc.setAssignments(assignments);
261 tdoc.setJobs(jobs);
262 tdoc.setTimeBlocks(timeBlocks);
263 }
264
265 public boolean isSynchronousUser() {
266 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(TKUser.getCurrentTargetPersonId(), TKUtils.getCurrentDate());
267 boolean isSynchronousUser = true;
268 for (Assignment assignment : assignments) {
269 isSynchronousUser &= assignment.isSynchronous();
270 }
271 return isSynchronousUser;
272 }
273
274 //this is an admin function used for testing
275 public void deleteTimesheet(String documentId) {
276 TkServiceLocator.getTimeBlockService().deleteTimeBlocksAssociatedWithDocumentId(documentId);
277 TkServiceLocator.getTimesheetDocumentHeaderService().deleteTimesheetHeader(documentId);
278 }
279
280 public TimeBlock resetWorkedHours(TimeBlock timeBlock) {
281 if (timeBlock.getBeginTime() != null && timeBlock.getEndTime() != null && StringUtils.equals(timeBlock.getEarnCodeType(), TkConstants.EARN_CODE_TIME)) {
282 BigDecimal hours = TKUtils.getHoursBetween(timeBlock.getBeginTime().getTime(), timeBlock.getEndTime().getTime());
283 timeBlock.setHours(hours);
284 }
285 return timeBlock;
286 }
287
288 @Override
289 public void resetTimeBlock(List<TimeBlock> timeBlocks) {
290 for (TimeBlock tb : timeBlocks) {
291 resetWorkedHours(tb);
292 }
293 TkServiceLocator.getTimeBlockService().resetTimeHourDetail(timeBlocks);
294 }
295
296 @Override
297 public boolean isReadyToApprove(TimesheetDocument document) {
298 if (document == null) {
299 return false;
300 }
301 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
302 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
303 leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
304 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
305 for(LeaveBlock lb : leaveBlocks) {
306 if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) &&
307 !StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED))
308 return false;
309 }
310 return true;
311 /* List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(),
312 document.getCalendarEntry().getBeginPeriodDate(),
313 document.getCalendarEntry().getEndPeriodDate());
314 if (!CollectionUtils.isEmpty(balanceTransfers)) {
315 for(BalanceTransfer balanceTransfer : balanceTransfers) {
316 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
317 return false;
318 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus())
319 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) {
320 return false;
321 }
322 }
323 }
324 List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(),
325 document.getCalendarEntry().getBeginPeriodDate(),
326 document.getCalendarEntry().getEndPeriodDate());
327 if (!CollectionUtils.isEmpty(leavePayouts)) {
328 for(LeavePayout payout : leavePayouts) {
329 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
330 return false;
331 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus())
332 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) {
333 return false;
334 }
335 }
336 }
337 return true;*/
338 }
339
340 }