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