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    }