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, boolean includeRegularEarnCode) {
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 || includeRegularEarnCode ) {
092 earnCodes.add(regularEarnCode);
093 }
094 }
095
096 List<String> listAccrualCategories = new LinkedList<String>();
097 String accrualCategory;
098
099 // 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 }