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.time.earncode.service;
017
018 import java.math.BigDecimal;
019 import java.sql.Date;
020 import java.util.ArrayList;
021 import java.util.Comparator;
022 import java.util.LinkedHashMap;
023 import java.util.LinkedList;
024 import java.util.List;
025 import java.util.Map;
026 import java.util.Set;
027
028 import org.apache.commons.lang.StringUtils;
029 import org.kuali.hr.job.Job;
030 import org.kuali.hr.lm.LMConstants;
031 import org.kuali.hr.lm.accrual.AccrualCategory;
032 import org.kuali.hr.lm.earncodesec.EarnCodeSecurity;
033 import org.kuali.hr.lm.earncodesec.EarnCodeType;
034 import org.kuali.hr.time.assignment.Assignment;
035 import org.kuali.hr.time.collection.rule.TimeCollectionRule;
036 import org.kuali.hr.time.earncode.EarnCode;
037 import org.kuali.hr.time.earncode.dao.EarnCodeDao;
038 import org.kuali.hr.time.principal.PrincipalHRAttributes;
039 import org.kuali.hr.time.roles.TkUserRoles;
040 import org.kuali.hr.time.service.base.TkServiceLocator;
041 import org.kuali.hr.time.util.TKContext;
042 import org.kuali.hr.time.util.TKUser;
043 import org.kuali.hr.time.util.TKUtils;
044 import org.kuali.hr.time.util.TkConstants;
045 import org.kuali.hr.time.workarea.WorkArea;
046 import org.kuali.rice.krad.util.GlobalVariables;
047
048 import com.google.common.collect.Ordering;
049
050 public class EarnCodeServiceImpl implements EarnCodeService {
051
052 private EarnCodeDao earnCodeDao;
053
054 public void setEarnCodeDao(EarnCodeDao earnCodeDao) {
055 this.earnCodeDao = earnCodeDao;
056 }
057
058 public List<EarnCode> getEarnCodesForLeaveAndTime(Assignment a, Date asOfDate, boolean isLeavePlanningCalendar) {
059 // This method combining both leave calendar and timesheet calendar earn codes may never be used, but it is available.
060 // It was specified in kpme-1745, "Implement getEarnCodesForLeaveAndTime and call both of the above methods and return in one collection."
061 List<EarnCode> earnCodes = getEarnCodesForTime(a, asOfDate);
062 List<EarnCode> leaveEarnCodes = getEarnCodesForLeave(a, asOfDate, isLeavePlanningCalendar);
063 // the following list processing does work as hoped, comparing the objects' data, rather than their references to memory structures.
064 earnCodes.removeAll(leaveEarnCodes); //ensures no overlap during the addAll
065 earnCodes.addAll(leaveEarnCodes);
066
067 return earnCodes;
068 }
069
070 public List<EarnCode> getEarnCodesForTime(Assignment a, Date asOfDate) {
071 //getEarnCodesForTime and getEarnCodesForLeave have some overlapping logic, but they were separated so that they could follow their own distinct logic, so consolidation of logic is not desirable.
072
073 if (a == null) throw new RuntimeException("No assignment parameter.");
074 Job job = a.getJob();
075 if (job == null || job.getPayTypeObj() == null) throw new RuntimeException("Null job or null job pay type on assignment.");
076
077 List<EarnCode> earnCodes = new LinkedList<EarnCode>();
078 String earnTypeCode = EarnCodeType.TIME.getCode();
079
080 TimeCollectionRule tcr = a.getTimeCollectionRule();
081
082 boolean isClockUser = tcr == null || tcr.isClockUserFl();
083 boolean isUsersTimesheet = StringUtils.equals(TKContext.getPrincipalId(),a.getPrincipalId());
084
085 // Reg earn codes will typically not be defined in the earn code security table
086 EarnCode regularEarnCode = getEarnCode(job.getPayTypeObj().getRegEarnCode(), asOfDate);
087 if (regularEarnCode == null) {
088 throw new RuntimeException("No regular earn code defined for job pay type.");
089 } else {
090 // if you are a clock user and this is your timesheet and you are processing the reg earn code, do not add this earn code. Use the clock in/out mechanism.
091 if (isClockUser && isUsersTimesheet) {
092 // do not add reg earn code. use clock.
093 } else {
094 earnCodes.add(regularEarnCode);
095 }
096 }
097
098 List<String> listAccrualCategories = new LinkedList<String>();
099 String accrualCategory;
100
101 // first make a list of the accrual categories available to the user's Leave Plan (yes, leave plan), for later comparison.
102 PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate);
103 boolean fmlaEligible = principalHRAttributes.isFmlaEligible();
104 boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible();
105
106 String leavePlan = principalHRAttributes.getLeavePlan();
107 if (leavePlan != null) {
108 for (AccrualCategory accrualCategories : TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) {
109 accrualCategory = accrualCategories.getAccrualCategory();
110 if(accrualCategory != null) {
111 listAccrualCategories.add(accrualCategory);
112 }
113 }
114 }
115
116 // get all earn codes by user security, then we'll filter on accrual category first as we process them.
117 List<EarnCodeSecurity> decs = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), asOfDate);
118 for (EarnCodeSecurity dec : decs) {
119
120 boolean addEarnCode = addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate);
121 if (addEarnCode) {
122
123 // allow types Time AND Both
124 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) {
125 EarnCode ec = getEarnCode(dec.getEarnCode(), asOfDate);
126
127 // make sure we got something back from the earn code dao
128 if (ec != null) {
129
130 // now that we have a list of security earn codes, compare their accrual categories to the user's accrual category list.
131 // we also allow earn codes that have no accrual category assigned.
132 if (listAccrualCategories.contains(ec.getAccrualCategory()) || ec.getAccrualCategory() == null) {
133
134 // if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown.
135 // if the fmla flag on a code is yes they can see it. (allow)
136 // if the fmla flag on a code is no they should see it. (allow)
137 // if the user's fmla flag is No,
138 // they can see any codes which are fmla=no. (allow)
139 // they can not see codes with fmla=yes. (exclude earn code)
140 // the fmla earn codes=no do not require any exclusion
141 // the only action required is if the fmla user flag=no: exclude those codes with fmla=yes.
142
143 if ( (fmlaEligible || ec.getFmla().equals("N")) ) {
144 // go on, we are allowing these three combinations: YY, YN, NN
145
146 // apply the same logic as FMLA to the Worker Compensation flags.
147 if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) {
148 // go on, we are allowing these three combinations: YY, YN, NN.
149
150 // determine if the holiday earn code should be displayed.
151 if ( showEarnCodeIfHoliday(ec, dec) ) {
152 // non-Holiday earn code will go on, Holiday earn code must meet some requirements in the method.
153
154 if ( StringUtils.equals(regularEarnCode.toString(), dec.getEarnCode()) ) {
155 // do not add the reg earn code here. the reg earn code has already been processed prior to this decs loop.
156
157 } else {
158 // add earn code if it is not the reg earn code.
159 earnCodes.add(ec);
160 }
161 } else {
162 // the showEarnCodeIfHoliday method determined that we should not add the Holiday earn code.
163 }
164 } else {
165 // do not add this earn code. User WC flag=no and earn code WC flag=yes.
166 }
167 } else {
168 // do not add this earn code. User FMLA flag=no and earn code FMLA flag=yes.
169 }
170 } else {
171 // do not add this earn code. accrual category does not allow.
172 }
173 } else {
174 // skip null earn code
175 }
176 } else {
177 // do not add this earn code. the leave type does not allow.
178 }
179 } else {
180 // do not add this earn code. user and approver settings disallow.
181 }
182 }
183
184 return earnCodes;
185 }
186
187 public List<EarnCode> getEarnCodesForLeave(Assignment a, Date asOfDate, boolean isLeavePlanningCalendar) {
188 //getEarnCodesForTime and getEarnCodesForLeave have some overlapping logic, but they were separated so that they could follow their own distinct logic, so consolidation of logic is not desirable.
189
190 if (a == null) throw new RuntimeException("No assignment parameter.");
191 Job job = a.getJob();
192 if (job == null || job.getPayTypeObj() == null) throw new RuntimeException("Null job or null job pay type on assignment.");
193
194 List<EarnCode> earnCodes = new LinkedList<EarnCode>();
195 String earnTypeCode = EarnCodeType.LEAVE.getCode();
196 // skip getting the regular earn code for Leave Calendar
197
198 List<String> listAccrualCategories = new LinkedList<String>();
199 String accrualCategory;
200
201 // first make a list of the accrual categories available to the user's leave plan, for later comparison.
202 PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate);
203 boolean fmlaEligible = principalHRAttributes.isFmlaEligible();
204 boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible();
205
206 String leavePlan = principalHRAttributes.getLeavePlan();
207 if (leavePlan != null) {
208 for (AccrualCategory accrualCategories : TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) {
209 accrualCategory = accrualCategories.getAccrualCategory();
210 if(accrualCategory != null) {
211 listAccrualCategories.add(accrualCategory);
212 }
213 }
214 }
215
216 // get all earn codes by user security, then we'll filter on accrual category first as we process them.
217 List<EarnCodeSecurity> decs = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), asOfDate);
218 for (EarnCodeSecurity dec : decs) {
219
220 boolean addEarnCode = addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate);
221 if (addEarnCode) {
222
223 // allow types Leave AND Both
224 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) {
225 EarnCode ec = getEarnCode(dec.getEarnCode(), asOfDate);
226
227 // make sure we got something back from the earn code dao
228 if (ec != null) {
229
230 // now that we have a list of security earn codes, compare their accrual categories to the user's accrual category list.
231 // we also allow earn codes that have no accrual category assigned.
232 if (listAccrualCategories.contains(ec.getAccrualCategory()) || ec.getAccrualCategory() == null) {
233
234 // if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown.
235 // if the fmla flag on a code is yes they can see it. (allow)
236 // if the fmla flag on a code is no they should see it. (allow)
237 // if the user's fmla flag is No,
238 // they can see any codes which are fmla=no. (allow)
239 // they can not see codes with fmla=yes. (exclude earn code)
240 // the fmla earn codes=no do not require any exclusion
241 // the only action required is if the fmla user flag=no: exclude those codes with fmla=yes.
242
243 if ( (fmlaEligible || ec.getFmla().equals("N")) ) {
244 // go on, we are allowing these three combinations: YY, YN, NN
245
246 // Apply the same logic as FMLA to the Worker Compensation flags.
247 if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) {
248 // go on, we are allowing these three combinations: YY, YN, NN.
249
250 // now process the scheduled leave flag, but only for the Planning Calendar, not for the Reporting Calendar.
251 // determine if the planning calendar is in effect.
252 if (isLeavePlanningCalendar) {
253
254 // if the allow_schd_leave flag=yes, add the earn code
255 if (ec.getAllowScheduledLeave().equals("Y")) {
256 earnCodes.add(ec);
257 } else {
258 // do not add this earn code. Earn code allowed scheduled leave flag=no.
259 }
260
261 } else {
262 // this is a reporting calendar, so ignore scheduled leave flag, and add this earn code.
263 earnCodes.add(ec);
264 }
265
266 } else {
267 // do not add this earn code. User WC flag=no and earn code WC flag=yes.
268 }
269 } else {
270 // do not add this earn code. User FMLA flag=no and earn code FMLA flag=yes.
271 }
272 } else {
273 // do not add this earn code. accrual category does not allow.
274 }
275 } else {
276 // skip null earn code
277 }
278 } else {
279 // do not add this earn code. the leave type does not allow.
280 }
281 } else {
282 // do not add this earn code. user and approver settings disallow.
283 }
284 } // end of decs loop
285
286 return earnCodes;
287 }
288
289 private boolean addEarnCodeBasedOnEmployeeApproverSettings(EarnCodeSecurity security, Assignment a, Date asOfDate) {
290 boolean addEarnCode = false;
291 if (security.isEmployee() &&
292 (StringUtils.equals(TKUser.getCurrentTargetPerson().getEmployeeId(), GlobalVariables.getUserSession().getPerson().getEmployeeId()))) {
293 addEarnCode = true;
294 }
295 // Check approver flag
296 if (!addEarnCode && security.isApprover()) {
297 Set<Long> workAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
298 for (Long wa : workAreas) {
299 WorkArea workArea = TkServiceLocator.getWorkAreaService().getWorkArea(wa, asOfDate);
300 if (workArea!= null && a.getWorkArea().compareTo(workArea.getWorkArea())==0) {
301 addEarnCode = true;
302 break;
303 }
304 }
305 }
306 return addEarnCode;
307 }
308
309 private boolean showEarnCodeIfHoliday(EarnCode earnCode, EarnCodeSecurity security) {
310 if (earnCode.getEarnCode().equals(TkConstants.HOLIDAY_EARN_CODE)) {
311 if (security.isApprover() || TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).isSystemAdmin()) {
312 return true;
313 } else {
314 return false;
315 }
316 } else {
317 return true;
318 }
319 }
320
321 @Override
322 public List<EarnCode> getEarnCodesForPrincipal(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) {
323 List<EarnCode> earnCodes = new LinkedList<EarnCode>();
324 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate);
325 for (Assignment assignment : assignments) {
326 List<EarnCode> assignmentEarnCodes = getEarnCodesForLeave(assignment, asOfDate, isLeavePlanningCalendar);
327 // the following list processing does work as hoped, comparing the objects' data, rather than their references to memory structures.
328 earnCodes.removeAll(assignmentEarnCodes); //ensures no overlap during the addAll
329 earnCodes.addAll(assignmentEarnCodes);
330 }
331
332 return earnCodes;
333 }
334
335 public EarnCode getEarnCode(String earnCode, Date asOfDate) {
336 return earnCodeDao.getEarnCode(earnCode, asOfDate);
337 }
338
339 @Override
340 public String getEarnCodeType(String earnCode, Date asOfDate) {
341 EarnCode earnCodeObj = getEarnCode(earnCode, asOfDate);
342 return earnCodeObj.getEarnCodeType();
343 }
344
345 @Override
346 public EarnCode getEarnCodeById(String earnCodeId) {
347 return earnCodeDao.getEarnCodeById(earnCodeId);
348 }
349
350 public List<EarnCode> getOvertimeEarnCodes(Date asOfDate){
351 return earnCodeDao.getOvertimeEarnCodes(asOfDate);
352 }
353
354 public List<String> getOvertimeEarnCodesStrs(Date asOfDate){
355 List<String> ovtEarnCodeStrs = new ArrayList<String>();
356 List<EarnCode> ovtEarnCodes = getOvertimeEarnCodes(asOfDate);
357 if(ovtEarnCodes != null){
358 for(EarnCode ovtEc : ovtEarnCodes){
359 ovtEarnCodeStrs.add(ovtEc.getEarnCode());
360 }
361 }
362 return ovtEarnCodeStrs;
363 }
364
365 @Override
366 public int getEarnCodeCount(String earnCode) {
367 return earnCodeDao.getEarnCodeCount(earnCode);
368 }
369
370 @Override
371 public int getNewerEarnCodeCount(String earnCode, Date effdt) {
372 return earnCodeDao.getNewerEarnCodeCount(earnCode, effdt);
373 }
374
375 @Override
376 public BigDecimal roundHrsWithEarnCode(BigDecimal hours, EarnCode earnCode) {
377 String roundOption = LMConstants.ROUND_OPTION_MAP.get(earnCode.getRoundingOption());
378 BigDecimal fractScale = new BigDecimal(earnCode.getFractionalTimeAllowed());
379 if(roundOption == null) {
380 throw new RuntimeException("Rounding option of Earn Code " + earnCode.getEarnCode() + " is not recognized.");
381 }
382 BigDecimal roundedHours = hours;
383 if(roundOption.equals("Traditional")) {
384 roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_HALF_EVEN);
385 } else if(roundOption.equals("Truncate")) {
386 roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_DOWN);
387 }
388 return roundedHours;
389 }
390
391 @Override
392 public Map<String, String> getEarnCodesForDisplay(String principalId, boolean isLeavePlanningCalendar) {
393 return getEarnCodesForDisplayWithEffectiveDate(principalId, TKUtils.getCurrentDate(), isLeavePlanningCalendar);
394 }
395
396 public List<EarnCode> getEarnCodes(String earnCode, String ovtEarnCode, String descr, String leavePlan, String accrualCategory, Date fromEffdt, Date toEffdt, String active, String showHist) {
397 return earnCodeDao.getEarnCodes(earnCode, ovtEarnCode, descr, leavePlan, accrualCategory, fromEffdt, toEffdt, active, showHist);
398 }
399
400 @Override
401 public Map<String, String> getEarnCodesForDisplayWithEffectiveDate(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) {
402 List<EarnCode> earnCodes = this.getEarnCodesForPrincipal(principalId, asOfDate, isLeavePlanningCalendar);
403
404 Date currentDate = TKUtils.getCurrentDate();
405 boolean futureDate = asOfDate.after(currentDate);
406 List<EarnCode> copyList = new ArrayList<EarnCode>();
407 copyList.addAll(earnCodes);
408 for (EarnCode earnCode : copyList) {
409 if ( futureDate
410 && !earnCode.getAllowScheduledLeave().equalsIgnoreCase("Y")) {
411 earnCodes.remove(earnCode);
412 }
413 }
414 Comparator<EarnCode> earnCodeComparator = new Comparator<EarnCode>() {
415 @Override
416 public int compare(EarnCode ec1, EarnCode ec2) {
417 return ec1.getEarnCode().compareToIgnoreCase(ec2.getEarnCode());
418 }
419 };
420 // Order by leaveCode ascending
421 Ordering<EarnCode> ordering = Ordering.from(earnCodeComparator);
422
423 Map<String, String> earnCodesForDisplay = new LinkedHashMap<String, String>();
424 for (EarnCode earnCode : ordering.sortedCopy(earnCodes)) {
425 earnCodesForDisplay.put(earnCode.getEarnCodeKeyForDisplay(), earnCode.getEarnCodeValueForDisplay());
426 }
427 return earnCodesForDisplay;
428 }
429
430 }