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.overtime.weekly.rule.service;
17  
18  import java.math.BigDecimal;
19  import java.sql.Date;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  
24  import org.joda.time.DateTime;
25  import org.junit.Assert;
26  import org.junit.Test;
27  import org.kuali.hr.test.KPMETestCase;
28  import org.kuali.hr.time.assignment.Assignment;
29  import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
30  import org.kuali.hr.time.calendar.CalendarEntries;
31  import org.kuali.hr.time.overtime.weekly.rule.WeeklyOvertimeRule;
32  import org.kuali.hr.time.service.base.TkServiceLocator;
33  import org.kuali.hr.time.test.TkTestUtils;
34  import org.kuali.hr.time.timeblock.TimeBlock;
35  import org.kuali.hr.time.timesheet.TimesheetDocument;
36  import org.kuali.hr.time.util.TKContext;
37  import org.kuali.hr.time.util.TKUtils;
38  import org.kuali.hr.time.util.TkTimeBlockAggregate;
39  import org.kuali.rice.krad.service.KRADServiceLocator;
40  
41  /**
42   * 
43   * @author djunk
44   *
45   */
46  public class WeeklyOvertimeRuleServiceTest extends KPMETestCase {
47  	
48  	private static Date DEFAULT_EFFDT = new Date((new DateTime(2010, 1, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone())).getMillis());
49  	private static Long DEFAULT_JOB_NUMBER = 30L;
50  	private static Long DEFAULT_WORK_AREA = 30L;
51  	
52  	@SuppressWarnings("serial")
53  	@Test
54  	/**
55  	 * This test should create 10 hours of OVT and leave 40 hours of REG remaining.
56  	 * It operates WITHIN a standard week.
57  	 * 
58  	 */
59  	public void testProcessSimpleStandardWeek() throws Exception {
60  		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
61  		DateTime start = new DateTime(2010, 1, 4, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
62  		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 5, BigDecimal.TEN, "REG", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
63  		CalendarEntries payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", DEFAULT_EFFDT);
64  
65  		// Check our initial data.
66  		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
67  		TkTestUtils.verifyAggregateHourSums(new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("REG", new BigDecimal(50));}},aggregate,1);
68  		
69  		// Create the rule.
70  		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT);		
71  		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(DEFAULT_EFFDT);
72  		timesheetDocument.setTimeBlocks(timeBlocks);
73  		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);
74  		
75  		// Check the rule for OVT applied data.
76  		TkTestUtils.verifyAggregateHourSums(new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.TEN);put("REG", new BigDecimal(40));}},aggregate,1);
77  	}
78  	
79  	@SuppressWarnings("serial")
80  	@Test
81  	/**
82  	 * OVT Hit on Current Month
83  	 * 
84  	 * This test should create 10 hours of OVT on the first FLSA week of the
85  	 * current pay period.  The first FLSA week has 20 REG hours, the previous
86  	 * pay period was a partial week and contained 30 REG hours.
87  	 * 
88  	 * march 29-31; april 1-4
89  	 *  
90  	 *  |--------+--------+--------+----+-------+-------|
91  	 *  |   29th |   30th |   31st | xx |   1st |   2nd |
92  	 *  |--------+--------+--------+----+-------+-------|
93  	 *  |     10 |     10 |     10 | xx |    10 |    10 |
94  	 *  |--------+--------+--------+----+-------+-------|
95  	 * 
96  	 *  4/1/2010 starts on a Thursday
97  	 */
98  	public void testProcessPreviousMonthFlsaBoundary() throws Exception {
99  		// March end time blocks: 3/29-3-31 [m, w]
100 		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
101 		DateTime start = new DateTime(2010, 3, 29, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
102 		Date beginPeriodDate = new Date(new DateTime(2010, 3, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
103 		Date endPeriodDate = new Date(new DateTime(2010, 4, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
104         Date calEntryEndDate = new Date(new DateTime(2010, 4, 1, 0, 0, 0).getMillis());
105 		CalendarEntries endOfMarch = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByIdAndPeriodEndDate("2", endPeriodDate);
106 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfMarch);
107 		Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment("admin", new AssignmentDescriptionKey("30_30_30"), beginPeriodDate);
108 		timeBlocks = TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 3, BigDecimal.TEN, BigDecimal.ZERO);
109 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(new ArrayList<TimeBlock>(), timeBlocks, TKContext.getPrincipalId());
110 		tdoc.setTimeBlocks(timeBlocks);
111 		
112 		// Verify previous calendar times
113 		CalendarEntries payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
114 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
115 		TkTestUtils.verifyAggregateHourSums("Prior month", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("RGN", new BigDecimal(30));}},aggregate,2);
116 				
117 		
118 		// April time blocks & document
119 		start = new DateTime(2010, 4, 1, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
120 		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 2, BigDecimal.TEN, "REG", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
121 		payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
122 		aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
123 		TkTestUtils.verifyAggregateHourSums("Pre-Rules verification", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("REG", new BigDecimal(20));}},aggregate,0);
124 		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(new Date(start.getMillis()));
125 		timesheetDocument.setTimeBlocks(timeBlocks);
126 		
127 		// Create Rule
128 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT);		
129 
130 		// Apply
131 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);		
132 		
133 		// Verify
134 		TkTestUtils.verifyAggregateHourSums("Overtime processed", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.TEN);put("REG", new BigDecimal(10));}},aggregate,0);
135 	}
136 	
137 	
138 	@SuppressWarnings("serial")
139 	@Test
140 	/**
141 	 * OVT hit on previous month.
142 	 * 
143 	 * |------+------+------+------+----+-----+-----|
144 	 * | 27th | 28th | 29th | 30th | xx | 1st | 2nd |
145 	 * |------+------+------+------+----+-----+-----|
146 	 * |   11 |   11 |   11 |   11 | xx |  11 |  11 |
147 	 * |------+------+------+------+----+-----+-----|
148 	 */
149 	public void testProcessPreviousMonthFlsaOT() throws Exception {
150 		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
151 		DateTime start = new DateTime(2010, 6, 27, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
152 		Date beginPeriodDate = new Date(new DateTime(2010, 6, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
153 		Date endPeriodDate = new Date(new DateTime(2010, 7, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
154         CalendarEntries endOfJune = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByIdAndPeriodEndDate("2", endPeriodDate);
155 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfJune);
156 		Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment("admin", new AssignmentDescriptionKey("30_30_30"), beginPeriodDate);
157 		timeBlocks = TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 4, new BigDecimal(11), BigDecimal.ZERO);
158 		
159 		tdoc.setTimeBlocks(timeBlocks);
160 		
161 		// Create Rule
162 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT);
163 
164 
165 		// Verify previous calendar times
166 		CalendarEntries payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
167 		
168 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
169 		// Create and Process Previous month to have totals set up correctly
170 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(tdoc, aggregate);
171 		TkTestUtils.verifyAggregateHourSums("Prior month", new HashMap<String,BigDecimal>() {{put("OVT", new BigDecimal(4));put("RGN", new BigDecimal(40));}},aggregate,2);
172 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(new ArrayList<TimeBlock>(), aggregate.getFlattenedTimeBlockList(), TKContext.getPrincipalId());
173 		
174 		// April time blocks & document
175 		start = new DateTime(2010, 7, 1, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
176 		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 2, new BigDecimal(11), "RGN", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
177 		payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
178 		aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
179 		TkTestUtils.verifyAggregateHourSums("Pre-Rules verification", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("RGN", new BigDecimal(22));}},aggregate,0);
180 		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(new Date(start.getMillis()));
181 		timesheetDocument.setTimeBlocks(timeBlocks);		
182 
183 		// Apply
184 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);		
185 		
186 		// Verify
187 		TkTestUtils.verifyAggregateHourSums("Overtime processed", new HashMap<String,BigDecimal>() {{put("OVT", new BigDecimal(22));put("RGN", BigDecimal.ZERO);}},aggregate,0);
188 	}
189 	
190 	@SuppressWarnings("serial")
191 	@Test
192 	/**
193 	 * Three step OVT Test
194 	 * 
195 	 * |------+------+------+------+----+-----+-----|
196 	 * | 27th | 28th | 29th | 30th | xx | 1st | 2nd |
197 	 * |------+------+------+------+----+-----+-----|
198 	 * |  ABC | XYZ  | ABC  |  XYZ | xx | ABC | REG | 
199 	 * |------+------+------+------+----+-----+-----|
200 	 * |   11 |   11 |   11 |   11 | xx |  11 |  11 |
201 	 * |------+------+------+------+----+-----+-----|
202 	 * 
203 	 * Contrived example using 3 steps that convert strangely coded hours to 
204 	 * REG via a multi-step process.  Eventually REG is converted to OVT using
205 	 * normal rules.
206 	 * 
207 	 * Step 1: g:SD3:[XYZ] to [ABC]
208 	 * Step 2: g:SD2:[ABC] to [REG]
209 	 * Step 3: g:REG:[REG] to [OVT]
210 	 * 
211 	 * XYZ -> ABC -> REG -> OVT
212 	 * 
213 	 * 27-30th:
214 	 * ABC: 1    
215 	 * XYZ: 1    
216 	 * REG: 40  
217 	 * OVT: 2    
218 	 * 
219 	 * 1st-2nd:
220 	 * ABC: 0    
221 	 * REG: 0    
222 	 * OVT: 22 
223 	 */
224 	public void testProcessThreeStepOvtRule() throws Exception {
225 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 3, new BigDecimal(40), DEFAULT_EFFDT);
226 		this.setupWeeklyOvertimeRule("SD2", "RGN", "SD2", 2, new BigDecimal(1), DEFAULT_EFFDT);
227 		this.setupWeeklyOvertimeRule("SD3", "ABC", "SD3", 1, new BigDecimal(1), DEFAULT_EFFDT);
228 
229 		
230 		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
231 		DateTime start = new DateTime(2010, 6, 27, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
232 		Date beginPeriodDate = new Date(new DateTime(2010, 6, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
233 		Date endPeriodDate = new Date(new DateTime(2010, 7, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
234         CalendarEntries endOfJune = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByIdAndPeriodEndDate("2", endPeriodDate);
235 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfJune);
236 		Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment("admin", new AssignmentDescriptionKey("30_30_30"), beginPeriodDate);		
237 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "ABC", start, 1, new BigDecimal(11), BigDecimal.ZERO));
238 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "XYZ", start.plusDays(1), 1, new BigDecimal(11), BigDecimal.ZERO));
239 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "ABC", start.plusDays(2), 1, new BigDecimal(11), BigDecimal.ZERO));
240 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "XYZ", start.plusDays(3), 1, new BigDecimal(11), BigDecimal.ZERO));		
241 		tdoc.setTimeBlocks(timeBlocks);
242 
243 		// Verify previous calendar times
244 		CalendarEntries payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
245 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
246 		// Create and Process Previous month to have totals set up correctly
247 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(tdoc, aggregate);
248 		TkTestUtils.verifyAggregateHourSums("Prior month", new HashMap<String,BigDecimal>() {{put("OVT", new BigDecimal(2));put("RGN", new BigDecimal(40));put("ABC", new BigDecimal(1));put("XYZ", new BigDecimal(1));}},aggregate,2);
249 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(new ArrayList<TimeBlock>(), aggregate.getFlattenedTimeBlockList(), TKContext.getPrincipalId());
250 		
251 		// April time blocks & document
252 		start = new DateTime(2010, 7, 1, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
253 		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 2, new BigDecimal(11), "REG", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
254 		payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
255 		aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
256 		TkTestUtils.verifyAggregateHourSums("Pre-Rules verification", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("REG", new BigDecimal(22));}},aggregate,0);
257 		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(new Date(start.getMillis()));
258 		timesheetDocument.setTimeBlocks(timeBlocks);		
259 
260 		// Apply
261 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);		
262 		
263 		// Verify
264 		TkTestUtils.verifyAggregateHourSums("Overtime processed", new HashMap<String,BigDecimal>() {{put("ABC", BigDecimal.ZERO);put("OVT", new BigDecimal(22));put("REG", BigDecimal.ZERO);}},aggregate,0);		
265 	}
266 	
267 	/**
268 	 * Helper method that creates a weekly overtime rule.
269 	 */
270 	private WeeklyOvertimeRule setupWeeklyOvertimeRule(String fromEarnGroup, String toEarnCode, String maxHoursEarnGroup, int step, BigDecimal maxHours, Date effectiveDate){
271 		WeeklyOvertimeRule weeklyOvertimeRule = new WeeklyOvertimeRule();
272 		weeklyOvertimeRule.setActive(true);
273 		weeklyOvertimeRule.setConvertFromEarnGroup(fromEarnGroup);
274 		weeklyOvertimeRule.setConvertToEarnCode(toEarnCode);
275 		weeklyOvertimeRule.setMaxHoursEarnGroup(maxHoursEarnGroup);
276 		weeklyOvertimeRule.setStep(new BigDecimal(step));
277 		weeklyOvertimeRule.setMaxHours(maxHours);
278 		weeklyOvertimeRule.setEffectiveDate(effectiveDate);
279 		
280 		weeklyOvertimeRule.setTkWeeklyOvertimeRuleGroupId(1L);
281 		
282 		TkServiceLocator.getWeeklyOvertimeRuleService().saveOrUpdate(weeklyOvertimeRule);
283 		return weeklyOvertimeRule;
284 	}
285 
286 	@Override
287 	public void tearDown() throws Exception {
288 		KRADServiceLocator.getBusinessObjectService().deleteMatching(WeeklyOvertimeRule.class, new HashMap());
289 		super.tearDown();
290 	}
291 		
292 }