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.balancetransfer.web;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.ArrayList;
021    import java.util.List;
022    
023    import javax.servlet.http.HttpServletRequest;
024    import javax.servlet.http.HttpServletResponse;
025    
026    import org.apache.commons.collections.CollectionUtils;
027    import org.apache.commons.lang.time.DateUtils;
028    import org.apache.commons.lang3.StringUtils;
029    import org.apache.struts.action.ActionForm;
030    import org.apache.struts.action.ActionForward;
031    import org.apache.struts.action.ActionMapping;
032    import org.apache.struts.action.ActionRedirect;
033    import org.kuali.hr.lm.LMConstants;
034    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
035    import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
036    import org.kuali.hr.lm.balancetransfer.validation.BalanceTransferValidationUtils;
037    import org.kuali.hr.lm.leaveSummary.LeaveSummary;
038    import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
039    import org.kuali.hr.lm.leaveblock.LeaveBlock;
040    import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
041    import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
042    import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
043    import org.kuali.hr.time.base.web.TkAction;
044    import org.kuali.hr.time.calendar.Calendar;
045    import org.kuali.hr.time.calendar.CalendarEntries;
046    import org.kuali.hr.time.earncode.EarnCode;
047    import org.kuali.hr.time.service.base.TkServiceLocator;
048    import org.kuali.hr.time.timesheet.TimesheetDocument;
049    import org.kuali.hr.time.util.TKUtils;
050    import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
051    import org.kuali.rice.krad.service.KRADServiceLocator;
052    import org.kuali.rice.krad.util.GlobalVariables;
053    import org.kuali.rice.krad.util.ObjectUtils;
054    
055    public class BalanceTransferAction extends TkAction {
056    
057            public ActionForward balanceTransferOnLeaveApproval(ActionMapping mapping, ActionForm form,
058                            HttpServletRequest request, HttpServletResponse response) throws Exception {
059    
060                    //if action was submit, execute the transfer
061                    BalanceTransferForm btf = (BalanceTransferForm) form;
062                    BalanceTransfer balanceTransfer = btf.getBalanceTransfer();
063            
064                    boolean valid = BalanceTransferValidationUtils.validateTransfer(balanceTransfer);
065                    
066                    //if transfer amount has changed, and the resulting change produces forfeiture
067                    //or changes the forfeiture amount, prompt for confirmation with the amount of
068                    //forfeiture that the entered amount would produce.
069    
070                    if(valid) {
071                            
072                            String accrualRuleId = balanceTransfer.getAccrualCategoryRule();
073                            
074                            String documentId = balanceTransfer.getLeaveCalendarDocumentId();
075                            TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
076                            LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
077                            CalendarEntries calendarEntry = null;
078                            String strutsActionForward = "";
079                            String methodToCall = "approveLeaveCalendar";
080                            if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
081                                    throw new RuntimeException("No document found");
082                            }
083                            else if(ObjectUtils.isNotNull(tsdh)) {
084                                    //Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
085                                    TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
086                                    calendarEntry = tsd.getCalendarEntry();
087                                    strutsActionForward = "timesheetTransferSuccess";
088                                    methodToCall = "approveTimesheet";
089                            }
090                            else {
091                                    LeaveCalendarDocument lcd = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
092                                    calendarEntry = lcd.getCalendarEntry();
093                                    strutsActionForward = "leaveCalendarTransferSuccess";
094                                    methodToCall = "approveLeaveCalendar";
095                            }
096                            
097                            if(ObjectUtils.isNull(calendarEntry)) {
098                                    throw new RuntimeException("Could not retreive calendar entry for document " + documentId);
099                            }
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    }