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.earncode.EarnCode;
029 import org.kuali.hr.time.principal.PrincipalHRAttributes;
030 import org.kuali.hr.time.service.base.TkServiceLocator;
031 import org.kuali.hr.time.timeblock.TimeBlock;
032 import org.kuali.hr.time.timesheet.TimesheetDocument;
033 import org.kuali.hr.time.util.TKContext;
034 import org.kuali.hr.time.util.TKUser;
035 import org.kuali.hr.time.util.TKUtils;
036 import org.kuali.hr.time.util.TkConstants;
037 import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
038 import org.kuali.rice.core.api.config.property.ConfigContext;
039 import org.kuali.rice.kew.api.KewApiServiceLocator;
040 import org.kuali.rice.kew.api.WorkflowDocument;
041 import org.kuali.rice.kew.api.WorkflowDocumentFactory;
042 import org.kuali.rice.kew.api.exception.WorkflowException;
043 import org.kuali.rice.kew.api.note.Note;
044 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
045 import org.kuali.rice.kim.api.identity.principal.Principal;
046 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
047
048 import java.math.BigDecimal;
049 import java.sql.Timestamp;
050 import java.util.ArrayList;
051 import java.util.Date;
052 import java.util.LinkedList;
053 import java.util.List;
054
055 public class TimesheetServiceImpl implements TimesheetService {
056
057 @SuppressWarnings("unused")
058 private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class);
059
060 @Override
061 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) {
062 routeTimesheet(principalId, timesheetDocument, TkConstants.DOCUMENT_ACTIONS.ROUTE);
063 }
064
065 @Override
066 public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
067 timesheetAction(action, principalId, timesheetDocument);
068 }
069
070 @Override
071 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
072 timesheetAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument);
073 }
074
075 @Override
076 public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
077 timesheetAction(action, principalId, timesheetDocument);
078 }
079
080 @Override
081 public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
082 timesheetAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument);
083 }
084
085 protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) {
086 WorkflowDocument wd = null;
087 if (timesheetDocument != null) {
088 String rhid = timesheetDocument.getDocumentId();
089 wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
090
091 if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
092 wd.route("Routing for Approval");
093 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
094 Note.Builder builder = Note.Builder.create(rhid, principalId);
095 builder.setCreateDate(new DateTime());
096 builder.setText("Routed via Employee Approval batch job");
097 KewApiServiceLocator.getNoteService().createNote(builder.build());
098
099 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 //TkServiceLocator.getAssignmentService().getAssignments(principalId, TKUtils.getTimelessDate(payCalendarDates.getEndPeriodDate()));
137 if (activeAssignments.size() == 0) {
138 LOG.warn("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
139 return null;
140 //throw new RuntimeException("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
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 //timesheetDocument.setPayCalendarEntry(calendarDates);
150 //this.loadTimesheetDocumentData(timesheetDocument, principalId, calendarDates);
151 //TODO switch this to scheduled time offs
152 //this.loadHolidaysOnTimesheet(timesheetDocument, principalId, begin, end);
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 //If system scheduled time off are loaded will need to save them to the database
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 //this is an admin function used for testing
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 //If earn code has an inflate min hours check if it is greater than zero
291 //and compare if the hours specified is less than min hours awarded for this
292 //earn code
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 //If earn code has an inflate factor multiple hours specified by the factor
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 /* List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(),
336 document.getCalendarEntry().getBeginPeriodDate(),
337 document.getCalendarEntry().getEndPeriodDate());
338 if (!CollectionUtils.isEmpty(balanceTransfers)) {
339 for(BalanceTransfer balanceTransfer : balanceTransfers) {
340 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
341 return false;
342 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus())
343 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) {
344 return false;
345 }
346 }
347 }
348 List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(),
349 document.getCalendarEntry().getBeginPeriodDate(),
350 document.getCalendarEntry().getEndPeriodDate());
351 if (!CollectionUtils.isEmpty(leavePayouts)) {
352 for(LeavePayout payout : leavePayouts) {
353 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
354 return false;
355 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus())
356 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) {
357 return false;
358 }
359 }
360 }
361 return true;*/
362 }
363
364 }