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.lm.leavecalendar.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.leavecalendar.LeaveCalendarDocument;
026 import org.kuali.hr.lm.leavecalendar.dao.LeaveCalendarDao;
027 import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
028 import org.kuali.hr.lm.workflow.LeaveRequestDocument;
029 import org.kuali.hr.time.assignment.Assignment;
030 import org.kuali.hr.time.calendar.CalendarEntries;
031 import org.kuali.hr.time.service.base.TkServiceLocator;
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.rice.core.api.config.property.ConfigContext;
037 import org.kuali.rice.kew.api.KewApiServiceLocator;
038 import org.kuali.rice.kew.api.WorkflowDocument;
039 import org.kuali.rice.kew.api.WorkflowDocumentFactory;
040 import org.kuali.rice.kew.api.exception.WorkflowException;
041 import org.kuali.rice.kew.api.note.Note;
042 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
043 import org.kuali.rice.kim.api.identity.principal.Principal;
044 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
045 import org.kuali.rice.krad.service.KRADServiceLocator;
046
047 import java.math.BigDecimal;
048 import java.util.Date;
049 import java.util.List;
050
051 public class LeaveCalendarServiceImpl implements LeaveCalendarService {
052
053 private static final Logger LOG = Logger.getLogger(LeaveCalendarServiceImpl.class);
054
055 private LeaveCalendarDao leaveCalendarDao;
056
057 @Override
058 public LeaveCalendarDocument getLeaveCalendarDocument(String documentId) {
059 LeaveCalendarDocument lcd = null;
060 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
061
062 if (lcdh != null) {
063 lcd = new LeaveCalendarDocument(lcdh);
064 CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(lcdh.getPrincipalId(), lcdh.getEndDate(), LMConstants.LEAVE_CALENDAR_TYPE);
065 lcd.setCalendarEntry(pce);
066 } else {
067 LOG.error("Could not find LeaveCalendarDocumentHeader for DocumentID: " + documentId);
068 return null;
069 }
070
071 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(documentId);
072 lcd.setLeaveBlocks(leaveBlocks);
073
074 // Fetching assignments
075 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(lcdh.getPrincipalId(), lcd.getCalendarEntry());
076 lcd.setAssignments(assignments);
077
078 return lcd;
079 }
080
081 @Override
082 public LeaveCalendarDocument openLeaveCalendarDocument(String principalId, CalendarEntries calEntry) throws WorkflowException {
083 LeaveCalendarDocument doc;
084
085 Date begin = calEntry.getBeginPeriodDateTime();
086 Date end = calEntry.getEndPeriodDateTime();
087
088 LeaveCalendarDocumentHeader header = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, begin, end);
089 if (header == null) {
090 EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
091 String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : StringUtils.EMPTY;
092 String beginDateString = TKUtils.formatDate(new java.sql.Date(begin.getTime()));
093 String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime()));
094 String leaveCalendarDocumentTitle = LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + beginDateString + "-" + endDateString;
095
096 doc = initiateWorkflowDocument(principalId, begin, end, calEntry, LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE, leaveCalendarDocumentTitle);
097 } else {
098 doc = getLeaveCalendarDocument(header.getDocumentId());
099 }
100 if (doc != null) {
101 doc.setCalendarEntry(calEntry);
102 }
103 // TODO: need to set the summary
104 return doc;
105 }
106
107 //Should only create leave calendar document if active jobs were found with flsa elig = no and ben elg = yes
108 public boolean shouldCreateLeaveDocument(String principalId, CalendarEntries calEntry){
109 if (StringUtils.isEmpty(principalId) || calEntry == null) {
110 return false;
111 }
112
113 boolean isPlanningCalendar = TkServiceLocator.getLeaveCalendarService().isLeavePlanningCalendar(principalId, calEntry.getBeginPeriodDateTime(), calEntry.getEndPeriodDateTime());
114 if (isPlanningCalendar) {
115 return false;
116 }
117
118 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByPayEntry(principalId, calEntry);
119 List<Assignment> results = TkServiceLocator.getAssignmentService().filterAssignments(assignments, TkConstants.FLSA_STATUS_EXEMPT, true);
120 return CollectionUtils.isNotEmpty(results);
121 }
122
123 protected LeaveCalendarDocument initiateWorkflowDocument(String principalId, Date payBeginDate, Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException {
124 LeaveCalendarDocument leaveCalendarDocument = null;
125 WorkflowDocument workflowDocument = null;
126
127 workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title);
128
129 String status = workflowDocument.getStatus().getCode();
130 LeaveCalendarDocumentHeader documentHeader = new LeaveCalendarDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status);
131
132 documentHeader.setDocumentId(workflowDocument.getDocumentId());
133 documentHeader.setDocumentStatus(TkConstants.ROUTE_STATUS.INITIATED);
134
135 KRADServiceLocator.getBusinessObjectService().save(documentHeader);
136
137 leaveCalendarDocument = new LeaveCalendarDocument(documentHeader);
138 leaveCalendarDocument.setCalendarEntry(calendarEntries);
139 loadLeaveCalendarDocumentData(leaveCalendarDocument, principalId, calendarEntries);
140 TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(leaveCalendarDocument, payEndDate);
141
142 updateLeaveBlockDocumentIds(principalId, payBeginDate, payEndDate, workflowDocument.getDocumentId());
143
144 updatePlannedLeaveBlocks(principalId, payBeginDate, payEndDate);
145
146 return leaveCalendarDocument;
147 }
148
149 private void updateLeaveBlockDocumentIds(String principalId, Date beginDate, Date endDate, String documentId) {
150 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
151
152 for (LeaveBlock leaveBlock : leaveBlocks) {
153 leaveBlock.setDocumentId(documentId);
154 }
155
156 TkServiceLocator.getLeaveBlockService().saveLeaveBlocks(leaveBlocks);
157 }
158
159 private void updatePlannedLeaveBlocks(String principalId, Date beginDate, Date endDate) {
160 String batchUserPrincipalId = getBatchUserPrincipalId();
161
162 if (batchUserPrincipalId != null) {
163 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
164
165 for (LeaveBlock leaveBlock : leaveBlocks) {
166 if (StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.PLANNED)
167 || StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.DEFERRED)) {
168 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
169 } else if (StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.REQUESTED)) {
170 if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.DELETE)) {
171 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
172 } else if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.APPROVE)) {
173 List<LeaveRequestDocument> leaveRequestDocuments = TkServiceLocator.getLeaveRequestDocumentService().getLeaveRequestDocumentsByLeaveBlockId(leaveBlock.getLmLeaveBlockId());
174 for (LeaveRequestDocument leaveRequestDocument : leaveRequestDocuments) {
175 TkServiceLocator.getLeaveRequestDocumentService().suBlanketApproveLeave(leaveRequestDocument.getDocumentNumber(), batchUserPrincipalId);
176 }
177 }
178 }
179 }
180 } else {
181 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
182 LOG.error("Could not update leave request blocks due to missing batch user " + principalName);
183 }
184 }
185
186 private String getBatchUserPrincipalId() {
187 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
188 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName);
189 return principal == null ? null : principal.getPrincipalId();
190 }
191
192 private String getInitiateLeaveRequestAction() {
193 return ConfigContext.getCurrentContextConfig().getProperty(LMConstants.INITIATE_LEAVE_REQUEST_ACTION);
194 }
195
196 /**
197 * Preload the document data. It preloads:
198 * - LeaveBlocks on the document.
199 * @param ldoc
200 * @param principalId
201 * @param calEntry
202 */
203 protected void loadLeaveCalendarDocumentData(LeaveCalendarDocument ldoc, String principalId, CalendarEntries calEntry) {
204 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(ldoc.getDocumentId());
205 ldoc.setLeaveBlocks(leaveBlocks);
206 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calEntry);
207 ldoc.setAssignments(assignments);
208 }
209
210 public LeaveCalendarDao getLeaveCalendarDao() {
211 return leaveCalendarDao;
212 }
213
214 public void setLeaveCalendarDao(LeaveCalendarDao leaveCalendarDao) {
215 this.leaveCalendarDao = leaveCalendarDao;
216 }
217
218 @Override
219 public LeaveCalendarDocument getLeaveCalendarDocument(
220 String principalId, CalendarEntries calendarEntry) {
221 LeaveCalendarDocument leaveCalendarDocument = new LeaveCalendarDocument(calendarEntry);
222 LeaveCalendarDocumentHeader lcdh = new LeaveCalendarDocumentHeader();
223 lcdh.setBeginDate(calendarEntry.getBeginPeriodDateTime());
224 lcdh.setEndDate(calendarEntry.getEndPeriodDateTime());
225 leaveCalendarDocument.setDocumentHeader(lcdh);
226 // Fetching assignments
227 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calendarEntry);
228 leaveCalendarDocument.setAssignments(assignments);
229 return leaveCalendarDocument;
230 }
231
232 @Override
233 public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
234 leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.ROUTE, principalId, leaveCalendarDocument);
235 }
236
237 @Override
238 public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) {
239 leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument);
240 }
241
242 @Override
243 public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
244 leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, leaveCalendarDocument);
245 }
246
247 @Override
248 public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) {
249 leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument);
250 }
251
252 @Override
253 public void disapproveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
254 leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, leaveCalendarDocument);
255 }
256
257 public boolean isReadyToApprove(LeaveCalendarDocument document) {
258 if (document == null) {
259 return false;
260 }
261 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
262 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
263 leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
264 document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
265 for(LeaveBlock lb : leaveBlocks) {
266 if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) &&
267 !StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED))
268 return false;
269 }
270 // check if there are any pending calendars are there
271 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getMinBeginDatePendingLeaveCalendar(document.getPrincipalId());
272 if (lcdh != null){ //if there were any pending document
273 //check to see if it's before the current document. if it is, then this document is not approvable.
274 if (TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(document.getDocumentId()).getBeginDate().compareTo(lcdh.getEndDate()) >= 0){
275 return false;
276 }
277 }
278
279 return true;
280 /* List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(),
281 document.getCalendarEntry().getBeginPeriodDate(),
282 document.getCalendarEntry().getEndPeriodDate());
283 if (!CollectionUtils.isEmpty(balanceTransfers)) {
284 for(BalanceTransfer balanceTransfer : balanceTransfers) {
285 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
286 return false;
287 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus())
288 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) {
289 return false;
290 }
291 }
292 }
293 List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(),
294 document.getCalendarEntry().getBeginPeriodDate(),
295 document.getCalendarEntry().getEndPeriodDate());
296 if (!CollectionUtils.isEmpty(leavePayouts)) {
297 for(LeavePayout payout : leavePayouts) {
298 if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
299 return false;
300 if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus())
301 && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) {
302 return false;
303 }
304 }
305 }
306 return true;*/
307 }
308
309 protected void leaveCalendarDocumentAction(String action, String principalId, LeaveCalendarDocument leaveCalendarDocument) {
310 WorkflowDocument wd = null;
311 if (leaveCalendarDocument != null) {
312 String rhid = leaveCalendarDocument.getDocumentId();
313 wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
314
315 if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
316 wd.route("Routing for Approval");
317 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
318 Note.Builder builder = Note.Builder.create(rhid, principalId);
319 builder.setCreateDate(new DateTime());
320 builder.setText("Routed via Employee Approval batch job");
321 KewApiServiceLocator.getNoteService().createNote(builder.build());
322
323 wd.route("Batch job routing leave calendar");
324 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) {
325 if (TKUser.getCurrentTargetRoles().isSystemAdmin() &&
326 !TKUser.getCurrentTargetRoles().isApproverForTimesheet(leaveCalendarDocument)) {
327 wd.superUserBlanketApprove("Superuser approving timesheet.");
328 } else {
329 wd.approve("Approving timesheet.");
330 }
331 } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
332 Note.Builder builder = Note.Builder.create(rhid, principalId);
333 builder.setCreateDate(new DateTime());
334 builder.setText("Approved via Supervisor Approval batch job");
335 KewApiServiceLocator.getNoteService().createNote(builder.build());
336
337 wd.superUserBlanketApprove("Batch job approving leave calendar");
338 } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
339 if (TKUser.getCurrentTargetRoles().isSystemAdmin()
340 && !TKUser.getCurrentTargetRoles().isApproverForTimesheet(leaveCalendarDocument)) {
341 wd.superUserDisapprove("Superuser disapproving timesheet.");
342 } else {
343 wd.disapprove("Disapproving timesheet.");
344 }
345 }
346 }
347 }
348
349 public boolean isLeavePlanningCalendar(String principalId, Date beginDate, Date endDate) {
350 Date today = new Date();
351
352 List<Job> jobs = TkServiceLocator.getJobService().getJobs(principalId, endDate);
353 for (Job job : jobs) {
354 // Check for Leave eligibility.
355 if (job.isEligibleForLeave()) {
356 // Check for Time (FLSA nonexempt) jobs. If one exists, then the Leave Calendar is always a Leave Planning Calendar
357 if (job.getFlsaStatus().equalsIgnoreCase(TkConstants.FLSA_STATUS_NON_EXEMPT)) {
358 return true;
359 } else {
360 // If leave eligible and FLSA exempt, then report leave in the Leave Calendar. Use the date to determine Planning vs Recording Calendars.
361 if ( beginDate.after(today) ) {
362 // future period, this is a Planning Calendar.
363 return true;
364 } else {
365 // not a future period, this is a Reporting Calendar.
366 return false;
367 }
368 }
369 } else {
370 // not leave eligible
371 return false;
372 }
373 }
374 return false;
375 }
376
377 }
378