View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.hr.lm.balancetransfer.web;
17  
18  import java.math.BigDecimal;
19  import java.sql.Date;
20  import java.util.*;
21  import java.util.Map.Entry;
22  
23  import javax.servlet.http.HttpServletRequest;
24  import javax.servlet.http.HttpServletResponse;
25  
26  import org.apache.commons.collections.CollectionUtils;
27  import org.apache.commons.lang.time.DateUtils;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.struts.action.ActionForm;
30  import org.apache.struts.action.ActionForward;
31  import org.apache.struts.action.ActionMapping;
32  import org.apache.struts.action.ActionRedirect;
33  import org.kuali.hr.lm.LMConstants;
34  import org.kuali.hr.lm.accrual.AccrualCategory;
35  import org.kuali.hr.lm.accrual.AccrualCategoryRule;
36  import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
37  import org.kuali.hr.lm.balancetransfer.validation.BalanceTransferValidationUtils;
38  import org.kuali.hr.lm.leaveSummary.LeaveSummary;
39  import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
40  import org.kuali.hr.lm.leaveblock.LeaveBlock;
41  import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
42  import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
43  import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
44  import org.kuali.hr.time.base.web.TkAction;
45  import org.kuali.hr.time.calendar.Calendar;
46  import org.kuali.hr.time.calendar.CalendarEntries;
47  import org.kuali.hr.time.earncode.EarnCode;
48  import org.kuali.hr.time.service.base.TkServiceLocator;
49  import org.kuali.hr.time.timesheet.TimesheetDocument;
50  import org.kuali.hr.time.util.TKUtils;
51  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
52  import org.kuali.rice.krad.service.KRADServiceLocator;
53  import org.kuali.rice.krad.util.GlobalVariables;
54  import org.kuali.rice.krad.util.ObjectUtils;
55  
56  public class BalanceTransferAction extends TkAction {
57  
58  	public ActionForward balanceTransferOnLeaveApproval(ActionMapping mapping, ActionForm form,
59  			HttpServletRequest request, HttpServletResponse response) throws Exception {
60  
61  		//if action was submit, execute the transfer
62  		BalanceTransferForm btf = (BalanceTransferForm) form;
63  		BalanceTransfer balanceTransfer = btf.getBalanceTransfer();
64  	
65  		boolean valid = BalanceTransferValidationUtils.validateTransfer(balanceTransfer);
66  		
67  		//if transfer amount has changed, and the resulting change produces forfeiture
68  		//or changes the forfeiture amount, prompt for confirmation with the amount of
69  		//forfeiture that the entered amount would produce.
70  
71  		if(valid) {
72  			
73  			String accrualRuleId = balanceTransfer.getAccrualCategoryRule();
74  			
75  			String documentId = balanceTransfer.getLeaveCalendarDocumentId();
76  			TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
77  			LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
78  			CalendarEntries calendarEntry = null;
79  			String strutsActionForward = "";
80  			String methodToCall = "approveLeaveCalendar";
81  			if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
82  				throw new RuntimeException("No document found");
83  			}
84  			else if(ObjectUtils.isNotNull(tsdh)) {
85  				//Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
86  				TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
87  				calendarEntry = tsd != null ? tsd.getCalendarEntry() : null;
88  				strutsActionForward = "timesheetTransferSuccess";
89  				methodToCall = "approveTimesheet";
90  			}
91  			else {
92  				LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
93  				calendarEntry = lcd != null ? lcd.getCalendarEntry() : null;
94  				strutsActionForward = "leaveCalendarTransferSuccess";
95  				methodToCall = "approveLeaveCalendar";
96  			}
97  			
98  			if(ObjectUtils.isNull(calendarEntry)) {
99  				throw new RuntimeException("Could not retreive calendar entry for document " + documentId);
100 			}
101 			
102 			AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
103 			
104 			AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
105 			BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(balanceTransfer.getPrincipalId(), accrualCategory, balanceTransfer.getEffectiveDate());
106 
107 			BalanceTransfer defaultBT = TkServiceLocator.getBalanceTransferService().initializeTransfer(balanceTransfer.getPrincipalId(), accrualRuleId, accruedBalance, balanceTransfer.getEffectiveDate());
108 			if(balanceTransfer.getTransferAmount().compareTo(defaultBT.getTransferAmount()) != 0) {
109 				//employee changed the transfer amount, recalculate forfeiture.
110 				//Note: transfer form has been validated.
111 				balanceTransfer = defaultBT.adjust(balanceTransfer.getTransferAmount());
112 				// showing the adjusted balance transfer via the execution of another forward
113 				// would cause a loop that would break only if the original transfer amount was re-established in the form.
114 				// javascript must be written if the forfeited amount is to be updated on the form object.
115 				// an alternative to javascript would be to render a "re-calculate" button attached to a dedicated action forward method.
116 				// must re-set leaveCalendarDocumentId, as balanceTransfer is now just an adjustment of the default initialized BT with no leave calendar doc id.
117 				balanceTransfer.setLeaveCalendarDocumentId(documentId);
118 			}
119 
120 			TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
121 			
122 			if(ObjectUtils.isNotNull(documentId)) {
123 				if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
124 						StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
125 					ActionForward forward = new ActionForward(mapping.findForward(strutsActionForward));
126 					forward.setPath(forward.getPath()+"?documentId="+documentId+"&action=R&methodToCall="+methodToCall);
127 					return forward;
128 				}
129 				else
130 					return mapping.findForward("closeBalanceTransferDoc");
131 			}
132 			else
133 				return mapping.findForward("closeBalanceTransferDoc");
134 		}
135 		else //show user errors.
136 			return mapping.findForward("basic");
137 	}
138 
139 	public ActionForward cancel(ActionMapping mapping, ActionForm form,
140 			HttpServletRequest request, HttpServletResponse response)
141 			throws Exception {
142 		
143 		BalanceTransferForm btf = (BalanceTransferForm) form;
144 		BalanceTransfer bt = btf.getBalanceTransfer();
145 
146 		if(btf.isSstoTransfer()) {
147 			return mapping.findForward("closeBalanceTransferDoc");
148 		}
149 		
150 		String accrualCategoryRuleId = bt.getAccrualCategoryRule();
151 		AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
152 		String actionFrequency = accrualRule.getMaxBalanceActionFrequency();
153 		
154 		if(StringUtils.equals(actionFrequency,LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)) 
155 			return mapping.findForward("closeBalanceTransferDoc");
156 		else 
157 			if(StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
158 					StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
159 				String documentId = bt.getLeaveCalendarDocumentId();
160 				TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
161 				LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
162 				String strutsActionForward = "";
163 				if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
164 					strutsActionForward = "/";
165 				}
166 				else if(ObjectUtils.isNotNull(tsdh)) {
167 					//Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
168 					strutsActionForward = mapping.findForward("timesheetCancel").getPath() + "?documentId=" + bt.getLeaveCalendarDocumentId();
169 				}
170 				else {
171 					strutsActionForward = mapping.findForward("leaveCalendarCancel").getPath() + "?documentId=" + bt.getLeaveCalendarDocumentId();
172 				}
173 
174 				ActionRedirect redirect = new ActionRedirect();
175 				redirect.setPath(strutsActionForward);
176 				return redirect;
177 			}
178 			else
179 				throw new RuntimeException("Action should only be reachable through triggers with frequency ON_DEMAND or LEAVE_APPROVE");
180 	}
181 	
182 	//Entry point for BalanceTransfer.do for accrual category rule triggered transfers with action frequency On Demand.
183 	//May be better suited in the LeaveCalendarAction class.
184 	public ActionForward balanceTransferOnDemand(ActionMapping mapping, ActionForm form,
185 			HttpServletRequest request, HttpServletResponse response)
186 			throws Exception {
187 		GlobalVariables.getMessageMap().putWarning("document.transferAmount","balanceTransfer.transferAmount.adjust");
188 
189 		BalanceTransferForm btf = (BalanceTransferForm) form;
190 		//the leave calendar document that triggered this balance transfer.
191 		String documentId = request.getParameter("documentId");
192 		String accrualRuleId = request.getParameter("accrualRuleId");
193 		String timesheet = request.getParameter("timesheet");
194 		boolean isTimesheet = false;
195 		if(StringUtils.equals(timesheet, "true")) {
196 			btf.isTimesheet(true);
197 			isTimesheet = true;
198 		}
199 		if(ObjectUtils.isNotNull(accrualRuleId)) {
200 			//LeaveBlock lb = TkServiceLocator.getLeaveBlockService().getLeaveBlock(leaveBlockId);
201 			AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
202 			if(ObjectUtils.isNotNull(aRule)) {
203 				//should somewhat safegaurd against url fabrication.
204 				if(!StringUtils.equals(aRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND))
205 					throw new RuntimeException("attempted to execute on-demand balance transfer for accrual category with action frequency " + aRule.getMaxBalanceActionFrequency());
206 				else {
207 					String principalId = null;
208 
209 					if(isTimesheet) {
210                         TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
211 						principalId = tsd == null ? null : tsd.getPrincipalId();
212 					}
213 					else {
214                         LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
215 						principalId = lcd == null ? null : lcd.getPrincipalId();
216 					}
217 
218 					AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
219 					AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
220 					BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, TKUtils.getCurrentDate());
221 
222 					BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(principalId, aRule.getLmAccrualCategoryRuleId(), accruedBalance, TKUtils.getCurrentDate());
223 					balanceTransfer.setLeaveCalendarDocumentId(documentId);
224 					if(ObjectUtils.isNotNull(balanceTransfer)) {
225 						if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {	
226 							// this particular combination of action / action frequency does not particularly make sense
227 							// unless for some reason users still need to be prompted to submit the loss.
228 							// For now, we treat as though it is a valid use-case.
229 							//TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
230 							// May need to update to save the business object to KPME's tables for record keeping.
231 							balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
232 							// May need to update to save the business object to KPME's tables for record keeping.
233 							LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
234 							forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
235 							TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId);
236 							return mapping.findForward("closeBalanceTransferDoc");
237 						}
238 						else {
239 							ActionForward forward = mapping.findForward("basic");
240 							btf.setLeaveCalendarDocumentId(documentId);
241 							btf.setBalanceTransfer(balanceTransfer);
242 							btf.setTransferAmount(balanceTransfer.getTransferAmount());
243 							return forward;
244 						}
245 					}
246 					else
247 						throw new RuntimeException("could not initialize a balance transfer");
248 
249 				}
250 			}
251 			else
252 				throw new RuntimeException("No rule for this accrual category could be found");
253 		}
254 		else
255 			throw new RuntimeException("No accrual category rule id has been sent in the request.");
256 	}
257 
258 	//Entry point for BalanceTransfer.do for accrual category rule triggered transfers with action frequency Leave Approve.
259 	//TODO: Rename method to differentiate from ActionForward with same name in LeaveCalendarSubmit.
260 	public ActionForward approveLeaveCalendar(ActionMapping mapping, ActionForm form,
261 			HttpServletRequest request, HttpServletResponse response)
262 					throws Exception {
263 		
264 		GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferAmount.adjust");
265 		BalanceTransferForm btf = (BalanceTransferForm) form;
266 
267 		List<LeaveBlock> eligibleTransfers = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities");
268 		if(!eligibleTransfers.isEmpty()) {
269 			
270 			Collections.sort(eligibleTransfers, new Comparator() {
271 
272                 @Override
273                 public int compare(Object o1, Object o2) {
274                     LeaveBlock l1 = (LeaveBlock) o1;
275                     LeaveBlock l2 = (LeaveBlock) o2;
276                     return l1.getLeaveDate().compareTo(l2.getLeaveDate());
277                 }
278 
279             });
280 			
281 			//This is the leave calendar document that triggered this balance transfer.
282 
283 			String leaveCalendarDocumentId = request.getParameter("documentId");
284 			ActionForward forward = new ActionForward(mapping.findForward("basic"));
285 			LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(leaveCalendarDocumentId);
286 			String principalId = lcd == null ? null : lcd.getPrincipalId();
287 			LeaveBlock leaveBlock = eligibleTransfers.get(0);
288 			Date effectiveDate = leaveBlock.getLeaveDate();
289 			String accrualCategoryRuleId = leaveBlock.getAccrualCategoryRuleId();
290 			if(!StringUtils.isBlank(accrualCategoryRuleId)) {
291 				
292 				AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
293 				AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
294 				BigDecimal accruedBalance = TkServiceLocator.getAccrualCategoryService().getAccruedBalanceForPrincipal(principalId, accrualCategory, leaveBlock.getLeaveDate());
295 				
296 				BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(principalId, accrualCategoryRuleId, accruedBalance, effectiveDate);
297 				
298 				balanceTransfer.setLeaveCalendarDocumentId(leaveCalendarDocumentId);
299 				
300 				if(ObjectUtils.isNotNull(balanceTransfer)) {
301 					if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
302 	
303                         //TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
304                         balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
305                         // May need to update to save the business object to KPME's tables for record keeping.
306                         LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
307                         KRADServiceLocator.getBusinessObjectService().save(balanceTransfer);
308                         forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
309                         TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId);
310 
311                         if(ObjectUtils.isNotNull(leaveCalendarDocumentId)) {
312                             if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
313                                     StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
314                                 ActionForward loseForward = new ActionForward(mapping.findForward("leaveCalendarTransferSuccess"));
315                                 loseForward.setPath(loseForward.getPath()+"?documentId="+leaveCalendarDocumentId+"&action=R&methodToCall=approveLeaveCalendar");
316                                 return loseForward;
317                             }
318                                 //on demand handled in separate action forward.
319                         }
320 
321 					} else {
322 						btf.setLeaveCalendarDocumentId(leaveCalendarDocumentId);
323 						btf.setBalanceTransfer(balanceTransfer);
324 						btf.setTransferAmount(balanceTransfer.getTransferAmount());
325 						return forward;
326 					}
327 			    }
328 			    throw new RuntimeException("could not initialize balance transfer");
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 	
337 	public ActionForward approveTimesheet(ActionMapping mapping, ActionForm form,
338 			HttpServletRequest request, HttpServletResponse response)
339 					throws Exception {
340 		
341 		GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferAmount.adjust");
342 		BalanceTransferForm btf = (BalanceTransferForm) form;
343 
344 		List<LeaveBlock> eligibleTransfers = (List<LeaveBlock>) request.getSession().getAttribute("eligibilities");
345 		if(!eligibleTransfers.isEmpty()) {
346 			
347 			Collections.sort(eligibleTransfers, new Comparator() {
348 				
349 				@Override
350 				public int compare(Object o1, Object o2) {
351 					LeaveBlock l1 = (LeaveBlock) o1;
352 					LeaveBlock l2 = (LeaveBlock) o2;
353 					return l1.getLeaveDate().compareTo(l2.getLeaveDate());
354 				}
355 				
356 			});
357 			
358 			//This is the leave calendar document that triggered this balance transfer.
359 
360 			String timesheetDocumentId = request.getParameter("documentId");
361 			ActionForward forward = new ActionForward(mapping.findForward("basic"));
362 			TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentId);
363 			String principalId = tsd == null ? null : tsd.getPrincipalId();
364 
365 			LeaveBlock leaveBlock = eligibleTransfers.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(principalId, accrualCategory, leaveBlock.getLeaveDate());
372 
373 				BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(principalId, accrualCategoryRuleId, accruedBalance, effectiveDate);
374 				balanceTransfer.setLeaveCalendarDocumentId(timesheetDocumentId);
375 	
376 				if(ObjectUtils.isNotNull(balanceTransfer)) {
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 						balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
382 						KRADServiceLocator.getBusinessObjectService().save(balanceTransfer);
383 	
384 						// May need to update to save the business object to KPME's tables for record keeping.
385 						LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
386 						forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
387 						TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId);
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("timesheetTransferSuccess"));
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.setBalanceTransfer(balanceTransfer);
402 						btf.setTransferAmount(balanceTransfer.getTransferAmount());
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 closeBalanceTransferDoc(ActionMapping mapping, ActionForm form,
418 			HttpServletRequest request, HttpServletResponse response)
419 			throws Exception {
420 		return mapping.findForward("closeBalanceTransferDoc");
421 	}
422 	
423 	/* Delete system scheduled time off usage leave block from Leave or Time Calendar 
424 	 */
425 	public ActionForward deleteSSTOLeaveBlock(ActionMapping mapping, ActionForm form,
426 			HttpServletRequest request, HttpServletResponse response)
427 			throws Exception {
428 		BalanceTransferForm btf = (BalanceTransferForm) form;
429 		buildBalanceTransferForLeaveBlock(btf, request.getParameter("leaveBlockId"));
430 	
431 		return new ActionForward(mapping.findForward("basic"));
432 	}
433 	
434 	/* Build balance transfer based on the to-be-deleted leave block 
435 	 */
436 	private void buildBalanceTransferForLeaveBlock(BalanceTransferForm btf, String lbId) {
437 		LeaveBlock lb = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
438 		// this leave block is a ssto usage block, need to use it fo find the accrualed leave block which has a positive amount
439 		if(lb == null || StringUtils.isEmpty(lb.getScheduleTimeOffId())) {
440 			throw new RuntimeException("could not find the System Scheduled Time Off leave block that needs to be transferred!");	
441 		}
442 		SystemScheduledTimeOff ssto = TkServiceLocator.getSysSchTimeOffService().getSystemScheduledTimeOff(lb.getScheduleTimeOffId());
443 		BigDecimal amountTransferred = ssto.getTransferConversionFactor() == null ? lb.getLeaveAmount() : lb.getLeaveAmount().multiply(ssto.getTransferConversionFactor());
444 		EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(ssto.getTransfertoEarnCode(), lb.getLeaveDate());
445 		
446 		BalanceTransfer bt = new BalanceTransfer();
447 		bt.setTransferAmount(lb.getLeaveAmount().abs());	// the usage leave block's leave amount is negative
448 		bt.setFromAccrualCategory(lb.getAccrualCategory());
449 		bt.setAmountTransferred(amountTransferred.abs());
450 		bt.setToAccrualCategory(ec.getAccrualCategory());
451 		bt.setSstoId(lb.getScheduleTimeOffId());
452 		bt.setEffectiveDate(lb.getLeaveDate());
453 		bt.setPrincipalId(lb.getPrincipalId());
454 		
455 		btf.setBalanceTransfer(bt);
456 		btf.setTransferAmount(bt.getTransferAmount());
457 		GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferSSTO", 
458 				bt.getTransferAmount().toString(), bt.getAmountTransferred().toString());
459 	}
460 	/*
461 	 * Submit a balance transfer document when deleting a ssto usage leave block from current Leave/time calendar
462 	 * delete both accrued and usage ssto leave blocks, a pending transferred leave block is created by the BT doc
463 	 */
464 	public ActionForward balanceTransferOnSSTO(ActionMapping mapping, ActionForm form,
465 			HttpServletRequest request, HttpServletResponse response) throws Exception {
466 		BalanceTransferForm btf = (BalanceTransferForm) form;
467 		BalanceTransfer bt = btf.getBalanceTransfer();
468 		
469 		if(StringUtils.isEmpty(bt.getSstoId())) {
470 			throw new RuntimeException("System Scheduled Time Off not found for this balance transfer!");
471 		}
472 		List<LeaveBlock> lbList = TkServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(bt.getPrincipalId(), bt.getSstoId(), bt.getEffectiveDate());
473 		if(CollectionUtils.isEmpty(lbList) || (CollectionUtils.isNotEmpty(lbList) && lbList.size() != 2)) {
474 			throw new RuntimeException("There should be 2 system scheduled time off leave blocks!");
475 		}
476 		TkServiceLocator.getBalanceTransferService().submitToWorkflow(bt);
477 		// delete both SSTO accrualed and usage leave blocks
478 		for(LeaveBlock lb : lbList) {
479 			TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(lb.getLmLeaveBlockId(), lb.getPrincipalId());
480 		}
481 		return mapping.findForward("closeBalanceTransferDoc");
482 	}
483 
484 }