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.ArrayList;
21  import java.util.List;
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.AccrualCategoryRule;
35  import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
36  import org.kuali.hr.lm.balancetransfer.validation.BalanceTransferValidationUtils;
37  import org.kuali.hr.lm.leaveSummary.LeaveSummary;
38  import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
39  import org.kuali.hr.lm.leaveblock.LeaveBlock;
40  import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
41  import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
42  import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
43  import org.kuali.hr.time.base.web.TkAction;
44  import org.kuali.hr.time.calendar.Calendar;
45  import org.kuali.hr.time.calendar.CalendarEntries;
46  import org.kuali.hr.time.earncode.EarnCode;
47  import org.kuali.hr.time.service.base.TkServiceLocator;
48  import org.kuali.hr.time.timesheet.TimesheetDocument;
49  import org.kuali.hr.time.util.TKUtils;
50  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
51  import org.kuali.rice.krad.service.KRADServiceLocator;
52  import org.kuali.rice.krad.util.GlobalVariables;
53  import org.kuali.rice.krad.util.ObjectUtils;
54  
55  public class BalanceTransferAction extends TkAction {
56  
57  	public ActionForward balanceTransferOnLeaveApproval(ActionMapping mapping, ActionForm form,
58  			HttpServletRequest request, HttpServletResponse response) throws Exception {
59  
60  		//if action was submit, execute the transfer
61  		BalanceTransferForm btf = (BalanceTransferForm) form;
62  		BalanceTransfer balanceTransfer = btf.getBalanceTransfer();
63  	
64  		boolean valid = BalanceTransferValidationUtils.validateTransfer(balanceTransfer);
65  		
66  		//if transfer amount has changed, and the resulting change produces forfeiture
67  		//or changes the forfeiture amount, prompt for confirmation with the amount of
68  		//forfeiture that the entered amount would produce.
69  
70  		if(valid) {
71  			
72  			String accrualRuleId = balanceTransfer.getAccrualCategoryRule();
73  			
74  			String documentId = balanceTransfer.getLeaveCalendarDocumentId();
75  			TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
76  			LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
77  			CalendarEntries calendarEntry = null;
78  			String strutsActionForward = "";
79  			String methodToCall = "approveLeaveCalendar";
80  			if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
81  				throw new RuntimeException("No document found");
82  			}
83  			else if(ObjectUtils.isNotNull(tsdh)) {
84  				//Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
85  				TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
86  				calendarEntry = tsd.getCalendarEntry();
87  				strutsActionForward = "timesheetTransferSuccess";
88  				methodToCall = "approveTimesheet";
89  			}
90  			else {
91  				LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
92  				calendarEntry = lcd.getCalendarEntry();
93  				strutsActionForward = "leaveCalendarTransferSuccess";
94  				methodToCall = "approveLeaveCalendar";
95  			}
96  			
97  			if(ObjectUtils.isNull(calendarEntry)) {
98  				throw new RuntimeException("Could not retreive calendar entry for document " + documentId);
99  			}
100 			
101 			AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
102 			Date effectiveDate = TKUtils.getCurrentDate();
103 			if(TKUtils.getCurrentDate().after(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1)))
104 				effectiveDate = new Date(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1).getTime());
105 			// if submitting a delinquent calendar, use the calendar's end period date for the effective date.
106 			// could adjust the end period date by subtracting a day so that the leave blocks appear on the month in question.
107 			
108 			LeaveSummary ls = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(balanceTransfer.getPrincipalId(), effectiveDate);
109 			LeaveSummaryRow transferRow = ls.getLeaveSummaryRowForAccrualCategory(accrualRule.getLmAccrualCategoryId());
110 			BalanceTransfer defaultBT = TkServiceLocator.getBalanceTransferService().initializeTransfer(balanceTransfer.getPrincipalId(), accrualRuleId, transferRow.getAccruedBalance(), effectiveDate);
111 			if(balanceTransfer.getTransferAmount().compareTo(defaultBT.getTransferAmount()) != 0) {
112 				//employee changed the transfer amount, recalculate forfeiture.
113 				//Note: transfer form has been validated.
114 				balanceTransfer = defaultBT.adjust(balanceTransfer.getTransferAmount());
115 				// showing the adjusted balance transfer via the execution of another forward
116 				// would cause a loop that would break only if the original transfer amount was re-established in the form.
117 				// javascript must be written if the forfeited amount is to be updated on the form object.
118 				// an alternative to javascript would be to render a "re-calculate" button attached to a dedicated action forward method.
119 				// must re-set leaveCalendarDocumentId, as balanceTransfer is now just an adjustment of the default initialized BT with no leave calendar doc id.
120 				balanceTransfer.setLeaveCalendarDocumentId(documentId);
121 			}
122 
123 			TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
124 			
125 			if(ObjectUtils.isNotNull(documentId)) {
126 				if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
127 						StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
128 					ActionForward forward = new ActionForward(mapping.findForward(strutsActionForward));
129 					forward.setPath(forward.getPath()+"?documentId="+documentId+"&action=R&methodToCall="+methodToCall);
130 					return forward;
131 				}
132 				else
133 					return mapping.findForward("closeBalanceTransferDoc");
134 			}
135 			else
136 				return mapping.findForward("closeBalanceTransferDoc");
137 		}
138 		else //show user errors.
139 			return mapping.findForward("basic");
140 	}
141 
142 	public ActionForward cancel(ActionMapping mapping, ActionForm form,
143 			HttpServletRequest request, HttpServletResponse response)
144 			throws Exception {
145 		
146 		BalanceTransferForm btf = (BalanceTransferForm) form;
147 		BalanceTransfer bt = btf.getBalanceTransfer();
148 
149 		if(btf.isSstoTransfer()) {
150 			return mapping.findForward("closeBalanceTransferDoc");
151 		}
152 		
153 		String accrualCategoryRuleId = bt.getAccrualCategoryRule();
154 		AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
155 		String actionFrequency = accrualRule.getMaxBalanceActionFrequency();
156 		
157 		if(StringUtils.equals(actionFrequency,LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)) 
158 			return mapping.findForward("closeBalanceTransferDoc");
159 		else 
160 			if(StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
161 					StringUtils.equals(actionFrequency, LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
162 				String documentId = bt.getLeaveCalendarDocumentId();
163 				TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
164 				LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
165 				String strutsActionForward = "";
166 				if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
167 					strutsActionForward = "/";
168 				}
169 				else if(ObjectUtils.isNotNull(tsdh)) {
170 					//Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
171 					strutsActionForward = mapping.findForward("timesheetCancel").getPath() + "?documentId=" + bt.getLeaveCalendarDocumentId();
172 				}
173 				else {
174 					strutsActionForward = mapping.findForward("leaveCalendarCancel").getPath() + "?documentId=" + bt.getLeaveCalendarDocumentId();
175 				}
176 
177 				ActionRedirect redirect = new ActionRedirect();
178 				redirect.setPath(strutsActionForward);
179 				return redirect;
180 			}
181 			else
182 				throw new RuntimeException("Action should only be reachable through triggers with frequency ON_DEMAND or LEAVE_APPROVE");
183 	}
184 	
185 	//Entry point for BalanceTransfer.do for accrual category rule triggered transfers with action frequency On Demand.
186 	//May be better suited in the LeaveCalendarAction class.
187 	public ActionForward balanceTransferOnDemand(ActionMapping mapping, ActionForm form,
188 			HttpServletRequest request, HttpServletResponse response)
189 			throws Exception {
190 		GlobalVariables.getMessageMap().putWarning("document.transferAmount","balanceTransfer.transferAmount.adjust");
191 
192 		BalanceTransferForm btf = (BalanceTransferForm) form;
193 		//the leave calendar document that triggered this balance transfer.
194 		String documentId = request.getParameter("documentId");
195 		String accrualRuleId = request.getParameter("accrualRuleId");
196 		String timesheet = request.getParameter("timesheet");
197 		boolean isTimesheet = false;
198 		if(StringUtils.equals(timesheet, "true")) {
199 			btf.isTimesheet(true);
200 			isTimesheet = true;
201 		}
202 		if(ObjectUtils.isNotNull(accrualRuleId)) {
203 			AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
204 			if(ObjectUtils.isNotNull(aRule)) {
205 				//should somewhat safegaurd against url fabrication.
206 				if(!StringUtils.equals(aRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND))
207 					throw new RuntimeException("attempted to execute on-demand balance transfer for accrual category with action frequency " + aRule.getMaxBalanceActionFrequency());
208 				else {
209 					TimesheetDocument tsd = null;
210 					LeaveCalendarDocument lcd = null;
211 					String principalId = null;
212 					CalendarEntries calendarEntry = null;
213 					
214 					if(isTimesheet) {
215 						tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
216 						principalId = tsd.getPrincipalId();
217 						calendarEntry = tsd.getCalendarEntry();
218 					}
219 					else {
220 						lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
221 						principalId = lcd.getPrincipalId();
222 						calendarEntry = lcd.getCalendarEntry();
223 					}
224 					
225 					Date effectiveDate = TKUtils.getCurrentDate();
226 					if(TKUtils.getCurrentDate().after(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1)))
227 						effectiveDate = new Date(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1).getTime());
228 
229 					LeaveSummary ls = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, effectiveDate);
230 
231 					
232 					LeaveSummaryRow transferRow = ls.getLeaveSummaryRowForAccrualCategory(aRule.getLmAccrualCategoryId());
233 					BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(principalId, accrualRuleId, transferRow.getAccruedBalance(), effectiveDate);
234 					balanceTransfer.setLeaveCalendarDocumentId(documentId);
235 					if(ObjectUtils.isNotNull(balanceTransfer)) {
236 						if(StringUtils.equals(aRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {	
237 							// this particular combination of action / action frequency does not particularly make sense
238 							// unless for some reason users still need to be prompted to submit the loss.
239 							// For now, we treat as though it is a valid use-case.
240 							//TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
241 							// May need to update to save the business object to KPME's tables for record keeping.
242 							balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
243 							// May need to update to save the business object to KPME's tables for record keeping.
244 							LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
245 							forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
246 							TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, principalId);
247 							return mapping.findForward("closeBalanceTransferDoc");
248 						}
249 						else {
250 							ActionForward forward = mapping.findForward("basic");
251 							btf.setLeaveCalendarDocumentId(documentId);
252 							btf.setBalanceTransfer(balanceTransfer);
253 							btf.setTransferAmount(balanceTransfer.getTransferAmount());
254 							return forward;
255 						}
256 					}
257 					else
258 						throw new RuntimeException("could not initialize a balance transfer");
259 
260 				}
261 			}
262 			else
263 				throw new RuntimeException("No rule for this accrual category could be found");
264 		}
265 		else
266 			throw new RuntimeException("No accrual category rule id has been sent in the request.");
267 	}
268 
269 	//Entry point for BalanceTransfer.do for accrual category rule triggered transfers with action frequency Leave Approve.
270 	//TODO: Rename method to differentiate from ActionForward with same name in LeaveCalendarSubmit.
271 	public ActionForward approveLeaveCalendar(ActionMapping mapping, ActionForm form,
272 			HttpServletRequest request, HttpServletResponse response)
273 					throws Exception {
274 		
275 		GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferAmount.adjust");
276 		BalanceTransferForm btf = (BalanceTransferForm) form;
277 
278 		int categoryCounter = 0;
279 		List<String> transferableAccrualCategoryRules = new ArrayList<String>();
280 		String accrualRuleId = request.getParameter("accrualCategory0");
281 		while(ObjectUtils.isNotNull(accrualRuleId)) {
282 			//TODO: Get rid of this loop
283 			categoryCounter++;
284 			transferableAccrualCategoryRules.add(accrualRuleId);
285 			accrualRuleId = request.getParameter("accrualCategory"+categoryCounter);
286 		}
287 
288 		//Bad.... User must be prompted for each transfer that needs to be made.
289 		//For now, assuming not more than one accrual category is eligible for transfer.
290 		if(!transferableAccrualCategoryRules.isEmpty()) {
291 			//This is the leave calendar document that triggered this balance transfer.
292 			String leaveCalendarDocumentId = request.getParameter("documentId");
293 			ActionForward forward = new ActionForward(mapping.findForward("basic"));
294 			LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(leaveCalendarDocumentId);
295 			
296 			Date effectiveDate = TKUtils.getCurrentDate();
297 			if(TKUtils.getCurrentDate().after(DateUtils.addSeconds(lcd.getCalendarEntry().getEndPeriodDate(),-1)))
298 				effectiveDate = new Date(DateUtils.addSeconds(lcd.getCalendarEntry().getEndPeriodDate(),-1).getTime());
299 			
300 			LeaveSummary leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(lcd.getPrincipalId(), effectiveDate);
301 
302 			accrualRuleId = transferableAccrualCategoryRules.get(0);
303 			AccrualCategoryRule aRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
304 			LeaveSummaryRow transferRow = leaveSummary.getLeaveSummaryRowForAccrualCategory(aRule.getLmAccrualCategoryId());
305 			BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(lcd.getPrincipalId(), accrualRuleId, transferRow.getAccruedBalance(), effectiveDate);
306 			balanceTransfer.setLeaveCalendarDocumentId(leaveCalendarDocumentId);
307 
308 			if(ObjectUtils.isNotNull(balanceTransfer)) {
309 				AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
310 				if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
311 
312 					//TkServiceLocator.getBalanceTransferService().submitToWorkflow(balanceTransfer);
313 					balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
314 					// May need to update to save the business object to KPME's tables for record keeping.
315 					LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
316 					KRADServiceLocator.getBusinessObjectService().save(balanceTransfer);
317 					forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
318 					TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, lcd.getPrincipalId());
319 					
320 					ActionRedirect redirect = new ActionRedirect();
321 					if(ObjectUtils.isNotNull(leaveCalendarDocumentId)) {
322 						if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
323 								StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
324 							ActionForward loseForward = new ActionForward(mapping.findForward("leaveCalendarTransferSuccess"));
325 							loseForward.setPath(loseForward.getPath()+"?documentId="+leaveCalendarDocumentId+"&action=R&methodToCall=approveLeaveCalendar");
326 							return loseForward;
327 						}
328 						//on demand handled in separate action forward.
329 					}
330 
331 				} else {
332 					btf.setLeaveCalendarDocumentId(leaveCalendarDocumentId);
333 					btf.setBalanceTransfer(balanceTransfer);
334 					btf.setTransferAmount(balanceTransfer.getTransferAmount());
335 					return forward;
336 				}
337 
338 			}
339 			throw new RuntimeException("could not initialize balance transfer");
340 
341 		}
342 		else
343 			throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer");
344 	}
345 	
346 	public ActionForward approveTimesheet(ActionMapping mapping, ActionForm form,
347 			HttpServletRequest request, HttpServletResponse response)
348 					throws Exception {
349 		
350 		GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferAmount.adjust");
351 		BalanceTransferForm btf = (BalanceTransferForm) form;
352 
353 		int categoryCounter = 0;
354 		List<String> transferableAccrualCategoryRules = new ArrayList<String>();
355 		String accrualRuleId = request.getParameter("accrualCategory0");
356 		while(ObjectUtils.isNotNull(accrualRuleId)) {
357 			//TODO: Get rid of this loop
358 			categoryCounter++;
359 			transferableAccrualCategoryRules.add(accrualRuleId);
360 			accrualRuleId = request.getParameter("accrualCategory"+categoryCounter);
361 		}
362 
363 		//Bad.... User must be prompted for each transfer that needs to be made.
364 		//For now, assuming not more than one accrual category is eligible for transfer.
365 		if(!transferableAccrualCategoryRules.isEmpty()) {
366 			//This is the leave calendar document that triggered this balance transfer.
367 			String timesheetDocumentId = request.getParameter("documentId");
368 			ActionForward forward = new ActionForward(mapping.findForward("basic"));
369 			TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentId);
370 			CalendarEntries timeCalendarEntry = tsd.getCalendarEntry();
371 			
372 			Date effectiveDate = TKUtils.getCurrentDate();
373 			if(TKUtils.getCurrentDate().after(DateUtils.addSeconds(timeCalendarEntry.getEndPeriodDate(),-1)))
374 				effectiveDate = new Date(DateUtils.addSeconds(timeCalendarEntry.getEndPeriodDate(),-1).getTime());
375 				
376 				LeaveSummary leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(tsd.getPrincipalId(), effectiveDate);
377 
378 /*				Uncommenting changes pending input on KPME-2138
379  * 
380  * 				Calendar calendar = TkServiceLocator.getCalendarService().getCalendarByPrincipalIdAndDate(tsd.getPrincipalId(), timeCalendarEntry.getBeginPeriodDate(), true);
381 				Date beginDate = timeCalendarEntry.getBeginPeriodDate();
382 				Date endDate = timeCalendarEntry.getEndPeriodDate();
383 				List<CalendarEntries> leaveCalendarEntries = new ArrayList<CalendarEntries>();
384 				leaveCalendarEntries.addAll(TkServiceLocator.getCalendarEntriesService().getCalendarEntriesEndingBetweenBeginAndEndDate(calendar.getHrCalendarId(),	beginDate, endDate));
385 				if(!leaveCalendarEntries.isEmpty()) {
386 					if(leaveCalendarEntries.size() < 2) {
387 						CalendarEntries endingLeaveEntry = leaveCalendarEntries.get(0);
388 						effectiveDate = new Date(DateUtils.addMinutes(endingLeaveEntry.getEndPeriodDate(), -1).getTime());
389 					}
390 					else {
391 						throw new RuntimeException("Unable to determine effective date");
392 					}
393 				}
394 				else
395 					effectiveDate = new Date(DateUtils.addMinutes(timeCalendarEntry.getEndPeriodDate(),-1).getTime());
396 */
397 			
398 			accrualRuleId = transferableAccrualCategoryRules.get(0);
399 			AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualRuleId);
400 			LeaveSummaryRow transferRow = leaveSummary.getLeaveSummaryRowForAccrualCategory(accrualRule.getLmAccrualCategoryId());
401 			BalanceTransfer balanceTransfer = TkServiceLocator.getBalanceTransferService().initializeTransfer(tsd.getPrincipalId(), accrualRuleId, transferRow.getAccruedBalance(), effectiveDate);
402 			balanceTransfer.setLeaveCalendarDocumentId(timesheetDocumentId);
403 
404 			if(ObjectUtils.isNotNull(balanceTransfer)) {
405 				if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
406 					// TODO: Redirect user to prompt stating excess leave will be forfeited and ask for confirmation.
407 					// Do not submit the object to workflow for this max balance action.
408 					balanceTransfer = TkServiceLocator.getBalanceTransferService().transfer(balanceTransfer);
409 					KRADServiceLocator.getBusinessObjectService().save(balanceTransfer);
410 
411 					// May need to update to save the business object to KPME's tables for record keeping.
412 					LeaveBlock forfeitedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(balanceTransfer.getForfeitedLeaveBlockId());
413 					forfeitedLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
414 					TkServiceLocator.getLeaveBlockService().updateLeaveBlock(forfeitedLeaveBlock, tsd.getPrincipalId());
415 					ActionRedirect redirect = new ActionRedirect();
416 					if(ObjectUtils.isNotNull(timesheetDocumentId)) {
417 						if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE) ||
418 								StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(), LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
419 							ActionForward loseForward = new ActionForward(mapping.findForward("timesheetTransferSuccess"));
420 							loseForward.setPath(loseForward.getPath()+"?documentId="+timesheetDocumentId+"&action=R&methodToCall=approveTimesheet");
421 							return loseForward;
422 						}
423 						//on demand handled in separate action forward.
424 					}
425 
426 				} else {
427 					btf.setLeaveCalendarDocumentId(timesheetDocumentId);
428 					btf.setBalanceTransfer(balanceTransfer);
429 					btf.setTransferAmount(balanceTransfer.getTransferAmount());
430 					return forward;
431 				}
432 
433 			}
434 			throw new RuntimeException("could not initialize balance transfer");
435 
436 		}
437 		else
438 			throw new RuntimeException("unable to fetch the accrual category that triggerred this transfer");
439 	}
440 	
441 	public ActionForward closeBalanceTransferDoc(ActionMapping mapping, ActionForm form,
442 			HttpServletRequest request, HttpServletResponse response)
443 			throws Exception {
444 		return mapping.findForward("closeBalanceTransferDoc");
445 	}
446 	
447 	/* Delete system scheduled time off usage leave block from Leave or Time Calendar 
448 	 */
449 	public ActionForward deleteSSTOLeaveBlock(ActionMapping mapping, ActionForm form,
450 			HttpServletRequest request, HttpServletResponse response)
451 			throws Exception {
452 		BalanceTransferForm btf = (BalanceTransferForm) form;
453 		buildBalanceTransferForLeaveBlock(btf, request.getParameter("leaveBlockId"));
454 	
455 		return new ActionForward(mapping.findForward("basic"));
456 	}
457 	
458 	/* Build balance transfer based on the to-be-deleted leave block 
459 	 */
460 	private void buildBalanceTransferForLeaveBlock(BalanceTransferForm btf, String lbId) {
461 		LeaveBlock lb = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lbId);
462 		// this leave block is a ssto usage block, need to use it fo find the accrualed leave block which has a positive amount
463 		if(lb == null && StringUtils.isEmpty(lb.getScheduleTimeOffId())) {
464 			throw new RuntimeException("could not find the System Scheduled Time Off leave block that needs to be transferred!");	
465 		}
466 		SystemScheduledTimeOff ssto = TkServiceLocator.getSysSchTimeOffService().getSystemScheduledTimeOff(lb.getScheduleTimeOffId());
467 		BigDecimal amountTransferred = ssto.getTransferConversionFactor() == null ? lb.getLeaveAmount() : lb.getLeaveAmount().multiply(ssto.getTransferConversionFactor());
468 		EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(ssto.getTransfertoEarnCode(), lb.getLeaveDate());
469 		
470 		BalanceTransfer bt = new BalanceTransfer();
471 		bt.setTransferAmount(lb.getLeaveAmount().abs());	// the usage leave block's leave amount is negative
472 		bt.setFromAccrualCategory(lb.getAccrualCategory());
473 		bt.setAmountTransferred(amountTransferred.abs());
474 		bt.setToAccrualCategory(ec.getAccrualCategory());
475 		bt.setSstoId(lb.getScheduleTimeOffId());
476 		bt.setEffectiveDate(lb.getLeaveDate());
477 		bt.setPrincipalId(lb.getPrincipalId());
478 		
479 		btf.setBalanceTransfer(bt);
480 		btf.setTransferAmount(bt.getTransferAmount());
481 		GlobalVariables.getMessageMap().putWarning("document.newMaintainableObj.transferAmount","balanceTransfer.transferSSTO", 
482 				bt.getTransferAmount().toString(), bt.getAmountTransferred().toString());
483 	}
484 	/*
485 	 * Submit a balance transfer document when deleting a ssto usage leave block from current Leave/time calendar
486 	 * delete both accrued and usage ssto leave blocks, a pending transferred leave block is created by the BT doc
487 	 */
488 	public ActionForward balanceTransferOnSSTO(ActionMapping mapping, ActionForm form,
489 			HttpServletRequest request, HttpServletResponse response) throws Exception {
490 		BalanceTransferForm btf = (BalanceTransferForm) form;
491 		BalanceTransfer bt = btf.getBalanceTransfer();
492 		
493 		if(StringUtils.isEmpty(bt.getSstoId())) {
494 			throw new RuntimeException("System Scheduled Time Off not found for this balance transfer!");
495 		}
496 		List<LeaveBlock> lbList = TkServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(bt.getPrincipalId(), bt.getSstoId(), bt.getEffectiveDate());
497 		if(CollectionUtils.isEmpty(lbList) || (CollectionUtils.isNotEmpty(lbList) && lbList.size() != 2)) {
498 			throw new RuntimeException("There should be 2 system scheduled time off leave blocks!");
499 		}
500 		TkServiceLocator.getBalanceTransferService().submitToWorkflow(bt);
501 		// delete both SSTO accrualed and usage leave blocks
502 		for(LeaveBlock lb : lbList) {
503 			TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(lb.getLmLeaveBlockId(), lb.getPrincipalId());
504 		}
505 		return mapping.findForward("closeBalanceTransferDoc");
506 	}
507 
508 }