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