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                                 //only want usage accrual balance actions
145                                 if (StringUtils.equals(ec.getAccrualBalanceAction(), LMConstants.ACCRUAL_BALANCE_ACTION.USAGE)) {
146                                 // go on, we are allowing these three combinations: YY, YN, NN
147 
148                                     //  apply the same logic as FMLA to the Worker Compensation flags.
149                                     if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) {
150                                         // go on, we are allowing these three combinations: YY, YN, NN.
151 
152                                         //  determine if the holiday earn code should be displayed.
153                                         if ( showEarnCodeIfHoliday(ec, dec) ) {
154                                             //  non-Holiday earn code will go on, Holiday earn code must meet some requirements in the method.
155                                             if ( !StringUtils.equals(regularEarnCode.toString(), dec.getEarnCode()) ) {
156                                                 //  add earn code if it is not the reg earn code.
157                                                 earnCodes.add(ec);
158                                             }
159                                         }
160                                     }
161                                 }
162                             }
163                         }
164                     }
165                 }
166             }
167         }
168 
169         return earnCodes;
170     }
171 
172     public List<EarnCode> getEarnCodesForLeave(Assignment a, Date asOfDate, boolean isLeavePlanningCalendar) {
173         //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.
174 
175         if (a == null) throw new RuntimeException("No assignment parameter.");
176         Job job = a.getJob();
177         if (job == null || job.getPayTypeObj() == null) throw new RuntimeException("Null job or null job pay type on assignment.");
178 
179         List<EarnCode> earnCodes = new LinkedList<EarnCode>();
180         String earnTypeCode = EarnCodeType.LEAVE.getCode();
181         // skip getting the regular earn code for Leave Calendar
182 
183         List<String> listAccrualCategories = new LinkedList<String>();
184         String accrualCategory;
185 
186         //  first make a list of the accrual categories available to the user's leave plan, for later comparison.
187         PrincipalHRAttributes principalHRAttributes = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(job.getPrincipalId(), asOfDate);
188         boolean fmlaEligible = principalHRAttributes.isFmlaEligible();
189         boolean workersCompEligible = principalHRAttributes.isWorkersCompEligible();
190 
191         String leavePlan = principalHRAttributes.getLeavePlan();
192         if (leavePlan != null) {
193             for (AccrualCategory accrualCategories : TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(leavePlan, asOfDate)) {
194                 accrualCategory = accrualCategories.getAccrualCategory();
195                 if(accrualCategory != null) {
196                     listAccrualCategories.add(accrualCategory);
197                 }
198             }
199         }
200 
201         //  get all earn codes by user security, then we'll filter on accrual category first as we process them.
202         List<EarnCodeSecurity> decs = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), asOfDate);
203         for (EarnCodeSecurity dec : decs) {
204 
205             boolean addEarnCode = addEarnCodeBasedOnEmployeeApproverSettings(dec, a, asOfDate);
206             if (addEarnCode) {
207 
208                 //  allow types Leave AND Both
209                 if (earnTypeCode.equals(dec.getEarnCodeType()) || EarnCodeType.BOTH.getCode().equals(dec.getEarnCodeType())) {
210                     EarnCode ec = getEarnCode(dec.getEarnCode(), asOfDate);
211 
212                     //  make sure we got something back from the earn code dao
213                     if (ec != null) {
214 
215                         //  now that we have a list of security earn codes, compare their accrual categories to the user's accrual category list.
216                         //  we also allow earn codes that have no accrual category assigned.
217                         if (listAccrualCategories.contains(ec.getAccrualCategory()) || ec.getAccrualCategory() == null) {
218 
219                             //  if the user's fmla flag is Yes, that means we are not restricting codes based on this flag, so any code is shown.
220                             //    if the fmla flag on a code is yes they can see it.    (allow)
221                             //    if the fmla flag on a code is no they should see it.  (allow)
222                             //  if the user's fmla flag is No,
223                             //    they can see any codes which are fmla=no.             (allow)
224                             //    they can not see codes with fmla=yes.                 (exclude earn code)
225                             //  the fmla earn codes=no do not require any exclusion
226                             //  the only action required is if the fmla user flag=no: exclude those codes with fmla=yes.
227 
228                             if ( (fmlaEligible || ec.getFmla().equals("N")) ) {
229                                 // go on, we are allowing these three combinations: YY, YN, NN
230 
231                                 //  Apply the same logic as FMLA to the Worker Compensation flags.
232                                 if ( (workersCompEligible || ec.getWorkmansComp().equals("N")) ) {
233                                     // go on, we are allowing these three combinations: YY, YN, NN.
234 
235                                     //accrual balance action needs to be usage
236                                     if (StringUtils.equals(ec.getAccrualBalanceAction(), LMConstants.ACCRUAL_BALANCE_ACTION.USAGE)) {
237                                         //  now process the scheduled leave flag, but only for the Planning Calendar, not for the Reporting Calendar.
238                                         //  determine if the planning calendar is in effect.
239                                         if (isLeavePlanningCalendar) {
240 
241                                             //  if the allow_schd_leave flag=yes, add the earn code
242                                             if (ec.getAllowScheduledLeave().equals("Y")) {
243                                                     earnCodes.add(ec);
244                                             }
245 
246                                         } else {
247                                             //  this is a reporting calendar, so ignore scheduled leave flag, and add this earn code.
248                                             earnCodes.add(ec);
249                                         }
250                                     }
251                                 }
252                             }
253                         }
254                     }
255                 }
256             }
257         }   //  end of decs loop
258 
259         return earnCodes;
260     }
261 
262     private boolean addEarnCodeBasedOnEmployeeApproverSettings(EarnCodeSecurity security, Assignment a, Date asOfDate) {
263         boolean addEarnCode = false;
264         if (security.isEmployee() &&
265                 (StringUtils.equals(TKUser.getCurrentTargetPerson().getEmployeeId(), GlobalVariables.getUserSession().getPerson().getEmployeeId()))) {
266             addEarnCode = true;
267         }
268         // Check approver flag
269         if (!addEarnCode && security.isApprover()) {
270             Set<Long> workAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
271             for (Long wa : workAreas) {
272                 WorkArea workArea = TkServiceLocator.getWorkAreaService().getWorkArea(wa, asOfDate);
273                 if (workArea!= null && a.getWorkArea().compareTo(workArea.getWorkArea())==0) {
274                     addEarnCode = true;
275                     break;
276                 }
277             }
278         }
279         return addEarnCode;
280     }
281 
282     private boolean showEarnCodeIfHoliday(EarnCode earnCode, EarnCodeSecurity security) {
283         if (earnCode.getEarnCode().equals(TkConstants.HOLIDAY_EARN_CODE)) {
284             if (security.isApprover() || TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).isSystemAdmin()) {
285                 return true;
286             } else {
287                 return false;
288             }
289         } else {
290             return true;
291         }
292     }
293 
294     @Override
295     public List<EarnCode> getEarnCodesForPrincipal(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) {
296         List<EarnCode> earnCodes = new LinkedList<EarnCode>();
297         List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate);
298         for (Assignment assignment : assignments) {
299             List<EarnCode> assignmentEarnCodes = getEarnCodesForLeave(assignment, asOfDate, isLeavePlanningCalendar);
300             //  the following list processing does work as hoped, comparing the objects' data, rather than their references to memory structures.
301             earnCodes.removeAll(assignmentEarnCodes); //ensures no overlap during the addAll
302             earnCodes.addAll(assignmentEarnCodes);
303         }
304 
305         return earnCodes;
306     }
307 
308     public EarnCode getEarnCode(String earnCode, Date asOfDate) {
309 		return earnCodeDao.getEarnCode(earnCode, asOfDate);
310 	}
311 
312     @Override
313     public String getEarnCodeType(String earnCode, Date asOfDate) {
314         EarnCode earnCodeObj = getEarnCode(earnCode, asOfDate);
315         return earnCodeObj.getEarnCodeType();
316     }
317 
318 	@Override
319 	public EarnCode getEarnCodeById(String earnCodeId) {
320 		return earnCodeDao.getEarnCodeById(earnCodeId);
321 	}
322 
323 	public List<EarnCode> getOvertimeEarnCodes(Date asOfDate){
324 		return earnCodeDao.getOvertimeEarnCodes(asOfDate);
325 	}
326 
327 	public List<String> getOvertimeEarnCodesStrs(Date asOfDate){
328 		List<String> ovtEarnCodeStrs = new ArrayList<String>();
329 		List<EarnCode> ovtEarnCodes = getOvertimeEarnCodes(asOfDate);
330 		if(ovtEarnCodes != null){
331 			for(EarnCode ovtEc : ovtEarnCodes){
332 				ovtEarnCodeStrs.add(ovtEc.getEarnCode());
333 			}
334 		}
335 		return ovtEarnCodeStrs;
336 	}
337 
338 	@Override
339 	public int getEarnCodeCount(String earnCode) {
340 		return earnCodeDao.getEarnCodeCount(earnCode);
341 	}
342 
343 	@Override
344 	public int getNewerEarnCodeCount(String earnCode, Date effdt) {
345 		return earnCodeDao.getNewerEarnCodeCount(earnCode, effdt);
346 	}
347 
348 	@Override
349 	public BigDecimal roundHrsWithEarnCode(BigDecimal hours, EarnCode earnCode) {
350 		String roundOption = LMConstants.ROUND_OPTION_MAP.get(earnCode.getRoundingOption());
351 		BigDecimal fractScale = new BigDecimal(earnCode.getFractionalTimeAllowed());
352 		if(roundOption == null) {
353 			throw new RuntimeException("Rounding option of Earn Code " + earnCode.getEarnCode() + " is not recognized.");
354 		}
355 		BigDecimal roundedHours = hours;
356 		if(roundOption.equals("Traditional")) {
357 			roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_HALF_EVEN);
358 		} else if(roundOption.equals("Truncate")) {
359 			roundedHours = hours.setScale(fractScale.scale(), BigDecimal.ROUND_DOWN);
360 		}
361 		return roundedHours;
362 	}
363 
364 	@Override
365 	public Map<String, String> getEarnCodesForDisplay(String principalId, boolean isLeavePlanningCalendar) {
366 		return getEarnCodesForDisplayWithEffectiveDate(principalId, TKUtils.getCurrentDate(), isLeavePlanningCalendar);
367 	}
368 
369     public List<EarnCode> getEarnCodes(String earnCode, String ovtEarnCode, String descr, String leavePlan, String accrualCategory, Date fromEffdt, Date toEffdt, String active, String showHist) {
370         return earnCodeDao.getEarnCodes(earnCode, ovtEarnCode, descr, leavePlan, accrualCategory, fromEffdt, toEffdt, active, showHist);
371     }
372 
373     @Override
374     public Map<String, String> getEarnCodesForDisplayWithEffectiveDate(String principalId, Date asOfDate, boolean isLeavePlanningCalendar) {
375         List<EarnCode> earnCodes = this.getEarnCodesForPrincipal(principalId, asOfDate, isLeavePlanningCalendar);
376 
377         Date currentDate = TKUtils.getCurrentDate();
378         boolean futureDate = asOfDate.after(currentDate);
379         List<EarnCode> copyList = new ArrayList<EarnCode>();
380         copyList.addAll(earnCodes);
381         for (EarnCode earnCode : copyList) {
382             if ( futureDate
383                     && !earnCode.getAllowScheduledLeave().equalsIgnoreCase("Y")) {
384                 earnCodes.remove(earnCode);
385             }
386         }
387         Comparator<EarnCode> earnCodeComparator = new Comparator<EarnCode>() {
388             @Override
389             public int compare(EarnCode ec1, EarnCode ec2) {
390                 return ec1.getEarnCode().compareToIgnoreCase(ec2.getEarnCode());
391             }
392         };
393         // Order by leaveCode ascending
394         Ordering<EarnCode> ordering = Ordering.from(earnCodeComparator);
395 
396         Map<String, String> earnCodesForDisplay = new LinkedHashMap<String, String>();
397         for (EarnCode earnCode : ordering.sortedCopy(earnCodes)) {
398             earnCodesForDisplay.put(earnCode.getEarnCodeKeyForDisplay(), earnCode.getEarnCodeValueForDisplay());
399         }
400         return earnCodesForDisplay;
401     }
402 
403 }