View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.hr.time.earncode.service;
17  
18  import java.math.BigDecimal;
19  import java.sql.Date;
20  import java.util.ArrayList;
21  import java.util.Comparator;
22  import java.util.LinkedHashMap;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.kuali.hr.job.Job;
30  import org.kuali.hr.lm.LMConstants;
31  import org.kuali.hr.lm.accrual.AccrualCategory;
32  import org.kuali.hr.lm.earncodesec.EarnCodeSecurity;
33  import org.kuali.hr.lm.earncodesec.EarnCodeType;
34  import org.kuali.hr.time.assignment.Assignment;
35  import org.kuali.hr.time.collection.rule.TimeCollectionRule;
36  import org.kuali.hr.time.earncode.EarnCode;
37  import org.kuali.hr.time.earncode.dao.EarnCodeDao;
38  import org.kuali.hr.time.principal.PrincipalHRAttributes;
39  import org.kuali.hr.time.roles.TkUserRoles;
40  import org.kuali.hr.time.service.base.TkServiceLocator;
41  import org.kuali.hr.time.util.TKContext;
42  import org.kuali.hr.time.util.TKUser;
43  import org.kuali.hr.time.util.TKUtils;
44  import org.kuali.hr.time.util.TkConstants;
45  import org.kuali.hr.time.workarea.WorkArea;
46  import org.kuali.rice.krad.util.GlobalVariables;
47  
48  import com.google.common.collect.Ordering;
49  
50  public class EarnCodeServiceImpl implements EarnCodeService {
51  
52  	private EarnCodeDao earnCodeDao;
53  
54  	public void setEarnCodeDao(EarnCodeDao earnCodeDao) {
55  		this.earnCodeDao = earnCodeDao;
56  	}
57  
58      public List<EarnCode> getEarnCodesForLeaveAndTime(Assignment a, Date asOfDate, boolean isLeavePlanningCalendar) {
59          //  This method combining both leave calendar and timesheet calendar earn codes may never be used, but it is available.
60          //  It was specified in kpme-1745, "Implement getEarnCodesForLeaveAndTime and call both of the above methods and return in one collection."
61          List<EarnCode> earnCodes = getEarnCodesForTime(a, asOfDate);
62          List<EarnCode> leaveEarnCodes = getEarnCodesForLeave(a, asOfDate, isLeavePlanningCalendar);
63          //  the following list processing does work as hoped, comparing the objects' data, rather than their references to memory structures.
64          earnCodes.removeAll(leaveEarnCodes); //ensures no overlap during the addAll
65          earnCodes.addAll(leaveEarnCodes);
66  
67          return earnCodes;
68      }
69  
70      public List<EarnCode> getEarnCodesForTime(Assignment a, Date asOfDate) {
71          //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.
72  
73          if (a == null) throw new RuntimeException("No assignment parameter.");
74          Job job = a.getJob();
75          if (job == null || job.getPayTypeObj() == null) throw new RuntimeException("Null job or null job pay type on assignment.");
76  
77          List<EarnCode> earnCodes = new LinkedList<EarnCode>();
78          String earnTypeCode = EarnCodeType.TIME.getCode();
79  
80          TimeCollectionRule tcr = a.getTimeCollectionRule();
81          
82          boolean isClockUser = tcr == null || tcr.isClockUserFl();
83          boolean isUsersTimesheet = StringUtils.equals(TKContext.getPrincipalId(),a.getPrincipalId());
84  
85          // Reg earn codes will typically not be defined in the earn code security table
86          EarnCode regularEarnCode = getEarnCode(job.getPayTypeObj().getRegEarnCode(), asOfDate);
87          if (regularEarnCode == null) {
88              throw new RuntimeException("No regular earn code defined for job pay type.");
89          } else {
90              //  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.
91              if (isClockUser && isUsersTimesheet) {
92                  // do not add reg earn code. use clock.
93              } else {
94                  earnCodes.add(regularEarnCode);
95              }
96          }
97  
98          List<String> listAccrualCategories = new LinkedList<String>();
99          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 }