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 }