001/**
002 * Copyright 2004-2014 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 */
016package org.kuali.kpme.tklm.leave.transfer.web;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.log4j.Logger;
021import org.apache.struts.action.ActionForm;
022import org.apache.struts.action.ActionForward;
023import org.apache.struts.action.ActionMapping;
024import org.apache.struts.action.ActionRedirect;
025import org.joda.time.LocalDate;
026import org.kuali.kpme.core.api.accrualcategory.AccrualCategory;
027import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRule;
028import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRuleContract;
029import org.kuali.kpme.core.api.calendar.entry.CalendarEntry;
030import org.kuali.kpme.core.api.earncode.EarnCode;
031import org.kuali.kpme.core.service.HrServiceLocator;
032import org.kuali.kpme.core.util.HrConstants;
033import org.kuali.kpme.core.web.KPMEAction;
034import org.kuali.kpme.tklm.api.leave.block.LeaveBlock;
035import org.kuali.kpme.tklm.api.leave.timeoff.SystemScheduledTimeOffContract;
036import org.kuali.kpme.tklm.leave.block.LeaveBlockBo;
037import org.kuali.kpme.tklm.leave.calendar.LeaveCalendarDocument;
038import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
039import org.kuali.kpme.tklm.leave.transfer.BalanceTransfer;
040import org.kuali.kpme.tklm.leave.transfer.validation.BalanceTransferValidationUtils;
041import org.kuali.kpme.tklm.leave.workflow.LeaveCalendarDocumentHeader;
042import org.kuali.kpme.tklm.time.service.TkServiceLocator;
043import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
044import org.kuali.kpme.tklm.time.workflow.TimesheetDocumentHeader;
045import org.kuali.rice.krad.util.GlobalVariables;
046import org.kuali.rice.krad.util.KRADConstants;
047import org.kuali.rice.krad.util.ObjectUtils;
048
049import javax.servlet.http.HttpServletRequest;
050import javax.servlet.http.HttpServletResponse;
051import java.math.BigDecimal;
052import java.util.Collections;
053import java.util.Comparator;
054import java.util.List;
055
056public class BalanceTransferAction extends KPMEAction {
057
058        private static final Logger LOG = Logger.getLogger(BalanceTransferAction.class);
059        
060        public ActionForward balanceTransferOnLeaveApproval(ActionMapping mapping, ActionForm form,
061                        HttpServletRequest request, HttpServletResponse response) throws Exception {
062
063                //if action was submit, execute the transfer
064                BalanceTransferForm btf = (BalanceTransferForm) form;
065                BalanceTransfer balanceTransfer = btf.getBalanceTransfer();
066        
067                boolean valid = BalanceTransferValidationUtils.validateTransfer(balanceTransfer);
068                
069                //if transfer amount has changed, and the resulting change produces forfeiture
070                //or changes the forfeiture amount, prompt for confirmation with the amount of
071                //forfeiture that the entered amount would produce.
072
073                if(valid) {
074                        
075                        String accrualRuleId = balanceTransfer.getAccrualCategoryRule();
076                        
077                        String documentId = balanceTransfer.getLeaveCalendarDocumentId();
078                        TimesheetDocumentHeader tsdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
079                        LeaveCalendarDocumentHeader lcdh = LmServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
080            CalendarEntry calendarEntry = null;
081                        String strutsActionForward = "";
082                        String methodToCall;
083                        if(ObjectUtils.isNull(tsdh) && ObjectUtils.isNull(lcdh)) {
084                                LOG.error("No document found");
085                                GlobalVariables.getMessageMap().putError(KRADConstants.GLOBAL_ERRORS, "error.document.notfound");
086                                return mapping.findForward("basic");
087//                              throw new RuntimeException("No document found");
088                        }
089                        else if(ObjectUtils.isNotNull(tsdh)) {
090                                //Throws runtime exception, separate action forwards for timesheet/leave calendar transfers.
091                                TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(documentId);
092                                calendarEntry = tsd != null ? tsd.getCalendarEntry() : null;
093                                strutsActionForward = "timesheetTransferSuccess";
094                                methodToCall = "approveTimesheet";
095                        }
096                        else {
097                                LeaveCalendarDocument lcd = LmServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(documentId);
098                                calendarEntry = lcd != null ? lcd.getCalendarEntry() : null;
099                                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}