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 //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 }