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