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.detail.web;
017
018 import java.math.BigDecimal;
019 import java.sql.Date;
020 import java.sql.Timestamp;
021 import java.text.SimpleDateFormat;
022 import java.util.ArrayList;
023 import java.util.Collection;
024 import java.util.Collections;
025 import java.util.HashMap;
026 import java.util.HashSet;
027 import java.util.List;
028 import java.util.Map;
029 import java.util.Map.Entry;
030 import java.util.Properties;
031 import java.util.Set;
032
033 import javax.servlet.http.HttpServletRequest;
034 import javax.servlet.http.HttpServletResponse;
035
036 import org.apache.commons.collections.MapUtils;
037 import org.apache.commons.lang.StringUtils;
038 import org.apache.commons.lang.SystemUtils;
039 import org.apache.struts.action.ActionForm;
040 import org.apache.struts.action.ActionForward;
041 import org.apache.struts.action.ActionMapping;
042 import org.joda.time.DateTime;
043 import org.joda.time.Interval;
044 import org.joda.time.LocalDateTime;
045 import org.kuali.hr.lm.LMConstants;
046 import org.kuali.hr.lm.accrual.AccrualCategory;
047 import org.kuali.hr.lm.accrual.AccrualCategoryRule;
048 import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
049 import org.kuali.hr.lm.balancetransfer.validation.BalanceTransferValidationUtils;
050 import org.kuali.hr.lm.leaveblock.LeaveBlock;
051 import org.kuali.hr.lm.leavecalendar.validation.LeaveCalendarValidationUtil;
052 import org.kuali.hr.lm.util.LeaveBlockAggregate;
053 import org.kuali.hr.time.assignment.Assignment;
054 import org.kuali.hr.time.calendar.Calendar;
055 import org.kuali.hr.time.calendar.CalendarEntries;
056 import org.kuali.hr.time.calendar.TkCalendar;
057 import org.kuali.hr.time.earncode.EarnCode;
058 import org.kuali.hr.time.principal.PrincipalHRAttributes;
059 import org.kuali.hr.time.roles.TkUserRoles;
060 import org.kuali.hr.time.roles.UserRoles;
061 import org.kuali.hr.time.service.base.TkServiceLocator;
062 import org.kuali.hr.time.timeblock.TimeBlock;
063 import org.kuali.hr.time.timeblock.TimeBlockHistory;
064 import org.kuali.hr.time.timesheet.TimesheetDocument;
065 import org.kuali.hr.time.timesheet.web.TimesheetAction;
066 import org.kuali.hr.time.timesheet.web.TimesheetActionForm;
067 import org.kuali.hr.time.timesummary.AssignmentColumn;
068 import org.kuali.hr.time.timesummary.AssignmentRow;
069 import org.kuali.hr.time.timesummary.EarnCodeSection;
070 import org.kuali.hr.time.timesummary.EarnGroupSection;
071 import org.kuali.hr.time.timesummary.TimeSummary;
072 import org.kuali.hr.time.util.TKContext;
073 import org.kuali.hr.time.util.TKUser;
074 import org.kuali.hr.time.util.TKUtils;
075 import org.kuali.hr.time.util.TkConstants;
076 import org.kuali.hr.time.util.TkTimeBlockAggregate;
077 import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
078 import org.kuali.rice.kew.api.document.DocumentStatus;
079 import org.kuali.rice.kew.service.KEWServiceLocator;
080 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
081 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
082 import org.kuali.rice.krad.exception.AuthorizationException;
083 import org.kuali.rice.krad.util.GlobalVariables;
084 import org.kuali.rice.krad.util.ObjectUtils;
085 import org.kuali.rice.krad.util.UrlFactory;
086
087 public class TimeDetailAction extends TimesheetAction {
088
089 @Override
090 protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
091 super.checkTKAuthorization(form, methodToCall); // Checks for read access first.
092 UserRoles roles = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId());
093 TimesheetDocument doc = TKContext.getCurrentTimesheetDocument();
094
095 // Check for write access to Timeblock.
096 if (StringUtils.equals(methodToCall, "addTimeBlock") || StringUtils.equals(methodToCall, "deleteTimeBlock") || StringUtils.equals(methodToCall, "updateTimeBlock")) {
097 if (!roles.isDocumentWritable(doc)) {
098 throw new AuthorizationException(roles.getPrincipalId(), "TimeDetailAction", "");
099 }
100 }
101 }
102
103 @Override
104 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
105 ActionForward forward = super.execute(mapping, form, request, response);
106 if (forward.getRedirect()) {
107 return forward;
108 }
109 if (TKContext.getCurrentTimesheetDocument() == null) {
110 return forward;
111 }
112 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
113 tdaf.setAssignmentDescriptions(TkServiceLocator.getAssignmentService().getAssignmentDescriptions(TKContext.getCurrentTimesheetDocument(), false));
114
115 // Handle User preference / timezone information (pushed up from TkCalendar to avoid duplication)
116 // Set calendar
117 CalendarEntries payCalendarEntry = tdaf.getPayCalendarDates();
118 Calendar payCalendar = TkServiceLocator.getCalendarService().getCalendar(payCalendarEntry != null ? payCalendarEntry.getHrCalendarId() : null);
119
120 //List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(Long.parseLong(tdaf.getTimesheetDocument().getDocumentHeader().getTimesheetDocumentId()));
121 List<TimeBlock> timeBlocks = TKContext.getCurrentTimesheetDocument().getTimeBlocks();
122 // get leave blocks
123 List<Assignment> timeAssignments = TKContext.getCurrentTimesheetDocument().getAssignments();
124 List<String> tAssignmentKeys = new ArrayList<String>();
125 for(Assignment assign : timeAssignments) {
126 tAssignmentKeys.add(assign.getAssignmentKey());
127 }
128
129 Date endDate = payCalendarEntry.getEndPeriodDate();
130 LocalDateTime tempLocalDate = payCalendarEntry.getEndLocalDateTime();
131 // if the end date of the calendar entry is the beginning time of a day, use the previous day as the end date to retrieve blocks
132 if(tempLocalDate.getHourOfDay() == 0 && tempLocalDate.getMinuteOfHour() == 0 && tempLocalDate.getMillisOfSecond() == 0) {
133 endDate = new java.sql.Date(tempLocalDate.minusDays(1).toDate().getTime());
134 }
135
136 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getCurrentTimesheetDocument().getPrincipalId(),
137 payCalendarEntry.getBeginPeriodDate(), endDate, tAssignmentKeys);
138 List<LeaveBlock> balanceTransferLeaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(TKContext.getCurrentTimesheetDocument().getPrincipalId(),
139 payCalendarEntry.getBeginPeriodDate(), payCalendarEntry.getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
140
141 // List<String> warnings = tdaf.getWarnings();
142 /* List<String> infoMessages = tdaf.getInfoMessages();
143 List<String> warningMessages = tdaf.getWarningMessages();
144 List<String> actionMessages = tdaf.getActionMessages();*/
145
146 Map<String, Set<String>> allMessages = LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(balanceTransferLeaveBlocks);
147 Map<String, Set<String>> transactionalMessages = LeaveCalendarValidationUtil.validatePendingTransactions(TKContext.getTargetPrincipalId(),
148 payCalendarEntry.getBeginPeriodDate(), payCalendarEntry.getEndPeriodDate());
149
150 List<String> warnings = new ArrayList<String>();
151 //placing the following "validation" further down in this method will overwrite messages added prior to this call.
152 //allMessages.putAll(LeaveCalendarValidationUtil.validatePendingTransactions(viewPrincipal, payCalendarEntry.getBeginPeriodDate(), payCalendarEntry.getEndPeriodDate()));
153
154 // add warning messages based on max carry over balances for each accrual category for non-exempt leave users
155 String viewPrincipal = TKContext.getTargetPrincipalId();
156 List<BalanceTransfer> losses = new ArrayList<BalanceTransfer>();
157 if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(viewPrincipal, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
158 PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(viewPrincipal, payCalendarEntry.getEndPeriodDate());
159
160 Interval calendarInterval = new Interval(payCalendarEntry.getBeginPeriodDate().getTime(), payCalendarEntry.getEndPeriodDate().getTime());
161 Map<String,Set<LeaveBlock>> maxBalInfractions = new HashMap<String,Set<LeaveBlock>>();
162
163 if(ObjectUtils.isNotNull(principalCalendar)) {
164 maxBalInfractions = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(payCalendarEntry, viewPrincipal);
165
166 for(Entry<String,Set<LeaveBlock>> entry : maxBalInfractions.entrySet()) {
167 for(LeaveBlock lb : entry.getValue()) {
168 if(calendarInterval.contains(lb.getLeaveDate().getTime())) {
169 AccrualCategory accrualCat = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(lb.getAccrualCategory(), lb.getLeaveDate());
170 AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(lb.getAccrualCategoryRuleId());
171 if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
172 DateTime aDate = null;
173 if(StringUtils.equals(aRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
174 aDate = TkServiceLocator.getLeavePlanService().getRolloverDayOfLeavePlan(principalCalendar.getLeavePlan(), lb.getLeaveDate());
175 }
176 else {
177 Calendar cal = TkServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(viewPrincipal, lb.getLeaveDate(), true);
178 CalendarEntries leaveEntry = TkServiceLocator.getCalendarEntriesService().getCurrentCalendarEntriesByCalendarId(cal.getHrCalendarId(), lb.getLeaveDate());
179 aDate = new DateTime(leaveEntry.getEndPeriodDate());
180 }
181 aDate = aDate.minusDays(1);
182 if(calendarInterval.contains(aDate.getMillis()) && aDate.toDate().compareTo(payCalendarEntry.getEndPeriodDate()) <= 0) {
183 //may want to calculate summary for all rows, displayable or not, and determine displayability via tags.
184 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(aRule.getLmAccrualCategoryId());
185 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(viewPrincipal, accrualCategory, lb.getLeaveDate());
186
187 BalanceTransfer loseTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(viewPrincipal, lb.getAccrualCategoryRuleId(), accruedBalance, lb.getLeaveDate());
188 boolean valid = BalanceTransferValidationUtils.validateTransfer(loseTransfer);
189 if(valid)
190 losses.add(loseTransfer);
191 }
192 }
193
194
195 // accrual categories within the leave plan that are hidden from the leave summary WILL appear.
196 String message = "You have exceeded the maximum balance limit for '" + accrualCat.getAccrualCategory() + "' as of " + lb.getLeaveDate() + ". "+
197 "Depending upon the accrual category rules, leave over this limit may be forfeited.";
198 // leave blocks are sorted in getMaxBalanceViolations() method, so we just take the one with the earliest leave date for an accrual category.
199 if(!StringUtils.contains(allMessages.get("warningMessages").toString(),"You have exceeded the maximum balance limit for '"+accrualCat.getAccrualCategory())) {
200 allMessages.get("warningMessages").add(message);
201 }
202 }
203 }
204 }
205 }
206 tdaf.setForfeitures(losses);
207
208 if (principalCalendar != null) {
209 Calendar calendar = TkServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(viewPrincipal, tdaf.getEndPeriodDateTime(), true);
210
211 if (calendar != null) {
212 List<CalendarEntries> leaveCalendarEntries = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesEndingBetweenBeginAndEndDate(calendar.getHrCalendarId(), tdaf.getBeginPeriodDateTime(), tdaf.getEndPeriodDateTime());
213
214 List<AccrualCategory> accrualCategories = TkServiceLocator.getAccrualCategoryService().getActiveLeaveAccrualCategoriesForLeavePlan(principalCalendar.getLeavePlan(), new java.sql.Date(tdaf.getEndPeriodDateTime().getTime()));
215 for (AccrualCategory accrualCategory : accrualCategories) {
216 if (TkServiceLocator.getAccrualCategoryMaxCarryOverService().exceedsAccrualCategoryMaxCarryOver(accrualCategory.getAccrualCategory(), viewPrincipal, leaveCalendarEntries, tdaf.getEndPeriodDateTime())) {
217 String message = "Your pending leave balance is greater than the annual max carry over for accrual category '" + accrualCategory.getAccrualCategory() + "' and upon approval, the excess balance will be lost.";
218 if (!warnings.contains(message)) {
219 warnings.add(message);
220 }
221 }
222 }
223 }
224 }
225 }
226 /* warnings.addAll(allMessages.get("infoMessages"));
227 warnings.addAll(allMessages.get("actionMessages"));
228 warnings.addAll(allMessages.get("warningMessages"));*/
229 allMessages.get("warningMessages").addAll(warnings);
230
231 List<String> infoMessages = tdaf.getInfoMessages();
232 infoMessages.addAll(allMessages.get("infoMessages"));
233 infoMessages.addAll(transactionalMessages.get("infoMessages"));
234
235 List<String> warningMessages = tdaf.getWarningMessages();
236 warningMessages.addAll(allMessages.get("warningMessages"));
237 warningMessages.addAll(transactionalMessages.get("warningMessages"));
238
239 List<String> actionMessages = tdaf.getActionMessages();
240 actionMessages.addAll(allMessages.get("actionMessages"));
241 actionMessages.addAll(transactionalMessages.get("actionMessages"));
242
243 tdaf.setInfoMessages(infoMessages);
244 tdaf.setWarningMessages(warningMessages);
245 tdaf.setActionMessages(actionMessages);
246
247 this.assignStypeClassMapForTimeSummary(tdaf,timeBlocks, leaveBlocks);
248
249 List<Interval> intervals = TKUtils.getFullWeekDaySpanForCalendarEntry(payCalendarEntry);
250 LeaveBlockAggregate lbAggregate = new LeaveBlockAggregate(leaveBlocks, payCalendarEntry, intervals);
251 TkTimeBlockAggregate tbAggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry, payCalendar, true,intervals);
252 // use both time aggregate and leave aggregate to populate the calendar
253 TkCalendar cal = TkCalendar.getCalendar(tbAggregate, lbAggregate);
254 cal.assignAssignmentStyle(tdaf.getAssignStyleClassMap());
255 tdaf.setTkCalendar(cal);
256
257 this.populateCalendarAndPayPeriodLists(request, tdaf);
258
259 tdaf.setTimeBlockString(ActionFormUtils.getTimeBlocksJson(tbAggregate.getFlattenedTimeBlockList()));
260 tdaf.setLeaveBlockString(ActionFormUtils.getLeaveBlocksJson(lbAggregate.getFlattenedLeaveBlockList()));
261
262 tdaf.setOvertimeEarnCodes(TkServiceLocator.getEarnCodeService().getOvertimeEarnCodesStrs(TKContext.getCurrentTimesheetDocument().getAsOfDate()));
263
264 if (StringUtils.equals(TKContext.getCurrentTimesheetDocument().getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())) {
265 tdaf.setWorkingOnItsOwn("true");
266 }
267
268 tdaf.setDocEditable("false");
269 if (TKUser.isSystemAdmin()) {
270 tdaf.setDocEditable("true");
271 } else {
272 String documentStatus = TKContext.getCurrentTimesheetDocument().getDocumentHeader().getDocumentStatus();
273 boolean docFinal = DocumentStatus.FINAL.getCode().equals(documentStatus)
274 || DocumentStatus.CANCELED.getCode().equals(documentStatus)
275 || DocumentStatus.DISAPPROVED.getCode().equals(documentStatus);
276 if (!docFinal) {
277 if(StringUtils.equals(TKContext.getCurrentTimesheetDocument().getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
278 || TKUser.isSystemAdmin()
279 || TKUser.isLocationAdmin()
280 || TKUser.isDepartmentAdmin()
281 || TKUser.isReviewer()
282 || TKUser.isApprover()) {
283 tdaf.setDocEditable("true");
284 }
285
286 //if the timesheet has been approved by at least one of the approvers, the employee should not be able to edit it
287 if (StringUtils.equals(TKContext.getCurrentTimesheetDocument().getPrincipalId(), GlobalVariables.getUserSession().getPrincipalId())
288 && TKContext.getCurrentTimesheetDocument().getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE)) {
289 Collection actions = KEWServiceLocator.getActionTakenService().findByDocIdAndAction(TKContext.getCurrentTimesheetDocument().getDocumentHeader().getDocumentId(), TkConstants.DOCUMENT_ACTIONS.APPROVE);
290 if(!actions.isEmpty()) {
291 tdaf.setDocEditable("false");
292 }
293 }
294 }
295 }
296
297 return forward;
298 }
299
300 // use lists of time blocks and leave blocks to build the style class map and assign css class to associated summary rows
301 private void assignStypeClassMapForTimeSummary(TimeDetailActionForm tdaf, List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks) throws Exception {
302 TimeSummary ts = TkServiceLocator.getTimeSummaryService().getTimeSummary(TKContext.getCurrentTimesheetDocument());
303 tdaf.setAssignStyleClassMap(ActionFormUtils.buildAssignmentStyleClassMap(timeBlocks, leaveBlocks));
304 Map<String, String> aMap = tdaf.getAssignStyleClassMap();
305 // set css classes for each assignment row
306 for (EarnGroupSection earnGroupSection : ts.getSections()) {
307 for (EarnCodeSection section : earnGroupSection.getEarnCodeSections()) {
308 for (AssignmentRow assignRow : section.getAssignmentsRows()) {
309 String assignmentCssStyle = MapUtils.getString(aMap, assignRow.getAssignmentKey());
310 assignRow.setCssClass(assignmentCssStyle);
311 for (AssignmentColumn assignmentColumn : assignRow.getAssignmentColumns()) {
312 assignmentColumn.setCssClass(assignmentCssStyle);
313 }
314 }
315 }
316
317 }
318 tdaf.setTimeSummary(ts);
319 // ActionFormUtils.validateHourLimit(tdaf);
320 ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
321 ActionFormUtils.addUnapprovedIPWarningFromClockLog(tdaf);
322 }
323
324 private void populateCalendarAndPayPeriodLists(HttpServletRequest request, TimeDetailActionForm tdaf) {
325 String viewPrincipal = TKContext.getTargetPrincipalId();
326 List<TimesheetDocumentHeader> documentHeaders = (List<TimesheetDocumentHeader>) TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeadersForPrincipalId(TKContext.getTargetPrincipalId());
327 SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
328 if(tdaf.getCalendarYears().isEmpty()) {
329 // get calendar year drop down list contents
330 Set<String> yearSet = new HashSet<String>();
331
332 for(TimesheetDocumentHeader tdh : documentHeaders) {
333 yearSet.add(sdf.format(tdh.getBeginDate()));
334 }
335 List<String> yearList = new ArrayList<String>(yearSet);
336 Collections.sort(yearList);
337 Collections.reverse(yearList); // newest on top
338 tdaf.setCalendarYears(yearList);
339 }
340 // if selected calendar year is passed in
341 if(request.getParameter("selectedCY")!= null) {
342 tdaf.setSelectedCalendarYear(request.getParameter("selectedCY").toString());
343 }
344 // if there is no selected calendr year, use the year of current pay calendar entry
345 if(StringUtils.isEmpty(tdaf.getSelectedCalendarYear())) {
346 tdaf.setSelectedCalendarYear(sdf.format(tdaf.getPayCalendarDates().getBeginPeriodDate()));
347 }
348 if(tdaf.getPayPeriodsMap().isEmpty()) {
349 List<CalendarEntries> payPeriodList = new ArrayList<CalendarEntries>();
350 for(TimesheetDocumentHeader tdh : documentHeaders) {
351 if(sdf.format(tdh.getBeginDate()).equals(tdaf.getSelectedCalendarYear())) {
352 CalendarEntries pe = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
353 //CalendarEntries pe = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(tdh.getBeginDate(), tdh.getEndDate());
354 payPeriodList.add(pe);
355 }
356 }
357 tdaf.setPayPeriodsMap(ActionFormUtils.getPayPeriodsMap(payPeriodList, viewPrincipal));
358 }
359 if(request.getParameter("selectedPP")!= null) {
360 tdaf.setSelectedPayPeriod(request.getParameter("selectedPP").toString());
361 }
362 if(StringUtils.isEmpty(tdaf.getSelectedPayPeriod())) {
363 tdaf.setSelectedPayPeriod(tdaf.getPayCalendarDates().getHrCalendarEntriesId());
364 }
365 }
366
367
368 /**
369 * This method involves creating an object-copy of every TimeBlock on the
370 * time sheet for overtime re-calculation.
371 *
372 * @throws Exception
373 */
374 public ActionForward deleteTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
375 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
376
377 String principalId = TKContext.getPrincipalId();
378 String targetPrincipalId = TKContext.getTargetPrincipalId();
379 String documentId = tdaf.getDocumentId();
380
381 //Grab timeblock to be deleted from form
382 List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
383 TimeBlock deletedTimeBlock = null;
384 for (TimeBlock tb : timeBlocks) {
385 if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
386 deletedTimeBlock = tb;
387 break;
388 }
389 }
390 if (deletedTimeBlock == null) {
391 return mapping.findForward("basic");
392 }
393 //Remove from the list of timeblocks
394 List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(tdaf.getTimesheetDocument().getTimeBlocks().size());
395 for (TimeBlock b : tdaf.getTimesheetDocument().getTimeBlocks()) {
396 referenceTimeBlocks.add(b.copy());
397 }
398
399 // simple pointer, for clarity
400 List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
401 newTimeBlocks.remove(deletedTimeBlock);
402
403 //Delete timeblock
404 TkServiceLocator.getTimeBlockService().deleteTimeBlock(deletedTimeBlock);
405 // Add a row to the history table
406 TimeBlockHistory tbh = new TimeBlockHistory(deletedTimeBlock);
407 tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
408 TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
409
410 List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
411 List<String> assignmentKeys = new ArrayList<String>();
412 for (Assignment assignment : assignments) {
413 assignmentKeys.add(assignment.getAssignmentKey());
414 }
415 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
416
417 //reset time block
418 TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
419 TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
420 TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, TKContext.getPrincipalId());
421
422 generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
423
424 return mapping.findForward("basic");
425 }
426
427 /**
428 * This method involves creating an object-copy of every TimeBlock on the
429 * time sheet for overtime re-calculation.
430 * Based on the form's timeBlockId or leaveBlockId, existing Time/Leave blocks will be deleted and new ones created
431 *
432 * @throws Exception
433 */
434 public ActionForward addTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
435 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
436
437 String principalId = TKContext.getPrincipalId();
438 String targetPrincipalId = TKContext.getTargetPrincipalId();
439 String documentId = tdaf.getDocumentId();
440
441 if(StringUtils.isNotEmpty(tdaf.getTkTimeBlockId())) {
442 // the user is changing an existing time block, so need to delete this time block
443 this.removeOldTimeBlock(tdaf);
444 } else if(StringUtils.isNotEmpty(tdaf.getLmLeaveBlockId())) {
445 // the user is changing an existing leave block, so need to delete this leave block
446 this.removeOldLeaveBlock(tdaf.getLmLeaveBlockId());
447 }
448 if(StringUtils.isNotEmpty(tdaf.getSelectedEarnCode())) {
449 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument().getAsOfDate());
450 if(ec != null && (ec.getLeavePlan() != null || ec.getEligibleForAccrual().equals("N"))) { // leave blocks changes
451 this.changeLeaveBlocks(tdaf);
452 } else { // time blocks changes
453 this.changeTimeBlocks(tdaf);
454 }
455 }
456
457 // ActionFormUtils.validateHourLimit(tdaf);
458 ActionFormUtils.addWarningTextFromEarnGroup(tdaf);
459
460 generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
461
462 return mapping.findForward("basic");
463 }
464
465 private void removeOldTimeBlock(TimeDetailActionForm tdaf) {
466 if (tdaf.getTkTimeBlockId() != null) {
467 TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
468 if (tb != null) {
469 TimeBlockHistory tbh = new TimeBlockHistory(tb);
470 TkServiceLocator.getTimeBlockService().deleteTimeBlock(tb);
471
472 // mark the original timeblock as deleted in the history table
473 tbh.setActionHistory(TkConstants.ACTIONS.DELETE_TIME_BLOCK);
474 TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
475
476 // delete the timeblock from the memory
477 tdaf.getTimesheetDocument().getTimeBlocks().remove(tb);
478 }
479 }
480 }
481
482 private void removeOldLeaveBlock(String lbId) {
483 if (lbId != null) {
484 LeaveBlock lb = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
485 if (lb != null) {
486 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(lbId, TKContext.getPrincipalId());
487 }
488 }
489 }
490
491 // add/update leave blocks
492 private void changeLeaveBlocks(TimeDetailActionForm tdaf) {
493 DateTime beginDate = null;
494 DateTime endDate = null;
495
496 if(tdaf.getStartTime() != null && tdaf.getEndTime() != null) {
497 beginDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getStartDate(), tdaf.getStartTime()));
498 endDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getEndDate(), tdaf.getEndTime()));
499 } else {
500 beginDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getStartDate()));
501 endDate = new DateTime(TKUtils.convertDateStringToTimestamp(tdaf.getEndDate()));
502 }
503
504 String selectedEarnCode = tdaf.getSelectedEarnCode();
505 BigDecimal leaveAmount = tdaf.getLeaveAmount();
506
507 String desc = ""; // there's no description field in time calendar pop window
508 String spanningWeeks = tdaf.getSpanningWeeks();
509 Assignment currentAssignment = TkServiceLocator.getAssignmentService().getAssignment(tdaf.getTimesheetDocument(), tdaf.getSelectedAssignment());
510 TkServiceLocator.getLeaveBlockService().addLeaveBlocks(beginDate, endDate, tdaf.getPayCalendarDates(), selectedEarnCode, leaveAmount, desc, currentAssignment,
511 spanningWeeks, LMConstants.LEAVE_BLOCK_TYPE.TIME_CALENDAR, TKContext.getTargetPrincipalId());
512
513 List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
514 List<String> assignmentKeys = new ArrayList<String>();
515 for (Assignment assignment : assignments) {
516 assignmentKeys.add(assignment.getAssignmentKey());
517 }
518 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
519
520 // A bad hack to apply rules to all timeblocks on timesheet
521 List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
522 TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
523 TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks, newTimeBlocks, TKContext.getPrincipalId());
524 }
525
526 // add/update time blocks
527 private void changeTimeBlocks(TimeDetailActionForm tdaf) {
528 Timestamp overtimeBeginTimestamp = null;
529 Timestamp overtimeEndTimestamp = null;
530 boolean isClockLogCreated = false;
531
532 // This is for updating a timeblock or changing
533 // If tkTimeBlockId is not null and the new timeblock is valid, delete the existing timeblock and a new one will be created after submitting the form.
534 if (tdaf.getTkTimeBlockId() != null) {
535 TimeBlock tb = TkServiceLocator.getTimeBlockService().getTimeBlock(tdaf.getTkTimeBlockId());
536 if (tb != null) {
537 isClockLogCreated = tb.getClockLogCreated();
538 if (StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
539 //TODO: This doesn't do anything!!! these variables are never used. Should they be?
540 overtimeBeginTimestamp = tb.getBeginTimestamp();
541 overtimeEndTimestamp = tb.getEndTimestamp();
542 }
543 }
544 // old time block is deleted from addTimeBlock method
545 // this.removeOldTimeBlock(tdaf);
546 }
547
548 Assignment currentAssignment = TkServiceLocator.getAssignmentService().getAssignment(tdaf.getTimesheetDocument(), tdaf.getSelectedAssignment());
549
550
551 // Surgery point - Need to construct a Date/Time with Appropriate Timezone.
552 Timestamp startTime = TKUtils.convertDateStringToTimestamp(tdaf.getStartDate(), tdaf.getStartTime());
553 Timestamp endTime = TKUtils.convertDateStringToTimestamp(tdaf.getEndDate(), tdaf.getEndTime());
554
555 // We need a cloned reference set so we know whether or not to
556 // persist any potential changes without making hundreds of DB calls.
557 List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(tdaf.getTimesheetDocument().getTimeBlocks().size());
558 for (TimeBlock tb : tdaf.getTimesheetDocument().getTimeBlocks()) {
559 referenceTimeBlocks.add(tb.copy());
560 }
561
562 // This is just a reference, for code clarity, the above list is actually
563 // separate at the object level.
564 List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
565 DateTime startTemp = new DateTime(startTime);
566 DateTime endTemp = new DateTime(endTime);
567 // KPME-1446 add spanningweeks to the calls below
568 if (StringUtils.equals(tdaf.getAcrossDays(), "y")
569 && !(endTemp.getDayOfYear() - startTemp.getDayOfYear() <= 1
570 && endTemp.getHourOfDay() == 0)) {
571 List<TimeBlock> timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocksSpanDates(currentAssignment,
572 tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
573 endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()), tdaf.getSpanningWeeks(), TKContext.getPrincipalId());
574 for (TimeBlock tb : timeBlocksToAdd) {
575 if (!newTimeBlocks.contains(tb)) {
576 newTimeBlocks.add(tb);
577 }
578 }
579 } else {
580 List<TimeBlock> timeBlocksToAdd = TkServiceLocator.getTimeBlockService().buildTimeBlocks(currentAssignment,
581 tdaf.getSelectedEarnCode(), tdaf.getTimesheetDocument(), startTime,
582 endTime, tdaf.getHours(), tdaf.getAmount(), isClockLogCreated, Boolean.parseBoolean(tdaf.getLunchDeleted()), TKContext.getPrincipalId());
583 for (TimeBlock tb : timeBlocksToAdd) {
584 if (!newTimeBlocks.contains(tb)) {
585 newTimeBlocks.add(tb);
586 }
587 }
588 }
589
590 //reset time block
591 TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
592
593 // apply overtime pref
594 // I changed start and end times comparison below. it used to be overtimeBeginTimestamp and overtimeEndTimestamp but
595 // for some reason, they're always null because, we have removed the time block before getting here. KPME-2162
596 for (TimeBlock tb : newTimeBlocks) {
597 if (tb.getBeginTimestamp().equals(startTime) && tb.getEndTimestamp().equals(endTime) && StringUtils.isNotEmpty(tdaf.getOvertimePref())) {
598 tb.setOvertimePref(tdaf.getOvertimePref());
599 }
600
601 }
602
603 List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
604 List<String> assignmentKeys = new ArrayList<String>();
605 for (Assignment assignment : assignments) {
606 assignmentKeys.add(assignment.getAssignmentKey());
607 }
608 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
609
610 TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
611 TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, TKContext.getPrincipalId());
612 }
613
614 public ActionForward updateTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
615
616 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
617 Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(tdaf.getTimesheetDocument(), tdaf.getSelectedAssignment());
618
619 //Grab timeblock to be updated from form
620 List<TimeBlock> timeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
621 TimeBlock updatedTimeBlock = null;
622 for (TimeBlock tb : timeBlocks) {
623 if (tb.getTkTimeBlockId().compareTo(tdaf.getTkTimeBlockId()) == 0) {
624 updatedTimeBlock = tb;
625 tb.setJobNumber(assignment.getJobNumber());
626 tb.setWorkArea(assignment.getWorkArea());
627 tb.setTask(assignment.getTask());
628 break;
629 }
630 }
631
632 Set<String> earnCodes = new HashSet<String>();
633 if (updatedTimeBlock != null) {
634 List<EarnCode> validEarnCodes = TkServiceLocator.getEarnCodeService().getEarnCodesForTime(assignment, updatedTimeBlock.getBeginDate(), true);
635 for (EarnCode e : validEarnCodes) {
636 earnCodes.add(e.getEarnCode());
637 }
638 }
639
640 if (updatedTimeBlock != null
641 && earnCodes.contains(updatedTimeBlock.getEarnCode())) {
642 TkServiceLocator.getTimeBlockService().updateTimeBlock(updatedTimeBlock);
643
644 TimeBlockHistory tbh = new TimeBlockHistory(updatedTimeBlock);
645 tbh.setActionHistory(TkConstants.ACTIONS.UPDATE_TIME_BLOCK);
646 TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
647 }
648 tdaf.setMethodToCall("addTimeBlock");
649 return mapping.findForward("basic");
650 }
651
652
653 public ActionForward actualTimeInquiry(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
654 return mapping.findForward("ati");
655 }
656
657 public ActionForward deleteLunchDeduction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
658
659 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
660 String timeHourDetailId = tdaf.getTkTimeHourDetailId();
661 TkServiceLocator.getTimeBlockService().deleteLunchDeduction(timeHourDetailId);
662
663 List<TimeBlock> newTimeBlocks = tdaf.getTimesheetDocument().getTimeBlocks();
664
665 List<Assignment> assignments = tdaf.getTimesheetDocument().getAssignments();
666 List<String> assignmentKeys = new ArrayList<String>();
667 for (Assignment assignment : assignments) {
668 assignmentKeys.add(assignment.getAssignmentKey());
669 }
670 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(TKContext.getTargetPrincipalId(), tdaf.getTimesheetDocument().getAsOfDate(), tdaf.getTimesheetDocument().getDocEndDate(), assignmentKeys);
671
672 TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdaf.getTimesheetDocument().getAsOfDate());
673
674 // KPME-1340
675 TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTimeBlocks, leaveBlocks, tdaf.getPayCalendarDates(), tdaf.getTimesheetDocument(), TKContext.getPrincipalId());
676 TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTimeBlocks);
677 TKContext.getCurrentTimesheetDocument().setTimeBlocks(newTimeBlocks);
678
679 return mapping.findForward("basic");
680 }
681
682 public ActionForward gotoCurrentPayPeriod(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
683 String viewPrincipal = TKUser.getCurrentTargetPersonId();
684 Date currentDate = TKUtils.getTimelessDate(null);
685 CalendarEntries pce = TkServiceLocator.getCalendarService().getCurrentCalendarDates(viewPrincipal, currentDate);
686 TimesheetDocument td = TkServiceLocator.getTimesheetService().openTimesheetDocument(viewPrincipal, pce);
687 setupDocumentOnFormContext((TimesheetActionForm)form, td);
688 return mapping.findForward("basic");
689 }
690
691 //Triggered by changes of pay period drop down list, reload the whole page based on the selected pay period
692 public ActionForward changeCalendarYear(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
693
694 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
695 if(request.getParameter("selectedCY") != null) {
696 tdaf.setSelectedCalendarYear(request.getParameter("selectedCY").toString());
697 }
698 return mapping.findForward("basic");
699 }
700
701 //Triggered by changes of pay period drop down list, reload the whole page based on the selected pay period
702 public ActionForward changePayPeriod(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
703 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
704 if(request.getParameter("selectedPP") != null) {
705 tdaf.setSelectedPayPeriod(request.getParameter("selectedPP").toString());
706 CalendarEntries pce = TkServiceLocator.getCalendarEntriesService()
707 .getCalendarEntries(request.getParameter("selectedPP").toString());
708 if(pce != null) {
709 String viewPrincipal = TKUser.getCurrentTargetPersonId();
710 TimesheetDocument td = TkServiceLocator.getTimesheetService().openTimesheetDocument(viewPrincipal, pce);
711 setupDocumentOnFormContext((TimesheetActionForm)form, td);
712 }
713 }
714 return mapping.findForward("basic");
715 }
716
717 public ActionForward deleteLeaveBlock(ActionMapping mapping,ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
718 TimeDetailActionForm tdaf = (TimeDetailActionForm) form;
719
720 String principalId = TKContext.getPrincipalId();
721 String targetPrincipalId = TKContext.getTargetPrincipalId();
722 String documentId = tdaf.getDocumentId();
723 String leaveBlockId = tdaf.getLmLeaveBlockId();
724
725 LeaveBlock blockToDelete = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
726 if (blockToDelete != null && TkServiceLocator.getPermissionsService().canDeleteLeaveBlock(blockToDelete)) {
727 TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlockId, TKContext.getPrincipalId());
728
729 generateTimesheetChangedNotification(principalId, targetPrincipalId, documentId);
730 }
731
732 // if the leave block is NOT eligible for accrual, rerun accrual service for the leave calendar the leave block is on
733 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(blockToDelete.getEarnCode(), blockToDelete.getLeaveDate());
734 if(ec != null && ec.getEligibleForAccrual().equals("N")) {
735 CalendarEntries ce = TkServiceLocator.getCalendarService()
736 .getCurrentCalendarDatesForLeaveCalendar(blockToDelete.getPrincipalId(), blockToDelete.getLeaveDate());
737 if(ce != null) {
738 TkServiceLocator.getLeaveAccrualService().runAccrual(blockToDelete.getPrincipalId(), ce.getBeginPeriodDate(), ce.getEndPeriodDate(), false);
739 }
740 }
741
742 return mapping.findForward("basic");
743 }
744
745 private void generateTimesheetChangedNotification(String principalId, String targetPrincipalId, String documentId) {
746 if (!StringUtils.equals(principalId, targetPrincipalId)) {
747 EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
748 if (person != null && person.getDefaultName() != null) {
749 String subject = "Timesheet Modification Notice";
750 StringBuilder message = new StringBuilder();
751 message.append("Your Timesheet was changed by ");
752 message.append(person.getDefaultName().getCompositeNameUnmasked());
753 message.append(" on your behalf.");
754 message.append(SystemUtils.LINE_SEPARATOR);
755 message.append(getTimesheetURL(documentId));
756
757 TkServiceLocator.getKPMENotificationService().sendNotification(subject, message.toString(), targetPrincipalId);
758 }
759 }
760 }
761
762 @SuppressWarnings("deprecation")
763 private String getTimesheetURL(String documentId) {
764 Properties params = new Properties();
765 params.put("documentId", documentId);
766 return UrlFactory.parameterizeUrl(getApplicationBaseUrl() + "/TimeDetail.do", params);
767 }
768
769 }