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.leavepayout.service;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.apache.commons.lang.time.DateUtils;
020 import org.joda.time.DateTime;
021 import org.joda.time.Interval;
022 import org.kuali.hr.lm.LMConstants;
023 import org.kuali.hr.lm.accrual.AccrualCategory;
024 import org.kuali.hr.lm.accrual.AccrualCategoryRule;
025 import org.kuali.hr.lm.leavepayout.LeavePayout;
026 import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
027 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
028 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
029 import org.kuali.hr.lm.leaveblock.LeaveBlock;
030 import org.kuali.hr.lm.leaveblock.LeaveBlockHistory;
031 import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
032 import org.kuali.hr.lm.leavepayout.LeavePayout;
033 import org.kuali.hr.lm.leavepayout.dao.LeavePayoutDao;
034
035 import org.kuali.hr.lm.leaveplan.LeavePlan;
036 import org.kuali.hr.time.calendar.CalendarEntries;
037 import org.kuali.hr.time.earncode.EarnCode;
038 import org.kuali.hr.time.principal.PrincipalHRAttributes;
039 import org.kuali.hr.time.service.base.TkServiceLocator;
040 import org.kuali.hr.time.util.TKContext;
041 import org.kuali.hr.time.util.TKUtils;
042 import org.kuali.hr.time.util.TkConstants;
043 import org.kuali.rice.kew.api.exception.WorkflowException;
044 import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
045 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
046 import org.kuali.rice.krad.maintenance.MaintenanceDocument;
047 import org.kuali.rice.krad.service.KRADServiceLocator;
048 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
049 import org.kuali.rice.krad.util.KRADConstants;
050 import org.kuali.rice.krad.util.ObjectUtils;
051
052 import edu.emory.mathcs.backport.java.util.Collections;
053
054 import java.math.BigDecimal;
055 import java.sql.Date;
056 import java.util.ArrayList;
057 import java.util.Calendar;
058 import java.util.Comparator;
059 import java.util.HashMap;
060 import java.util.HashSet;
061 import java.util.List;
062 import java.util.Map;
063 import java.util.Set;
064
065 public class LeavePayoutServiceImpl implements LeavePayoutService {
066
067 private LeavePayoutDao leavePayoutDao;
068
069 @Override
070 public List<LeavePayout> getAllLeavePayoutsForPrincipalId(
071 String principalId) {
072 return leavePayoutDao.getAllLeavePayoutsForPrincipalId(principalId);
073 }
074
075 @Override
076 public List<LeavePayout> getAllLeavePayoutsForPrincipalIdAsOfDate(
077 String principalId, Date effectiveDate) {
078 return leavePayoutDao.getAllLeavePayoutsForPrincipalIdAsOfDate(principalId,effectiveDate);
079 }
080
081 @Override
082 public List<LeavePayout> getAllLeavePayoutsByEffectiveDate(
083 Date effectiveDate) {
084 return leavePayoutDao.getAllLeavePayoutsByEffectiveDate(effectiveDate);
085 }
086
087 @Override
088 public LeavePayout getLeavePayoutById(String lmLeavePayoutId) {
089 return leavePayoutDao.getLeavePayoutById(lmLeavePayoutId);
090 }
091
092 public LeavePayoutDao getLeavePayoutDao() {
093 return leavePayoutDao;
094 }
095
096 public void setLeavePayoutDao(LeavePayoutDao leavePayoutDao) {
097 this.leavePayoutDao = leavePayoutDao;
098 }
099
100 @Override
101 public LeavePayout initializePayout(String principalId,
102 String accrualCategoryRule, BigDecimal accruedBalance,
103 Date effectiveDate) {
104 //Initially, principals may be allowed to edit the transfer amount when prompted to submit this balance transfer, however,
105 //a base transfer amount together with a forfeited amount is calculated to bring the balance back to its limit in accordance
106 //with transfer limits.
107 LeavePayout leavePayout = null;
108 AccrualCategoryRule accrualRule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(accrualCategoryRule);
109
110 if(ObjectUtils.isNotNull(accrualRule) && ObjectUtils.isNotNull(accruedBalance)) {
111 leavePayout = new LeavePayout();
112 //Leave summary is not a requirement, per se, but the information it contains is.
113 //The only thing that is obtained from leave summary is the accrued balance of the leave summary row matching the
114 //passed accrualCategoryRules accrual category.
115 //These two objects are essential to balance transfers triggered when the employee submits their leave calendar for approval.
116 //Neither of these objects should be null, otherwise this method could not have been called.
117 AccrualCategory fromAccrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(accrualRule.getLmAccrualCategoryId());
118 BigDecimal fullTimeEngagement = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate);
119
120 // AccrualRule.maxBalance == null -> no balance limit. No balance limit -> no accrual triggered transfer / payout / lose.
121 // execution point should not be here if max balance on accrualRule is null, unless there exists an employee override.
122 BigDecimal maxBalance = accrualRule.getMaxBalance();
123 BigDecimal adjustedMaxBalance = maxBalance.multiply(fullTimeEngagement).setScale(2);
124
125 BigDecimal maxPayoutAmount = null;
126 BigDecimal adjustedMaxPayoutAmount = null;
127 if(ObjectUtils.isNotNull(accrualRule.getMaxPayoutAmount())) {
128 maxPayoutAmount = new BigDecimal(accrualRule.getMaxPayoutAmount());
129 adjustedMaxPayoutAmount = maxPayoutAmount.multiply(fullTimeEngagement).setScale(2);
130 }
131 else {
132 // no limit on transfer amount
133 maxPayoutAmount = new BigDecimal(Long.MAX_VALUE);
134 adjustedMaxPayoutAmount = maxPayoutAmount;
135 }
136
137 BigDecimal maxCarryOver = null;
138 BigDecimal adjustedMaxCarryOver = null;
139 if(ObjectUtils.isNotNull(accrualRule.getMaxCarryOver())) {
140 maxCarryOver = new BigDecimal(accrualRule.getMaxCarryOver());
141 adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
142 }
143 else {
144 //no limit to carry over.
145 maxCarryOver = new BigDecimal(Long.MAX_VALUE);
146 adjustedMaxCarryOver = maxCarryOver;
147 }
148
149 EmployeeOverride maxBalanceOverride = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MB", effectiveDate);
150 EmployeeOverride maxPayoutAmountOverride = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MPA", effectiveDate);
151 EmployeeOverride maxAnnualCarryOverOverride = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, fromAccrualCategory.getLeavePlan(), fromAccrualCategory.getAccrualCategory(), "MAC", effectiveDate);
152 //Do not pro-rate override values for FTE.
153 if(maxBalanceOverride != null)
154 adjustedMaxBalance = new BigDecimal(maxBalanceOverride.getOverrideValue());
155 if(maxPayoutAmountOverride != null)
156 adjustedMaxPayoutAmount = new BigDecimal(maxPayoutAmountOverride.getOverrideValue());
157 if(maxAnnualCarryOverOverride != null)
158 adjustedMaxCarryOver = new BigDecimal(maxAnnualCarryOverOverride.getOverrideValue());
159
160
161 BigDecimal transferAmount = accruedBalance.subtract(adjustedMaxBalance);
162 if(transferAmount.compareTo(adjustedMaxPayoutAmount) > 0) {
163 //there's forfeiture.
164 //bring transfer amount down to the adjusted maximum transfer amount, and place excess in forfeiture.
165 //accruedBalance - adjustedMaxPayoutAmount - adjustedMaxBalance = forfeiture.
166 //transferAmount = accruedBalance - adjustedMaxBalance; forfeiture = transferAmount - adjustedMaxPayoutAmount.
167 BigDecimal forfeiture = transferAmount.subtract(adjustedMaxPayoutAmount).setScale(2);
168 forfeiture = forfeiture.stripTrailingZeros();
169 leavePayout.setForfeitedAmount(forfeiture);
170 leavePayout.setPayoutAmount(adjustedMaxPayoutAmount);
171 }
172 else {
173 leavePayout.setPayoutAmount(transferAmount);
174 leavePayout.setForfeitedAmount(BigDecimal.ZERO);
175 }
176
177 assert(adjustedMaxBalance.compareTo(accruedBalance.subtract(leavePayout.getPayoutAmount().add(leavePayout.getForfeitedAmount()))) == 0);
178
179 // Max Carry Over logic for Year End transfers.
180 if(StringUtils.equals(accrualRule.getMaxBalanceActionFrequency(),LMConstants.MAX_BAL_ACTION_FREQ.YEAR_END)) {
181 if(ObjectUtils.isNotNull(maxCarryOver)) {
182
183 if(ObjectUtils.isNull(adjustedMaxCarryOver))
184 adjustedMaxCarryOver = maxCarryOver.multiply(fullTimeEngagement).setScale(2);
185 //otherwise, adjustedMaxCarryOver has an employee override value, which trumps accrual rule defined MAC.
186 //At this point, transfer amount and forfeiture have been set so that the new accrued balance will be the
187 //adjusted max balance, so this amount is used to check against carry over.
188 if(adjustedMaxBalance.compareTo(adjustedMaxCarryOver) > 0) {
189 BigDecimal carryOverDiff = adjustedMaxBalance.subtract(adjustedMaxCarryOver);
190
191 if(StringUtils.equals(accrualRule.getActionAtMaxBalance(),LMConstants.ACTION_AT_MAX_BAL.LOSE)){
192 //add carry over excess to forfeiture.
193 leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverDiff));
194 }
195 else {
196 //maximize the transfer amount.
197 BigDecimal potentialPayoutAmount = leavePayout.getPayoutAmount().add(carryOverDiff);
198
199 //Can this entire amount be added to the transfer amount??
200 if(potentialPayoutAmount.compareTo(adjustedMaxPayoutAmount) <= 0) {
201 //yes
202 leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff));
203 }
204 else {
205 //no
206 BigDecimal carryOverExcess = potentialPayoutAmount.subtract(adjustedMaxPayoutAmount);
207 //move excess to forfeiture
208 leavePayout.setForfeitedAmount(leavePayout.getForfeitedAmount().add(carryOverExcess));
209 //the remainder (if any) can be added to the transfer amount ( unless action is LOSE ).
210 leavePayout.setPayoutAmount(leavePayout.getPayoutAmount().add(carryOverDiff.subtract(carryOverExcess)));
211
212 assert(adjustedMaxCarryOver.compareTo(accruedBalance.subtract(leavePayout.getPayoutAmount().add(leavePayout.getForfeitedAmount()))) == 0);
213 }
214 }
215 }
216 //otherwise, given balance will be at or under the max annual carry over.
217 }
218 }
219
220 leavePayout.setEffectiveDate(effectiveDate);
221 leavePayout.setAccrualCategoryRule(accrualCategoryRule);
222 leavePayout.setFromAccrualCategory(fromAccrualCategory.getAccrualCategory());
223 leavePayout.setPrincipalId(principalId);
224 leavePayout.setEarnCode(accrualRule.getMaxPayoutEarnCode());
225
226 }
227 return leavePayout;
228 }
229
230 @Override
231 public LeavePayout payout(LeavePayout leavePayout) {
232 if(ObjectUtils.isNull(leavePayout))
233 throw new RuntimeException("did not supply a valid LeavePayout object.");
234 else {
235 List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>();
236 BigDecimal transferAmount = leavePayout.getPayoutAmount();
237 LeaveBlock aLeaveBlock = null;
238
239 if(ObjectUtils.isNotNull(transferAmount)) {
240 if(transferAmount.compareTo(BigDecimal.ZERO) > 0) {
241
242 aLeaveBlock = new LeaveBlock();
243 //Create a leave block that adds the adjusted transfer amount to the "transfer to" accrual category.
244 aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
245 aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
246 aLeaveBlock.setEarnCode(leavePayout.getEarnCode());
247 aLeaveBlock.setAccrualCategory(leavePayout.getEarnCodeObj().getAccrualCategory());
248 aLeaveBlock.setDescription("Amount payed out");
249 aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount());
250 aLeaveBlock.setAccrualGenerated(true);
251 aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
252 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
253 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
254 aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
255 aLeaveBlock.setBlockId(0L);
256
257 //Want to store the newly created leave block id on this maintainable object
258 //when the status of the maintenance document encapsulating this maintainable changes
259 //the id will be used to fetch and update the leave block statuses.
260 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
261
262 leavePayout.setPayoutLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
263 // save history
264 LeaveBlockHistory lbh = new LeaveBlockHistory(aLeaveBlock);
265 lbh.setAction(LMConstants.ACTION.ADD);
266 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
267 leaveBlocks.add(aLeaveBlock);
268
269 //Create leave block that removes the correct transfer amount from the originating accrual category.
270 aLeaveBlock = new LeaveBlock();
271 aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
272 aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
273 aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode());
274 aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory());
275 aLeaveBlock.setDescription("Payout amount");
276 aLeaveBlock.setLeaveAmount(leavePayout.getPayoutAmount().negate());
277 aLeaveBlock.setAccrualGenerated(true);
278 aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
279 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
280 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
281 aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
282 aLeaveBlock.setBlockId(0L);
283
284 //Want to store the newly created leave block id on this maintainable object.
285 //when the status of the maintenance document encapsulating this maintainable changes
286 //the id will be used to fetch and update the leave block statuses.
287 aLeaveBlock = KRADServiceLocator.getBusinessObjectService().save(aLeaveBlock);
288
289 leavePayout.setPayoutFromLeaveBlockId(aLeaveBlock.getLmLeaveBlockId());
290 // save history
291 lbh = new LeaveBlockHistory(aLeaveBlock);
292 lbh.setAction(LMConstants.ACTION.ADD);
293 TkServiceLocator.getLeaveBlockHistoryService().saveLeaveBlockHistory(lbh);
294
295 leaveBlocks.add(aLeaveBlock);
296 }
297 }
298
299 BigDecimal forfeitedAmount = leavePayout.getForfeitedAmount();
300 if(ObjectUtils.isNotNull(forfeitedAmount)) {
301 //Any amount forfeited must come out of the originating accrual category in order to bring balance back to max.
302 if(forfeitedAmount.compareTo(BigDecimal.ZERO) > 0) {
303 //for balance transfers with action = lose, transfer amount must be moved to forfeitedAmount
304 aLeaveBlock = new LeaveBlock();
305 aLeaveBlock.setPrincipalId(leavePayout.getPrincipalId());
306 aLeaveBlock.setLeaveDate(leavePayout.getEffectiveDate());
307 aLeaveBlock.setEarnCode(leavePayout.getFromAccrualCategoryObj().getEarnCode());
308 aLeaveBlock.setAccrualCategory(leavePayout.getFromAccrualCategory());
309 aLeaveBlock.setDescription("Forfeited payout amount");
310 aLeaveBlock.setLeaveAmount(forfeitedAmount.negate());
311 aLeaveBlock.setAccrualGenerated(true);
312 aLeaveBlock.setTransactionDocId(leavePayout.getDocumentHeaderId());
313 aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
314 aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.REQUESTED);
315 aLeaveBlock.setDocumentId(leavePayout.getLeaveCalendarDocumentId());
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 leavePayout.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 leavePayout;
334 }
335 }
336
337 @Override
338 public void submitToWorkflow(LeavePayout leavePayout)
339 throws WorkflowException {
340 //leavePayout.setStatus(TkConstants.ROUTE_STATUS.ENROUTE);
341 EntityNamePrincipalName principalName = null;
342 if (leavePayout.getPrincipalId() != null) {
343 principalName = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(leavePayout.getPrincipalId());
344 }
345
346 MaintenanceDocument document = KRADServiceLocatorWeb.getMaintenanceDocumentService().setupNewMaintenanceDocument(LeavePayout.class.getName(),
347 "LeavePayoutDocumentType",KRADConstants.MAINTENANCE_NEW_ACTION);
348
349 String personName = (principalName != null && principalName.getDefaultName() != null) ? principalName.getDefaultName().getCompositeName() : StringUtils.EMPTY;
350 String date = TKUtils.formatDate(new java.sql.Date(leavePayout.getEffectiveDate().getTime()));
351 document.getDocumentHeader().setDocumentDescription(personName + " (" + leavePayout.getPrincipalId() + ") - " + date);
352 Map<String,String[]> params = new HashMap<String,String[]>();
353
354 KRADServiceLocatorWeb.getMaintenanceDocumentService().setupMaintenanceObject(document, KRADConstants.MAINTENANCE_NEW_ACTION, params);
355 LeavePayout lpObj = (LeavePayout) document.getNewMaintainableObject().getDataObject();
356
357 lpObj.setAccrualCategoryRule(leavePayout.getAccrualCategoryRule());
358 lpObj.setEffectiveDate(leavePayout.getEffectiveDate());
359 lpObj.setLeaveCalendarDocumentId(leavePayout.getLeaveCalendarDocumentId());
360 lpObj.setForfeitedAmount(leavePayout.getForfeitedAmount());
361 lpObj.setFromAccrualCategory(leavePayout.getFromAccrualCategory());
362 lpObj.setPrincipalId(leavePayout.getPrincipalId());
363 lpObj.setEarnCode(leavePayout.getEarnCode());
364 lpObj.setPayoutAmount(leavePayout.getPayoutAmount());
365 lpObj.setDocumentHeaderId(document.getDocumentHeader().getWorkflowDocument().getDocumentId());
366
367 document.getNewMaintainableObject().setDataObject(lpObj);
368 KRADServiceLocatorWeb.getDocumentService().saveDocument(document);
369 document.getDocumentHeader().getWorkflowDocument().saveDocument("");
370
371 document.getDocumentHeader().getWorkflowDocument().route("");
372 }
373
374 @Override
375 public List<LeavePayout> getLeavePayouts(String viewPrincipal,
376 Date beginPeriodDate, Date endPeriodDate) {
377 // TODO Auto-generated method stub
378 return leavePayoutDao.getLeavePayouts(viewPrincipal, beginPeriodDate, endPeriodDate);
379 }
380
381 @Override
382 public void saveOrUpdate(LeavePayout payout) {
383 leavePayoutDao.saveOrUpdate(payout);
384 }
385 }