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 == null ? null : tsd.getCalendarEntry(); 085 strutsActionForward = "timesheetPayoutSuccess"; 086 methodToCall = "approveTimesheet"; 087 } 088 else { 089 LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId); 090 calendarEntry = lcd == null ? null : 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 == null ? null : tsd.getPrincipalId(); 209 } 210 else { 211 lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId); 212 principalId = lcd == null ? null : lcd.getPrincipalId(); 213 } 214 215 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(leaveBlockId); 216 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 217 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, TKUtils.getCurrentDate()); 218 219 LeavePayout leavePayout = TkServiceLocator.getLeavePayoutService().initializePayout(principalId, leaveBlockId, accruedBalance, TKUtils.getCurrentDate()); 220 leavePayout.setLeaveCalendarDocumentId(documentId); 221 if(ObjectUtils.isNotNull(leavePayout)) { 222 if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) { 223 // this particular combination of action / action frequency does not particularly make sense 224 // unless for some reason users still need to be prompted to submit the loss. 225 // For now, we treat as though it is a valid use-case. 226 //TkServiceLocator.getLeavePayoutService().submitToWorkflow(leavePayout); 227 // May need to update to save the business object to KPME's tables for record keeping. 228 leavePayout = TkServiceLocator.getLeavePayoutService().payout(leavePayout); 229 // May need to update to save the business object to KPME's tables for record keeping. 230 LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leavePayout.getForfeitedLeaveBlockId()); 231 forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED); 232 TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId); 233 return mapping.findForward("closeLeavePayoutDoc"); 234 } 235 else { 236 ActionForward forward = mapping.findForward("basic"); 237 lpf.setLeaveCalendarDocumentId(documentId); 238 lpf.setLeavePayout(leavePayout); 239 lpf.setPayoutAmount(leavePayout.getPayoutAmount()); 240 return forward; 241 } 242 } 243 else 244 throw new RuntimeException("could not initialize a balance payout"); 245 246 } 247 } 248 else 249 throw new RuntimeException("No rule for this accrual category could be found"); 250 } 251 else 252 throw new RuntimeException("No accrual category rule id has been sent in the request."); 253 } 254 255 //Entry point for LeavePayout.do for accrual category rule triggered transfers with action frequency Leave Approve. 256 //TODO: Rename method to differentiate from ActionForward with same name in LeaveCalendarSubmit. 257 public ActionForward approveLeaveCalendar(ActionMapping mapping, ActionForm form, 258 HttpServletRequest request, HttpServletResponse response) 259 throws Exception { 260 261 GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","leavePayout.transferAmount.adjust"); 262 LeavePayoutForm btf = (LeavePayoutForm) form; 263 264 List<LeaveBlock> eligiblePayouts = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities"); 265 if(!eligiblePayouts.isEmpty()) { 266 267 Collections.sort(eligiblePayouts, new Comparator() { 268 269 @Override 270 public int compare(Object o1, Object o2) { 271 LeaveBlock l1 = (LeaveBlock) o1; 272 LeaveBlock l2 = (LeaveBlock) o2; 273 return l1.getLeaveDate().compareTo(l2.getLeaveDate()); 274 } 275 276 }); 277 278 //This is the leave calendar document that triggered this balance transfer. 279 280 String leaveCalendarDocumentId = request.getParameter("documentId"); 281 ActionForward forward = new ActionForward(mapping.findForward("basic")); 282 LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(leaveCalendarDocumentId); 283 String principalId = lcd == null ? null : lcd.getPrincipalId(); 284 LeaveBlock leaveBlock = eligiblePayouts.get(0); 285 Date effectiveDate = leaveBlock.getLeaveDate(); 286 String accrualCategoryRuleId = leaveBlock.getAccrualCategoryRuleId(); 287 if(!StringUtils.isBlank(accrualCategoryRuleId)) { 288 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId); 289 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 290 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, effectiveDate); 291 292 LeavePayout leavePayout = TkServiceLocator.getLeavePayoutService().initializePayout(principalId, accrualCategoryRuleId, accruedBalance, effectiveDate); 293 leavePayout.setLeaveCalendarDocumentId(leaveCalendarDocumentId); 294 295 if(ObjectUtils.isNotNull(leavePayout)) { 296 297 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) { 298 //payouts should never contain losses. 299 //losses are treated as a special case of transfer 300 //TkServiceLocator.getLeavePayoutService().submitToWorkflow(leavePayout); 301 leavePayout = TkServiceLocator.getLeavePayoutService().payout(leavePayout); 302 // May need to update to save the business object to KPME's tables for record keeping. 303 LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leavePayout.getForfeitedLeaveBlockId()); 304 KRADServiceLocator.getBusinessObjectService().save(leavePayout); 305 forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED); 306 TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId); 307 308 if(ObjectUtils.isNotNull(leaveCalendarDocumentId)) { 309 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) || 310 StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) { 311 ActionForward loseForward = new ActionForward(mapping.findForward("leaveCalendarPayoutSuccess")); 312 loseForward.setPath(loseForward.getPath()+"?documentId="+leaveCalendarDocumentId+"&action=R&methodToCall=approveLeaveCalendar"); 313 return loseForward; 314 } 315 //on demand handled in separate action forward. 316 } 317 318 } else { 319 btf.setLeaveCalendarDocumentId(leaveCalendarDocumentId); 320 btf.setLeavePayout(leavePayout); 321 btf.setPayoutAmount(leavePayout.getPayoutAmount()); 322 return forward; 323 } 324 325 } 326 throw new RuntimeException("could not initialize balance transfer"); 327 328 } 329 else 330 throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer"); 331 } 332 else 333 throw new RuntimeException("No infractions given"); 334 } 335 336 public ActionForward approveTimesheet(ActionMapping mapping, ActionForm form, 337 HttpServletRequest request, HttpServletResponse response) 338 throws Exception { 339 340 GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","leavePayout.transferAmount.adjust"); 341 LeavePayoutForm btf = (LeavePayoutForm) form; 342 343 List<LeaveBlock> eligiblePayouts = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities"); 344 if(!eligiblePayouts.isEmpty()) { 345 346 Collections.sort(eligiblePayouts, new Comparator() { 347 348 @Override 349 public int compare(Object o1, Object o2) { 350 LeaveBlock l1 = (LeaveBlock) o1; 351 LeaveBlock l2 = (LeaveBlock) o2; 352 return l1.getLeaveDate().compareTo(l2.getLeaveDate()); 353 } 354 355 }); 356 357 //This is the leave calendar document that triggered this balance transfer. 358 359 String timesheetDocumentId = request.getParameter("documentId"); 360 ActionForward forward = new ActionForward(mapping.findForward("basic")); 361 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentId); 362 String principalId = tsd == null ? null : tsd.getPrincipalId(); 363 364 LeaveBlock leaveBlock = eligiblePayouts.get(0); 365 Date effectiveDate = leaveBlock.getLeaveDate(); 366 String accrualCategoryRuleId = leaveBlock.getAccrualCategoryRuleId(); 367 if(!StringUtils.isBlank(accrualCategoryRuleId)) { 368 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId); 369 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId()); 370 BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, effectiveDate); 371 372 LeavePayout leavePayout = TkServiceLocator.getLeavePayoutService().initializePayout(principalId, accrualCategoryRuleId, accruedBalance, effectiveDate); 373 leavePayout.setLeaveCalendarDocumentId(timesheetDocumentId); 374 375 if(ObjectUtils.isNotNull(leavePayout)) { 376 377 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) { 378 // TODO: Redirect user to prompt stating excess leave will be forfeited and ask for confirmation. 379 // Do not submit the object to workflow for this max balance action. 380 leavePayout = TkServiceLocator.getLeavePayoutService().payout(leavePayout); 381 KRADServiceLocator.getBusinessObjectService().save(leavePayout); 382 383 // May need to update to save the business object to KPME's tables for record keeping. 384 LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leavePayout.getForfeitedLeaveBlockId()); 385 forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED); 386 TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId); 387 388 if(ObjectUtils.isNotNull(timesheetDocumentId)) { 389 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) || 390 StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) { 391 ActionForward loseForward = new ActionForward(mapping.findForward("timesheetPayoutSuccess")); 392 loseForward.setPath(loseForward.getPath()+"?documentId="+timesheetDocumentId+"&action=R&methodToCall=approveTimesheet"); 393 return loseForward; 394 } 395 //on demand handled in separate action forward. 396 } 397 398 } else { 399 btf.setLeaveCalendarDocumentId(timesheetDocumentId); 400 btf.setLeavePayout(leavePayout); 401 btf.setPayoutAmount(leavePayout.getPayoutAmount()); 402 return forward; 403 } 404 405 } 406 throw new RuntimeException("could not initialize balance transfer"); 407 408 } 409 else 410 throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer"); 411 } 412 else 413 throw new RuntimeException("no eligible transfers exist"); 414 } 415 416 public ActionForward closeLeavePayoutDoc(ActionMapping mapping, ActionForm form, 417 HttpServletRequest request, HttpServletResponse response) 418 throws Exception { 419 return mapping.findForward("closeLeavePayoutDoc"); 420 } 421 }