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