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.service;
017
018 import java.math.BigDecimal;
019 import java.sql.Date;
020 import java.util.ArrayList;
021 import java.util.Calendar;
022 import java.util.HashMap;
023 import java.util.List;
024 import java.util.Map;
025
026 import org.apache.commons.collections.CollectionUtils;
027 import org.apache.commons.lang.StringUtils;
028 import org.apache.commons.lang.time.DateUtils;
029 import org.joda.time.Interval;
030 import org.kuali.hr.lm.LMConstants;
031 import org.kuali.hr.lm.accrual.AccrualCategory;
032 import org.kuali.hr.lm.accrual.AccrualCategoryRule;
033 import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
034 import org.kuali.hr.lm.balancetransfer.dao.BalanceTransferDao;
035 import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
036 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
037 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
038 import org.kuali.hr.lm.leaveblock.LeaveBlock;
039 import org.kuali.hr.lm.leaveblock.LeaveBlockHistory;
040 import org.kuali.hr.lm.leaveplan.LeavePlan;
041 import org.kuali.hr.time.calendar.CalendarEntries;
042 import org.kuali.hr.time.principal.PrincipalHRAttributes;
043 import org.kuali.hr.time.service.base.TkServiceLocator;
044 import org.kuali.hr.time.util.TKUtils;
045 import org.kuali.hr.time.util.TkConstants;
046 import org.kuali.rice.kew.api.exception.WorkflowException;
047 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
048 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
049 import org.kuali.rice.krad.maintenance.MaintenanceDocument;
050 import org.kuali.rice.krad.service.KRADServiceLocator;
051 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
052 import org.kuali.rice.krad.util.KRADConstants;
053 import org.kuali.rice.krad.util.ObjectUtils;
054
055 public class BalanceTransferServiceImpl implements BalanceTransferService {
056
057 private BalanceTransferDao balanceTransferDao;
058
059 @Override
060 public List<BalanceTransfer> getAllBalanceTransfersForPrincipalId(
061 String principalId) {
062 return balanceTransferDao.getAllBalanceTransfersForPrincipalId(principalId);
063 }
064
065 @Override
066 public List<BalanceTransfer> getAllBalanceTransferForPrincipalIdAsOfDate(
067 String principalId, Date effectiveDate) {
068 return balanceTransferDao.getAllBalanceTransferForPrincipalIdAsOfDate(principalId,effectiveDate);
069 }
070
071 @Override
072 public List<BalanceTransfer> getAllBalanceTransferByEffectiveDate(
073 Date effectiveDate) {
074 return balanceTransferDao.getAllBalanceTransferByEffectiveDate(effectiveDate);
075 }
076
077 @Override
078 public BalanceTransfer getBalanceTransferById(String balanceTransferId) {
079 return balanceTransferDao.getBalanceTransferById(balanceTransferId);
080 }
081
082 @Override
083 public BalanceTransfer initializeTransfer(String principalId, String accrualCategoryRule, BigDecimal accruedBalance, Date effectiveDate) {
084 //Initially, principals may be allowed to edit the transfer amount when prompted to submit this balance transfer, however,
085 //a base transfer amount together with a forfeited amount is calculated to bring the balance back to its limit in accordance
086 //with transfer limits.
087 BalanceTransfer bt = null;
088 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRule);
089
090 if(ObjectUtils.isNotNull(accrualRule) && ObjectUtils.isNotNull(accruedBalance)) {
091 bt = new BalanceTransfer();
092 //Leave summary is not a requirement, per se, but the information it contains is.
093 //The only thing that is obtained from leave summary is the accrued balance of the leave summary row matching the
094 //passed accrualCategoryRules accrual category.
095 //These two objects are essential to balance transfers triggered when the employee submits their leave calendar for approval.
096 //Neither of these objects should be null, otherwise this method could not have been called.
097 AccrualCategory fromAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
098 AccrualCategory toAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getMaxBalanceTransferToAccrualCategory(),effectiveDate);
099 BigDecimal fullTimeEngagement = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate);
100
101 BigDecimal transferConversionFactor = null;
102 if(ObjectUtils.isNotNull(accrualRule.getMaxBalanceTransferConversionFactor()))
103 transferConversionFactor = accrualRule.getMaxBalanceTransferConversionFactor();
104
105 // AccrualRule.maxBalance == null -> no balance limit. No balance limit -> no accrual triggered transfer / payout / lose.
106 // execution point should not be here if max balance on accrualRule is null, unless there exists an employee override.
107 BigDecimal maxBalance = accrualRule.getMaxBalance();
108 BigDecimal adjustedMaxBalance = maxBalance.multiply(fullTimeEngagement).setScale(2);
109
110 BigDecimal maxTransferAmount = null;
111 BigDecimal adjustedMaxTransferAmount = null;
112 if(ObjectUtils.isNotNull(accrualRule.getMaxTransferAmount())) {
113 maxTransferAmount = new BigDecimal(accrualRule.getMaxTransferAmount());
114 adjustedMaxTransferAmount = maxTransferAmount.multiply(fullTimeEngagement).setScale(2);
115 }
116 else {
117 // no limit on transfer amount
118 maxTransferAmount = new BigDecimal(Long.MAX_VALUE);
119 adjustedMaxTransferAmount = maxTransferAmount;
120 }
121
122 BigDecimal maxCarryOver = null;
123 BigDecimal adjustedMaxCarryOver = null;
124 if(ObjectUtils.isNotNull(accrualRule.getMaxCarryOver())) {
125 maxCarryOver = new BigDecimal(accrualRule.getMaxCarryOver());
126 adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
127 }
128 else {
129 //no limit to carry over.
130 maxCarryOver = new BigDecimal(Long.MAX_VALUE);
131 adjustedMaxCarryOver = maxCarryOver;
132 }
133
134 List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, effectiveDate);
135 for(EmployeeOverride override : overrides) {
136 if(StringUtils.equals(override.getAccrualCategory(),fromAccrualCategory.getAccrualCategory())) {
137 if(StringUtils.equals(override.getOverrideType(),"MB"))
138 adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
139 //override values are not pro-rated for FTE.
140 if(StringUtils.equals(override.getOverrideType(),"MTA"))
141 adjustedMaxTransferAmount = new BigDecimal(override.getOverrideValue());
142 if(StringUtils.equals(override.getOverrideType(),"MAC"))
143 adjustedMaxCarryOver = new BigDecimal(override.getOverrideValue());
144 }
145 }
146
147
148 BigDecimal transferAmount = accruedBalance.subtract(adjustedMaxBalance);
149 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
150 //Move all time in excess of employee's fte adjusted max balance to forfeiture.
151 bt.setForfeitedAmount(transferAmount);
152 //There is no transfer to another accrual category.
153 bt.setTransferAmount(BigDecimal.ZERO);
154 bt.setAmountTransferred(BigDecimal.ZERO);
155 // to accrual category is a required field on maintenance document. Set as from accrual category
156 // to remove validation errors when routing, approving, etc.
157 bt.setToAccrualCategory(fromAccrualCategory.getAccrualCategory());
158 }
159 else {
160 // ACTION_AT_MAX_BAL = TRANSFER
161 bt.setToAccrualCategory(toAccrualCategory.getAccrualCategory());
162 if(transferAmount.compareTo(adjustedMaxTransferAmount) > 0) {
163 //there's forfeiture.
164 //bring transfer amount down to the adjusted maximum transfer amount, and place excess in forfeiture.
165 //accruedBalance - adjustedMaxTransferAmount - adjustedMaxBalance = forfeiture.
166 //transferAmount = accruedBalance - adjustedMaxBalance; forfeiture = transferAmount - adjustedMaxTransferAmount.
167 BigDecimal forfeiture = transferAmount.subtract(adjustedMaxTransferAmount).setScale(2);
168 forfeiture = forfeiture.stripTrailingZeros();
169 bt.setForfeitedAmount(forfeiture);
170 bt.setTransferAmount(adjustedMaxTransferAmount);
171 }
172 else {
173 bt.setTransferAmount(transferAmount);
174 bt.setForfeitedAmount(BigDecimal.ZERO);
175 }
176 }
177
178 // Max Carry Over logic for Year End transfers.
179 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
180 if(ObjectUtils.isNotNull(maxCarryOver)) {
181 if(ObjectUtils.isNull(adjustedMaxCarryOver))
182 adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
183 //otherwise, adjustedMaxCarryOver has an employee override value, which trumps accrual rule defined MAC.
184 //At this point, transfer amount and forfeiture have been set so that the new accrued balance will be the
185 //adjusted max balance, so this amount is used to check against carry over.
186 if(adjustedMaxBalance.compareTo(adjustedMaxCarryOver) > 0) {
187 BigDecimal carryOverDiff = adjustedMaxBalance.subtract(adjustedMaxCarryOver);
188
189 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)){
190 //add carry over excess to forfeiture.
191 bt.setForfeitedAmount(bt.getForfeitedAmount().add(carryOverDiff));
192 }
193 else {
194 //maximize the transfer amount.
195 BigDecimal potentialTransferAmount = bt.getTransferAmount().add(carryOverDiff);
196
197 //Can this amount be added to the transfer amount??
198 if(potentialTransferAmount.compareTo(adjustedMaxTransferAmount) <= 0) {
199 //yes
200 bt.setTransferAmount(bt.getTransferAmount().add(carryOverDiff));
201 }
202 else {
203 //no
204 BigDecimal carryOverExcess = potentialTransferAmount.subtract(adjustedMaxTransferAmount);
205 //move excess to forfeiture
206 bt.setForfeitedAmount(bt.getForfeitedAmount().add(carryOverExcess));
207 //the remainder (if any) can be added to the transfer amount ( unless action is LOSE ).
208 bt.setTransferAmount(bt.getTransferAmount().add(carryOverDiff.subtract(carryOverExcess)));
209 assert(bt.getTransferAmount().compareTo(adjustedMaxTransferAmount)==0);
210 }
211 }
212 }
213 //otherwise, given balance will be at or under the max annual carry over.
214 }
215 }
216
217 bt.setEffectiveDate(effectiveDate);
218 bt.setAccrualCategoryRule(accrualCategoryRule);
219 bt.setFromAccrualCategory(fromAccrualCategory.getAccrualCategory());
220 bt.setPrincipalId(principalId);
221 if(ObjectUtils.isNotNull(transferConversionFactor))
222 bt.setAmountTransferred(bt.getTransferAmount().multiply(transferConversionFactor).setScale(2));
223 else
224 bt.setAmountTransferred(bt.getTransferAmount());
225 }
226 return bt;
227 }
228
229 @Override
230 public BalanceTransfer transfer(BalanceTransfer balanceTransfer) {
231 if(ObjectUtils.isNull(balanceTransfer))
232 throw new RuntimeException("did not supply a valid BalanceTransfer object.");
233 else {
234 List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>();
235 BigDecimal transferAmount = balanceTransfer.getTransferAmount();
236 LeaveBlock aLeaveBlock = null;
237
238 if(ObjectUtils.isNotNull(balanceTransfer.getAmountTransferred())) {
239 if(balanceTransfer.getAmountTransferred().compareTo(BigDecimal.ZERO) > 0 ) {
240
241 aLeaveBlock = new LeaveBlock();
242 //Create a leave block that adds the adjusted transfer amount to the "transfer to" accrual category.
243 aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
244 aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
245 aLeaveBlock.setEarnCode(balanceTransfer.getCreditedAccrualCategory().getEarnCode());
246 aLeaveBlock.setAccrualCategory(balanceTransfer.getToAccrualCategory());
247 aLeaveBlock.setDescription("Amount transferred");
248 aLeaveBlock.setLeaveAmount(balanceTransfer.getAmountTransferred());
249 aLeaveBlock.setAccrualGenerated(true);
250 aLeaveBlock.setTransactionDocId(balanceTransfer.getDocumentHeaderId());
251 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
252 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
253 aLeaveBlock.setBlockId(0L);
254
255 //Want to store the newly created leave block id on this maintainable object
256 //when the status of the maintenance document encapsulating this maintainable changes
257 //the id will be used to fetch and update the leave block statuses.
258 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
259
260 balanceTransfer.setAccruedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
261 // save history
262 LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
263 lbh.setAction(LMConstants.ACTION.ADD);
264 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
265 leaveBlocks.add(aLeaveBlock);
266 }
267 }
268
269 if(ObjectUtils.isNotNull(transferAmount)) {
270 if(transferAmount.compareTo(BigDecimal.ZERO) > 0) {
271 //Create leave block that removes the correct transfer amount from the originating accrual category.
272 aLeaveBlock = new LeaveBlock();
273 aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
274 aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
275 aLeaveBlock.setEarnCode(balanceTransfer.getDebitedAccrualCategory().getEarnCode());
276 aLeaveBlock.setAccrualCategory(balanceTransfer.getFromAccrualCategory());
277 aLeaveBlock.setDescription("Transferred amount");
278 aLeaveBlock.setLeaveAmount(balanceTransfer.getTransferAmount().negate());
279 aLeaveBlock.setAccrualGenerated(true);
280 aLeaveBlock.setTransactionDocId(balanceTransfer.getDocumentHeaderId());
281 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
282 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
283 aLeaveBlock.setBlockId(0L);
284
285 //Want to store the newly created leave block id on this maintainable object.
286 //when the status of the maintenance document encapsulating this maintainable changes
287 //the id will be used to fetch and update the leave block statuses.
288 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
289
290 balanceTransfer.setDebitedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
291 // save history
292 LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
293 lbh.setAction(LMConstants.ACTION.ADD);
294 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
295
296 leaveBlocks.add(aLeaveBlock);
297 }
298 }
299
300 BigDecimal forfeitedAmount = balanceTransfer.getForfeitedAmount();
301 if(ObjectUtils.isNotNull(forfeitedAmount)) {
302 //Any amount forfeited must come out of the originating accrual category in order to bring balance back to max.
303 if(forfeitedAmount.compareTo(BigDecimal.ZERO) > 0) {
304 //for balance transfers with action = lose, transfer amount must be moved to forfeitedAmount
305 aLeaveBlock = new LeaveBlock();
306 aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
307 aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
308 aLeaveBlock.setEarnCode(balanceTransfer.getDebitedAccrualCategory().getEarnCode());
309 aLeaveBlock.setAccrualCategory(balanceTransfer.getFromAccrualCategory());
310 aLeaveBlock.setDescription("Forfeited balance transfer amount");
311 aLeaveBlock.setLeaveAmount(forfeitedAmount.negate());
312 aLeaveBlock.setAccrualGenerated(true);
313 aLeaveBlock.setTransactionDocId(balanceTransfer.getDocumentHeaderId());
314 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
315 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
316 aLeaveBlock.setBlockId(0L);
317
318 //Want to store the newly created leave block id on this maintainable object
319 //when the status of the maintenance document encapsulating this maintainable changes
320 //the id will be used to fetch and update the leave block statuses.
321 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
322
323 balanceTransfer.setForfeitedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
324 // save history
325 LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
326 lbh.setAction(LMConstants.ACTION.ADD);
327 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
328
329 leaveBlocks.add(aLeaveBlock);
330 }
331 }
332
333 return balanceTransfer;
334 }
335 }
336
337 //getMaxBalanceViolations...
338 @Override
339 public Map<String,ArrayList<String>> getEligibleTransfers(CalendarEntries calendarEntry, String principalId) throws Exception {
340 //Employee override check here, or return base-eligible accrual categories,
341 //filtering out those that have increased balance limits due to employee override in caller?
342 //null check inserted to fix LeaveCalendarWebTest failures on kpme-trunk-build-unit #2069
343 List<String> eligibleAccrualCategories = new ArrayList<String>();
344 Map<String, ArrayList<String>> eligibilities = new HashMap<String,ArrayList<String>>();
345 //TODO: create map for MAX_BAL_ACTION_FREQ in LMConstants
346 eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE, new ArrayList<String>());
347 eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END, new ArrayList<String>());
348 eligibilities.put(LMConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND, new ArrayList<String>());
349 if(ObjectUtils.isNull(calendarEntry)) {
350 return eligibilities;
351 }
352
353 PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, calendarEntry.getEndPeriodDate());
354 if(ObjectUtils.isNull(pha)) {
355 return eligibilities;
356 }
357
358 List<AccrualCategory> accrualCategories = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), calendarEntry.getEndPeriodDate());
359
360 org.kuali.hr.time.calendar.Calendar leaveCalendar = pha.getLeaveCalObj();
361 CalendarEntries thisLeaveEntry = null;
362 Interval thisEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
363 Date asOfDate = TKUtils.getCurrentDate();
364 if(TKUtils.getCurrentDate().after(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1)))
365 asOfDate = new Date(DateUtils.addSeconds(calendarEntry.getEndPeriodDate(),-1).getTime());
366
367 if(ObjectUtils.isNotNull(leaveCalendar)) {
368 for(CalendarEntries entry : leaveCalendar.getCalendarEntries()) {
369 if(thisEntryInterval.contains(DateUtils.addDays(entry.getEndPeriodTime(),-1).getTime()))
370 thisLeaveEntry = entry;
371 }
372 }
373 // this calendar entry interval does not contain a leave calendar's rollover date.
374 if(ObjectUtils.isNull(thisLeaveEntry))
375 return eligibilities;
376 //TODO: Find the end period date for the corresponding leave calendar.
377 // must check if this date falls within the interval of the calendar entries begin / end.
378 // if so, get the leave blocks and calculate the accrued balance.
379 //LeaveSummary leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, getCalendarEntry());
380 if(!accrualCategories.isEmpty()) {
381
382 LeaveSummary summary = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(principalId, asOfDate);
383 BigDecimal fte = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, calendarEntry.getEndPeriodDate());
384 List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, calendarEntry.getEndPeriodDate());
385
386 //null check inserted to fix LeaveCalendarWebTst failures on kpme-trunk-build-unit #2069
387 for(AccrualCategory accrualCategory : accrualCategories) {
388 //TODO: Iterate over Accrual Categories within this calendar entry.
389 AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, DateUtils.addDays(calendarEntry.getEndPeriodDate(), -1), pha.getServiceDate());
390 //Employee overrides...
391 if(ObjectUtils.isNotNull(rule)) {
392 if(StringUtils.equals(rule.getMaxBalFlag(),"Y")) {
393 if(StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.TRANSFER)
394 || StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
395 //There is a disagreement between the constant value LMConstants.MAX_BAL_ACTION_FREQ, and the value being
396 //set on LM_ACCRUAL_CATEGORY_RULES_T table. Temporarily have changed the constant to reflect the field
397 //value being set for MAX_BAL_ACTION_FREQ when accrual category rule records are saved.
398 if(ObjectUtils.isNotNull(rule.getMaxBalanceActionFrequency())) {
399 BigDecimal maxBalance = rule.getMaxBalance();
400
401 LeaveSummaryRow row = summary.getLeaveSummaryRowForAccrualCategory(accrualCategory.getLmAccrualCategoryId());
402 if(row == null)
403 continue;
404 BigDecimal accruedBalance = row.getAccruedBalance();
405 /* for(LeaveBlock leaveBlock : leaveBlockMap.get(accrualCategory.getAccrualCategory())) {
406 //TODO: limit leave blocks to those created on or after the calendar year period containing this calendar entry.
407 if(StringUtils.equals(leaveBlock.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED))
408 accruedBalance = accruedBalance.add(leaveBlock.getLeaveAmount());
409 }*/
410 BigDecimal adjustedMaxBalance = maxBalance.multiply(fte);
411 BigDecimal maxAnnualCarryOver = null;
412 if(ObjectUtils.isNotNull(rule.getMaxCarryOver())) {
413 maxAnnualCarryOver = new BigDecimal(rule.getMaxCarryOver());
414 }
415 BigDecimal adjustedMaxAnnualCarryOver = null;
416 if(ObjectUtils.isNotNull(maxAnnualCarryOver)) {
417 adjustedMaxAnnualCarryOver = maxAnnualCarryOver.multiply(fte);
418 }
419
420 for(EmployeeOverride override : overrides) {
421 if(StringUtils.equals(override.getAccrualCategory(),accrualCategory.getAccrualCategory())) {
422 if(StringUtils.equals(override.getOverrideType(),"MB")) {
423 adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
424 }
425 if(StringUtils.equals(override.getOverrideType(),"MAC")) {
426 adjustedMaxAnnualCarryOver = new BigDecimal(override.getOverrideValue());
427 }
428 //override values are not pro-rated.
429 }
430 }
431 //should extend a BalanceTransferBase class, or use an algorithm swapping pattern.
432 //allow institutions to extend/customize/implement their own max_bal_action_frequency types.
433 if(StringUtils.equals(rule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
434 //For year end transfer frequencies...
435 //Should use an "asOfDate" or effective date for principalHRAttributes. If getting eligibilities for a past calendar,
436 //pha may not be the same.
437 LeavePlan lp = TkServiceLocator.getLeavePlanService().getLeavePlan(pha.getLeavePlan(),TKUtils.getCurrentDate());
438 StringBuilder sb = new StringBuilder("");
439 String calendarYearStart = lp.getCalendarYearStart();
440 // mm/dd
441 sb.append(calendarYearStart+"/");
442 if(lp.getCalendarYearStartMonth().equals("01") && calendarEntry.getBeginPeriodDate().getMonth() == 11) {
443 //a calendar may start on 01/15, with monthly intervals.
444 //calendarEntry.beginPeriodDate.year = calendarYearStart.year - 1
445 sb.append(DateUtils.toCalendar(DateUtils.addYears(calendarEntry.getBeginPeriodDate(),1)).get(Calendar.YEAR));
446 }
447 else {
448 sb.append(DateUtils.toCalendar(calendarEntry.getBeginPeriodDateTime()).get(Calendar.YEAR));
449 }
450 //if the calendar being submitted is the final calendar in the leave plans calendar year.
451 //must check the calendar year start month. If its the first month of the year, add a year to the date.
452 //otherwise, the end period date and the calendar year start date have the same year.
453 if(thisEntryInterval.contains(DateUtils.addDays(TKUtils.formatDateString(sb.toString()),-1).getTime())) {
454 //BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
455 if(accruedBalance.compareTo(adjustedMaxBalance) > 0 ||
456 (ObjectUtils.isNotNull(adjustedMaxAnnualCarryOver) &&
457 accruedBalance.compareTo(adjustedMaxAnnualCarryOver) > 0)) {
458 eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
459 eligibilities.get(LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END).add(rule.getLmAccrualCategoryRuleId());
460 }
461 }
462 //otherwise its not transferable under year end frequency.
463 }
464 else {
465 //BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
466 if(accruedBalance.compareTo(adjustedMaxBalance) > 0 ) {
467 eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
468 eligibilities.get(rule.getMaxBalanceActionFrequency()).add(rule.getLmAccrualCategoryRuleId());
469 }
470 }
471 }
472 }
473 }
474 }
475 }
476 }
477 return eligibilities;
478
479 }
480
481
482 /* //TODO: Move to LeaveCalendarService or implement as an accessor on LeaveCalendarDocument object.
483 @Override
484 public List<String> getEligibleTransfers(CalendarEntries calendarEntry, String principalId, final String actionFrequency) throws Exception {
485 List<String> eligibleAccrualCategories = new ArrayList<String>();
486 //Employee override check here, or return base-eligible accrual categories,
487 //filtering out those that have increased balance limits due to employee override in caller?
488 if(ObjectUtils.isNotNull(calendarEntry)) {
489 //null check inserted to fix LeaveCalendarWebTest failures on kpme-trunk-build-unit #2069
490
491 LeaveSummary leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId,calendarEntry);
492 PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, calendarEntry.getEndPeriodDate());
493
494 org.kuali.hr.time.calendar.Calendar leaveCalendar = pha.getLeaveCalObj();
495 CalendarEntries thisLeaveEntry = null;
496 Interval thisEntryInterval = new Interval(calendarEntry.getBeginPeriodDate().getTime(),calendarEntry.getEndPeriodDate().getTime());
497
498 if(ObjectUtils.isNotNull(leaveCalendar)) {
499 for(CalendarEntries entry : leaveCalendar.getCalendarEntries()) {
500 if(thisEntryInterval.contains(DateUtils.addSeconds(entry.getEndPeriodTime(),-1).getTime()))
501 thisLeaveEntry = entry;
502 }
503 }
504
505 // this calendar entry interval does not contain a leave calendar's rollover date.
506 if(ObjectUtils.isNull(thisLeaveEntry))
507 return eligibleAccrualCategories;
508
509 if(ObjectUtils.isNotNull(leaveSummary)) {
510 //null check inserted to fix LeaveCalendarWebTst failures on kpme-trunk-build-unit #2069
511
512 for(LeaveSummaryRow lsr : leaveSummary.getLeaveSummaryRows()) {
513
514 String accrualCategoryRuleId = lsr.getAccrualCategoryRuleId();
515 AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRuleId);
516 //Employee overrides...
517 if(ObjectUtils.isNotNull(rule)) {
518 if(StringUtils.equals(rule.getMaxBalFlag(),"Y")) {
519 if(StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.TRANSFER)
520 || StringUtils.equals(rule.getActionAtMaxBalance(), LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
521 //There is a disagreement between the constant value LMConstants.MAX_BAL_ACTION_FREQ, and the value being
522 //set on LM_ACCRUAL_CATEGORY_RULES_T table. Temporarily have changed the constant to reflect the field
523 //value being set for MAX_BAL_ACTION_FREQ when accrual category rule records are saved.
524 if(StringUtils.equals(rule.getMaxBalanceActionFrequency(),actionFrequency)) {
525 BigDecimal maxBalance = rule.getMaxBalance();
526
527 BigDecimal fullTimeEngagement = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, TKUtils.getCurrentDate());
528 BigDecimal adjustedMaxBalance = maxBalance.multiply(fullTimeEngagement);
529 BigDecimal maxAnnualCarryOver = null;
530 if(ObjectUtils.isNotNull(rule.getMaxCarryOver()))
531 maxAnnualCarryOver = new BigDecimal(rule.getMaxCarryOver());
532 BigDecimal adjustedMaxAnnualCarryOver = null;
533 if(ObjectUtils.isNotNull(maxAnnualCarryOver))
534 adjustedMaxAnnualCarryOver = maxAnnualCarryOver.multiply(fullTimeEngagement);
535
536 List<EmployeeOverride> overrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId, TKUtils.getCurrentDate());
537 for(EmployeeOverride override : overrides) {
538 if(StringUtils.equals(override.getAccrualCategory(),lsr.getAccrualCategoryId())) {
539 if(StringUtils.equals(override.getOverrideType(),"MB"))
540 adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
541 if(StringUtils.equals(override.getOverrideType(),"MAC"))
542 adjustedMaxAnnualCarryOver = new BigDecimal(override.getOverrideValue());
543 //override values are not pro-rated.
544 }
545 }
546 //should extend a BalanceTransferBase class, or use an algorithm swapping pattern.
547 //allow institutions to extend/customize/implement their own max_bal_action_frequency types.
548 if(StringUtils.equals(actionFrequency,LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
549 //For year end transfer frequencies...
550 LeavePlan lp = TkServiceLocator.getLeavePlanService().getLeavePlan(pha.getLeavePlan(),TKUtils.getCurrentDate());
551 StringBuilder sb = new StringBuilder("");
552 String calendarYearStart = lp.getCalendarYearStart();
553 // mm/dd
554 sb.append(calendarYearStart+"/");
555 if(lp.getCalendarYearStartMonth().equals("01") && calendarEntry.getBeginPeriodDate().getMonth() == 11) {
556 //a calendar may start on 01/15, with monthly intervals.
557 sb.append(DateUtils.toCalendar(DateUtils.addYears(calendarEntry.getBeginPeriodDate(),1)).get(Calendar.YEAR));
558 }
559 else
560 sb.append(DateUtils.toCalendar(calendarEntry.getBeginPeriodDateTime()).get(Calendar.YEAR));
561 //if the calendar being submitted is the final calendar in the leave plans calendar year.
562 //must check the calendar year start month. If its the first month of the year, add a year to the date.
563 //otherwise, the end period date and the calendar year start date have the same year.
564 if(thisEntryInterval.contains(DateUtils.addSeconds(TKUtils.formatDateString(sb.toString()),-1).getTime())) {
565 //BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
566 if(lsr.getAccruedBalance().compareTo(adjustedMaxBalance) > 0 ||
567 (ObjectUtils.isNotNull(adjustedMaxAnnualCarryOver) &&
568 lsr.getAccruedBalance().compareTo(adjustedMaxAnnualCarryOver) > 0)) {
569 eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
570 }
571 }
572 //otherwise its not transferable under year end frequency.
573 }
574 else {
575 //BigDecimal accruedBalanceLessPendingTransfers = lsr.getAccruedBalance().add(adjustment);
576 if(lsr.getAccruedBalance().compareTo(adjustedMaxBalance) > 0 ) {
577 eligibleAccrualCategories.add(rule.getLmAccrualCategoryRuleId());
578 }
579 }
580 }
581 }
582 }
583 }
584 }
585 }
586 }
587 return eligibleAccrualCategories;
588 }
589 */
590 public BalanceTransferDao getBalanceTransferDao() {
591 return balanceTransferDao;
592 }
593
594 public void setBalanceTransferDao(BalanceTransferDao balanceTransferDao) {
595 this.balanceTransferDao = balanceTransferDao;
596 }
597
598 @Override
599 public void submitToWorkflow(BalanceTransfer balanceTransfer)
600 throws WorkflowException {
601
602 //balanceTransfer.setStatus(TkConstants.ROUTE_STATUS.ENROUTE);
603 EntityNamePrincipalName principalName = null;
604 if (balanceTransfer.getPrincipalId() != null) {
605 principalName = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(balanceTransfer.getPrincipalId());
606 }
607
608 MaintenanceDocument document = KRADServiceLocatorWeb.getMaintenanceDocumentService().setupNewMaintenanceDocument(BalanceTransfer.class.getName(),
609 "BalanceTransferDocumentType",KRADConstants.MAINTENANCE_NEW_ACTION);
610
611 String personName = (principalName != null && principalName.getDefaultName() != null) ? principalName.getDefaultName().getCompositeName() : StringUtils.EMPTY;
612 String date = TKUtils.formatDate(new java.sql.Date(balanceTransfer.getEffectiveDate().getTime()));
613 document.getDocumentHeader().setDocumentDescription(personName + " (" + balanceTransfer.getPrincipalId() + ") - " + date);
614 Map<String,String[]> params = new HashMap<String,String[]>();
615
616 KRADServiceLocatorWeb.getMaintenanceDocumentService().setupMaintenanceObject(document, KRADConstants.MAINTENANCE_NEW_ACTION, params);
617 BalanceTransfer btObj = (BalanceTransfer) document.getNewMaintainableObject().getDataObject();
618
619 btObj.setAccrualCategoryRule(balanceTransfer.getAccrualCategoryRule());
620 btObj.setEffectiveDate(balanceTransfer.getEffectiveDate());
621 btObj.setForfeitedAmount(balanceTransfer.getForfeitedAmount());
622 btObj.setFromAccrualCategory(balanceTransfer.getFromAccrualCategory());
623 btObj.setPrincipalId(balanceTransfer.getPrincipalId());
624 btObj.setToAccrualCategory(balanceTransfer.getToAccrualCategory());
625 btObj.setTransferAmount(balanceTransfer.getTransferAmount());
626 btObj.setAmountTransferred(balanceTransfer.getAmountTransferred());
627 btObj.setSstoId(balanceTransfer.getSstoId());
628 btObj.setDocumentHeaderId(document.getDocumentHeader().getWorkflowDocument().getDocumentId());
629 /* TkServiceLocator.getBalanceTransferService().saveOrUpdate(btObj);
630 document.getNewMaintainableObject().setDataObject(btObj);*/
631 KRADServiceLocatorWeb.getDocumentService().saveDocument(document);
632 document.getDocumentHeader().getWorkflowDocument().saveDocument("");
633
634 document.getDocumentHeader().getWorkflowDocument().route("");
635
636
637
638
639 }
640
641 @Override
642 public BalanceTransfer transferSsto(BalanceTransfer balanceTransfer) {
643 if(ObjectUtils.isNull(balanceTransfer))
644 throw new RuntimeException("did not supply a valid BalanceTransfer object.");
645 else {
646 List<LeaveBlock> sstoLbList = TkServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(balanceTransfer.getPrincipalId(), balanceTransfer.getSstoId(), balanceTransfer.getEffectiveDate());
647 String leaveDocId = "";
648 if(CollectionUtils.isNotEmpty(sstoLbList)) {
649 leaveDocId = sstoLbList.get(0).getDocumentId();
650 }
651 List<LeaveBlock> lbList = new ArrayList<LeaveBlock>();
652 // create a new leave block with transferred amount, make sure system scheduled timeoff id is added to it
653 LeaveBlock aLeaveBlock = new LeaveBlock();
654 aLeaveBlock.setPrincipalId(balanceTransfer.getPrincipalId());
655 aLeaveBlock.setLeaveDate(balanceTransfer.getEffectiveDate());
656 aLeaveBlock.setEarnCode(balanceTransfer.getCreditedAccrualCategory().getEarnCode());
657 aLeaveBlock.setAccrualCategory(balanceTransfer.getToAccrualCategory());
658 aLeaveBlock.setDescription("System Scheduled Time off Amount transferred");
659 aLeaveBlock.setLeaveAmount(balanceTransfer.getAmountTransferred());
660 aLeaveBlock.setAccrualGenerated(false);
661 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
662 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
663 aLeaveBlock.setBlockId(0L);
664 aLeaveBlock.setScheduleTimeOffId(balanceTransfer.getSstoId());
665 aLeaveBlock.setDocumentId(leaveDocId);
666
667 lbList.add(aLeaveBlock);
668 TkServiceLocator.getLeaveBlockService().saveLeaveBlocks(lbList);
669
670 balanceTransfer.setAccruedLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
671 return balanceTransfer;
672 }
673 }
674
675 @Override
676 public List<BalanceTransfer> getBalanceTransfers(String viewPrincipal,
677 Date beginPeriodDate, Date endPeriodDate) {
678 // TODO Auto-generated method stub
679 return balanceTransferDao.getBalanceTransfers(viewPrincipal, beginPeriodDate, endPeriodDate);
680 }
681
682 @Override
683 public void saveOrUpdate(BalanceTransfer balanceTransfer) {
684 // TODO Auto-generated method stub
685 balanceTransferDao.saveOrUpdate(balanceTransfer);
686 }
687 }