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