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.leavepayout.web; 017 018 import java.math.BigDecimal; 019 import java.sql.Date; 020 import java.util.*; 021 022 import org.apache.commons.lang.time.DateUtils; 023 import org.apache.commons.lang3.StringUtils; 024 import org.apache.struts.action.ActionForm; 025 import org.apache.struts.action.ActionForward; 026 import org.apache.struts.action.ActionMapping; 027 import org.apache.struts.action.ActionRedirect; 028 import org.joda.time.Interval; 029 import org.kuali.hr.lm.LMConstants; 030 import org.kuali.hr.lm.accrual.AccrualCategory; 031 import org.kuali.hr.lm.accrual.AccrualCategoryRule; 032 import org.kuali.hr.lm.leavepayout.LeavePayout; 033 import org.kuali.hr.lm.leavepayout.validation.LeavePayoutValidationUtils; 034 import org.kuali.hr.lm.leavepayout.web.LeavePayoutForm; 035 import org.kuali.hr.lm.leaveSummary.LeaveSummary; 036 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow; 037 import org.kuali.hr.lm.leaveblock.LeaveBlock; 038 import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument; 039 import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader; 040 import org.kuali.hr.time.base.web.TkAction; 041 import org.kuali.hr.time.calendar.CalendarEntries; 042 import org.kuali.hr.time.service.base.TkServiceLocator; 043 import org.kuali.hr.time.timesheet.TimesheetDocument; 044 import org.kuali.hr.time.util.TKUtils; 045 import org.kuali.hr.time.workflow.TimesheetDocumentHeader; 046 import org.kuali.rice.krad.service.KRADServiceLocator; 047 import org.kuali.rice.krad.util.GlobalVariables; 048 import org.kuali.rice.krad.util.ObjectUtils; 049 050 import javax.servlet.http.HttpServletRequest; 051 import javax.servlet.http.HttpServletResponse; 052 053 public class LeavePayoutAction extends TkAction { 054 055 public ActionForward leavePayoutOnLeaveApproval(ActionMapping mapping, ActionForm form, 056 HttpServletRequest request, HttpServletResponse response) throws Exception { 057 058 //if action was submit, execute the payout 059 LeavePayoutForm lpf = (LeavePayoutForm) form; 060 LeavePayout leavePayout = lpf.getLeavePayout(); 061 062 boolean valid = LeavePayoutValidationUtils.validatePayout(leavePayout); 063 064 //if payout amount has changed, and the resulting change produces forfeiture 065 //or changes the forfeiture amount, prompt for confirmation with the amount of 066 //forfeiture that the entered amount would produce. 067 068 if(valid) { 069 070 String accrualRuleId = leavePayout.getAccrualCategoryRule(); 071 072 String documentId = leavePayout.getLeaveCalendarDocumentId(); 073 TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId); 074 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId); 075 CalendarEntries calendarEntry = null; 076 String strutsActionForward = ""; 077 String methodToCall = "approveLeaveCalendar"; 078 if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) { 079 throw new RuntimeException("No document found"); 080 } 081 else if(ObjectUtils.isNotNull(tsdh)) { 082 //Throws runtime exception, separate action forwards for timesheet/leave calendar payouts. 083 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId); 084 calendarEntry = tsd.getCalendarEntry(); 085 strutsActionForward = "timesheetPayoutSuccess"; 086 methodToCall = "approveTimesheet"; 087 } 088 else { 089 LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId); 090 calendarEntry = lcd.getCalendarEntry(); 091 strutsActionForward = "leaveCalendarPayoutSuccess"; 092 methodToCall = "approveLeaveCalendar"; 093 } 094 095 if(ObjectUtils.isNull(calendarEntry)) { 096 throw new RuntimeException("Could not retreive calendar entry for document " + documentId); 097 } 098 099 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId); 100 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 101 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(leavePayout.getPrincipalId(), accrualCategory, leavePayout.getEffectiveDate()); 102 103 LeavePayout defaultBT = TkServiceLocator.getLeavePayoutService().initializePayout(leavePayout.getPrincipalId(), accrualRuleId, accruedBalance, leavePayout.getEffectiveDate()); 104 if(leavePayout.getPayoutAmount().compareTo(defaultBT.getPayoutAmount()) != 0) { 105 //employee changed the payout amount, recalculate forfeiture. 106 //Note: payout form has been validated. 107 leavePayout = defaultBT.adjust(leavePayout.getPayoutAmount()); 108 // showing the adjusted balance payout via the execution of another forward 109 // would cause a loop that would break only if the original payout amount was re-established in the form. 110 // javascript must be written if the forfeited amount is to be updated on the form object. 111 // an alternative to javascript would be to render a "re-calculate" button attached to a dedicated action forward method. 112 // must re-set leaveCalendarDocumentId, as leavePayout is now just an adjustment of the default initialized BT with no leave calendar doc id. 113 leavePayout.setLeaveCalendarDocumentId(documentId); 114 } 115 116 TkServiceLocator.getLeavePayoutService().submitToWorkflow(leavePayout); 117 118 if(ObjectUtils.isNotNull(documentId)) { 119 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) || 120 StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) { 121 122 ActionForward forward = new ActionForward(mapping.findForward(strutsActionForward)); 123 forward.setPath(forward.getPath()+"?documentId="+documentId+"&action=R&methodToCall="+methodToCall); 124 return forward; 125 } 126 else 127 return mapping.findForward("closeLeavePayoutDoc"); 128 } 129 else 130 return mapping.findForward("closeLeavePayoutDoc"); 131 } 132 else //show user errors. 133 return mapping.findForward("basic"); 134 } 135 136 public ActionForward cancel(ActionMapping mapping, ActionForm form, 137 HttpServletRequest request, HttpServletResponse response) 138 throws Exception { 139 140 LeavePayoutForm lpf = (LeavePayoutForm) form; 141 LeavePayout leavePayout = lpf.getLeavePayout(); 142 String accrualCategoryRuleId = leavePayout.getAccrualCategoryRule(); 143 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId); 144 String actionFrequency = accrualRule.getMaxBalanceActionFrequency(); 145 146 if(StringUtils.equals(actionFrequency,LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)) 147 return mapping.findForward("closeLeavePayoutDoc"); 148 else 149 if(StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) || 150 StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) { 151 152 String documentId = leavePayout.getLeaveCalendarDocumentId(); 153 TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId); 154 LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId); 155 String strutsActionForward = ""; 156 if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) { 157 strutsActionForward = "/"; 158 } 159 else if(ObjectUtils.isNotNull(tsdh)) { 160 //Throws runtime exception, separate action forwards for timesheet/leave calendar transfers. 161 strutsActionForward = mapping.findForward("timesheetCancel").getPath() + "?documentId=" + leavePayout.getLeaveCalendarDocumentId(); 162 } 163 else { 164 strutsActionForward = mapping.findForward("leaveCalendarCancel").getPath() + "?documentId=" + leavePayout.getLeaveCalendarDocumentId(); 165 } 166 167 ActionRedirect redirect = new ActionRedirect(); 168 redirect.setPath(strutsActionForward); 169 return redirect; 170 171 } 172 else 173 throw new RuntimeException("Action should only be reachable through triggers with frequency ON_DEMAND or LEAVE_APPROVE"); 174 } 175 176 //Entry point for LeavePayout.do for accrual category rule triggered payouts with action frequency On Demand. 177 //May be better suited in the LeaveCalendarAction class. 178 public ActionForward leavePayoutOnDemand(ActionMapping mapping, ActionForm form, 179 HttpServletRequest request, HttpServletResponse response) 180 throws Exception { 181 GlobalVariables.getMessageMap().putWarning("document.payoutAmount","leavePayout.payoutAmount.adjust"); 182 183 LeavePayoutForm lpf = (LeavePayoutForm) form; 184 //the leave calendar document that triggered this balance payout. 185 String documentId = request.getParameter("documentId"); 186 String leaveBlockId = request.getParameter("accrualRuleId"); 187 String timesheet = request.getParameter("timesheet"); 188 189 boolean isTimesheet = false; 190 if(StringUtils.equals(timesheet, "true")) { 191 lpf.isTimesheet(true); 192 isTimesheet = true; 193 } 194 if(ObjectUtils.isNotNull(leaveBlockId)) { 195 AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(leaveBlockId); 196 if(ObjectUtils.isNotNull(aRule)) { 197 //should somewhat safegaurd against url fabrication. 198 if(!StringUtils.equals(aRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)) 199 throw new RuntimeException("attempted to execute on-demand balance payout for accrual category with action frequency " + aRule.getMaxBalanceActionFrequency()); 200 else { 201 TimesheetDocument tsd = null; 202 LeaveCalendarDocument lcd = null; 203 String principalId = null; 204 CalendarEntries calendarEntry = null; 205 206 if(isTimesheet) { 207 tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId); 208 principalId = tsd.getPrincipalId(); 209 calendarEntry = tsd.getCalendarEntry(); 210 } 211 else { 212 lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId); 213 principalId = lcd.getPrincipalId(); 214 calendarEntry = lcd.getCalendarEntry(); 215 } 216 217 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(leaveBlockId); 218 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 219 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, TKUtils.getCurrentDate()); 220 221 LeavePayout leavePayout = TkServiceLocator.getLeavePayoutService().initializePayout(principalId, leaveBlockId, accruedBalance, TKUtils.getCurrentDate()); 222 leavePayout.setLeaveCalendarDocumentId(documentId); 223 if(ObjectUtils.isNotNull(leavePayout)) { 224 if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) { 225 // this particular combination of action / action frequency does not particularly make sense 226 // unless for some reason users still need to be prompted to submit the loss. 227 // For now, we treat as though it is a valid use-case. 228 //TkServiceLocator.getLeavePayoutService().submitToWorkflow(leavePayout); 229 // May need to update to save the business object to KPME's tables for record keeping. 230 leavePayout = TkServiceLocator.getLeavePayoutService().payout(leavePayout); 231 // May need to update to save the business object to KPME's tables for record keeping. 232 LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leavePayout.getForfeitedLeaveBlockId()); 233 forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED); 234 TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId); 235 return mapping.findForward("closeLeavePayoutDoc"); 236 } 237 else { 238 ActionForward forward = mapping.findForward("basic"); 239 lpf.setLeaveCalendarDocumentId(documentId); 240 lpf.setLeavePayout(leavePayout); 241 lpf.setPayoutAmount(leavePayout.getPayoutAmount()); 242 return forward; 243 } 244 } 245 else 246 throw new RuntimeException("could not initialize a balance payout"); 247 248 } 249 } 250 else 251 throw new RuntimeException("No rule for this accrual category could be found"); 252 } 253 else 254 throw new RuntimeException("No accrual category rule id has been sent in the request."); 255 } 256 257 //Entry point for LeavePayout.do for accrual category rule triggered transfers with action frequency Leave Approve. 258 //TODO: Rename method to differentiate from ActionForward with same name in LeaveCalendarSubmit. 259 public ActionForward approveLeaveCalendar(ActionMapping mapping, ActionForm form, 260 HttpServletRequest request, HttpServletResponse response) 261 throws Exception { 262 263 GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","leavePayout.transferAmount.adjust"); 264 LeavePayoutForm btf = (LeavePayoutForm) form; 265 266 List<LeaveBlock> eligiblePayouts = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities"); 267 if(!eligiblePayouts.isEmpty()) { 268 269 Collections.sort(eligiblePayouts, new Comparator() { 270 271 @Override 272 public int compare(Object o1, Object o2) { 273 LeaveBlock l1 = (LeaveBlock) o1; 274 LeaveBlock l2 = (LeaveBlock) o2; 275 return l1.getLeaveDate().compareTo(l2.getLeaveDate()); 276 } 277 278 }); 279 280 //This is the leave calendar document that triggered this balance transfer. 281 282 String leaveCalendarDocumentId = request.getParameter("documentId"); 283 ActionForward forward = new ActionForward(mapping.findForward("basic")); 284 LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(leaveCalendarDocumentId); 285 286 LeaveBlock leaveBlock = eligiblePayouts.get(0); 287 Date effectiveDate = leaveBlock.getLeaveDate(); 288 String accrualCategoryRuleId = leaveBlock.getAccrualCategoryRuleId(); 289 if(!StringUtils.isBlank(accrualCategoryRuleId)) { 290 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId); 291 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 292 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(lcd.getPrincipalId(), accrualCategory, effectiveDate); 293 294 LeavePayout leavePayout = TkServiceLocator.getLeavePayoutService().initializePayout(lcd.getPrincipalId(), accrualCategoryRuleId, accruedBalance, effectiveDate); 295 leavePayout.setLeaveCalendarDocumentId(leaveCalendarDocumentId); 296 297 if(ObjectUtils.isNotNull(leavePayout)) { 298 299 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) { 300 //payouts should never contain losses. 301 //losses are treated as a special case of transfer 302 //TkServiceLocator.getLeavePayoutService().submitToWorkflow(leavePayout); 303 leavePayout = TkServiceLocator.getLeavePayoutService().payout(leavePayout); 304 // May need to update to save the business object to KPME's tables for record keeping. 305 LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leavePayout.getForfeitedLeaveBlockId()); 306 KRADServiceLocator.getBusinessObjectService().save(leavePayout); 307 forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED); 308 TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, lcd.getPrincipalId()); 309 310 if(ObjectUtils.isNotNull(leaveCalendarDocumentId)) { 311 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) || 312 StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) { 313 ActionForward loseForward = new ActionForward(mapping.findForward("leaveCalendarPayoutSuccess")); 314 loseForward.setPath(loseForward.getPath()+"?documentId="+leaveCalendarDocumentId+"&action=R&methodToCall=approveLeaveCalendar"); 315 return loseForward; 316 } 317 //on demand handled in separate action forward. 318 } 319 320 } else { 321 btf.setLeaveCalendarDocumentId(leaveCalendarDocumentId); 322 btf.setLeavePayout(leavePayout); 323 btf.setPayoutAmount(leavePayout.getPayoutAmount()); 324 return forward; 325 } 326 327 } 328 throw new RuntimeException("could not initialize balance transfer"); 329 330 } 331 else 332 throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer"); 333 } 334 else 335 throw new RuntimeException("No infractions given"); 336 } 337 338 public ActionForward approveTimesheet(ActionMapping mapping, ActionForm form, 339 HttpServletRequest request, HttpServletResponse response) 340 throws Exception { 341 342 GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","leavePayout.transferAmount.adjust"); 343 LeavePayoutForm btf = (LeavePayoutForm) form; 344 345 List<LeaveBlock> eligiblePayouts = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities"); 346 if(!eligiblePayouts.isEmpty()) { 347 348 Collections.sort(eligiblePayouts, new Comparator() { 349 350 @Override 351 public int compare(Object o1, Object o2) { 352 LeaveBlock l1 = (LeaveBlock) o1; 353 LeaveBlock l2 = (LeaveBlock) o2; 354 return l1.getLeaveDate().compareTo(l2.getLeaveDate()); 355 } 356 357 }); 358 359 //This is the leave calendar document that triggered this balance transfer. 360 361 String timesheetDocumentId = request.getParameter("documentId"); 362 ActionForward forward = new ActionForward(mapping.findForward("basic")); 363 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentId); 364 365 LeaveBlock leaveBlock = eligiblePayouts.get(0); 366 Date effectiveDate = leaveBlock.getLeaveDate(); 367 String accrualCategoryRuleId = leaveBlock.getAccrualCategoryRuleId(); 368 if(!StringUtils.isBlank(accrualCategoryRuleId)) { 369 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId); 370 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 371 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(tsd.getPrincipalId(), accrualCategory, effectiveDate); 372 373 LeavePayout leavePayout = TkServiceLocator.getLeavePayoutService().initializePayout(tsd.getPrincipalId(), accrualCategoryRuleId, accruedBalance, effectiveDate); 374 leavePayout.setLeaveCalendarDocumentId(timesheetDocumentId); 375 376 if(ObjectUtils.isNotNull(leavePayout)) { 377 378 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) { 379 // TODO: Redirect user to prompt stating excess leave will be forfeited and ask for confirmation. 380 // Do not submit the object to workflow for this max balance action. 381 leavePayout = TkServiceLocator.getLeavePayoutService().payout(leavePayout); 382 KRADServiceLocator.getBusinessObjectService().save(leavePayout); 383 384 // May need to update to save the business object to KPME's tables for record keeping. 385 LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leavePayout.getForfeitedLeaveBlockId()); 386 forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED); 387 TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, tsd.getPrincipalId()); 388 389 if(ObjectUtils.isNotNull(timesheetDocumentId)) { 390 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) || 391 StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) { 392 ActionForward loseForward = new ActionForward(mapping.findForward("timesheetPayoutSuccess")); 393 loseForward.setPath(loseForward.getPath()+"?documentId="+timesheetDocumentId+"&action=R&methodToCall=approveTimesheet"); 394 return loseForward; 395 } 396 //on demand handled in separate action forward. 397 } 398 399 } else { 400 btf.setLeaveCalendarDocumentId(timesheetDocumentId); 401 btf.setLeavePayout(leavePayout); 402 btf.setPayoutAmount(leavePayout.getPayoutAmount()); 403 return forward; 404 } 405 406 } 407 throw new RuntimeException("could not initialize balance transfer"); 408 409 } 410 else 411 throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer"); 412 } 413 else 414 throw new RuntimeException("no eligible transfers exist"); 415 } 416 417 public ActionForward closeLeavePayoutDoc(ActionMapping mapping, ActionForm form, 418 HttpServletRequest request, HttpServletResponse response) 419 throws Exception { 420 return mapping.findForward("closeLeavePayoutDoc"); 421 } 422 }