View Javadoc
1   /**
2    * Copyright 2004-2014 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.shiftdiff.rule;
17  
18  import com.google.common.collect.ImmutableMap;
19  import org.joda.time.DateTime;
20  import org.joda.time.DateTimeZone;
21  import org.joda.time.LocalTime;
22  import org.junit.Assert;
23  import org.junit.Ignore;
24  import org.junit.Test;
25  import org.kuali.hr.KPMEWebTestCase;
26  import org.kuali.kpme.core.FunctionalTest;
27  import org.kuali.kpme.core.api.assignment.Assignment;
28  import org.kuali.kpme.core.api.assignment.AssignmentDescriptionKey;
29  import org.kuali.kpme.core.api.calendar.entry.CalendarEntry;
30  import org.kuali.kpme.core.calendar.CalendarBo;
31  import org.kuali.kpme.core.service.HrServiceLocator;
32  import org.kuali.kpme.core.util.TKUtils;
33  import org.kuali.kpme.tklm.api.time.timeblock.TimeBlock;
34  import org.kuali.kpme.tklm.time.rules.shiftdifferential.ShiftDifferentialRule;
35  import org.kuali.kpme.tklm.time.rules.shiftdifferential.service.ShiftDifferentialRuleService;
36  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
37  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
38  import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
39  import org.kuali.kpme.tklm.utils.TkTestUtils;
40  
41  import java.math.BigDecimal;
42  import java.sql.Time;
43  import java.util.ArrayList;
44  import java.util.HashMap;
45  import java.util.List;
46  
47  /**
48   *
49   * @author djunk
50   *
51   */
52  @FunctionalTest
53  public class ShiftDifferentialRuleServiceProcessTest extends KPMEWebTestCase {
54  
55  
56  	public static final String USER_PRINCIPAL_ID = "admin";
57  	private DateTime JAN_AS_OF_DATE = new DateTime(2010, 1, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
58  
59  
60  	/**
61  	 * Test with boundary carryover and overlapping rules.
62  	 *
63  	 * Rule 1:
64  	 *
65  	 * Runs on Tu, Wed, Th on the interval: [22:00, 4:00)
66  	 * Max Gap: 15 minutes
67  	 * Min Hours: 3
68  	 *
69  	 * Rule 2:
70  	 *
71  	 * Runs on Tu, Th on the interval: [23:00, 2:00)
72  	 * Max Gap: 2 hours
73  	 * Min Hours: 3
74  	 *
75  	 * Rule 3:
76  	 *
77  	 * Runs on W, Th on the interval: [5:00, 12:00)
78  	 * Max Gap: 15 minutes
79  	 * Min Hours: 7 hours
80  	 *
81  	 * Rule 4:
82  	 *
83  	 * Runs on W on the interval: [5:00, 12:00)
84  	 * Max Gap: 15 minutes
85  	 * Min Hours: 5
86  	 *
87  	 *
88  	 * |--------------+----+------------+------------|
89  	 * | Tu : 8/31/10 | XX | W : 9/1/10 | Th: 9/2/10 |
90  	 * |--------------+----+------------+------------|
91  	 * | 9:45p - 11:45| XX | Mid - 5a   | 5p - 11p   |
92  	 * |              | XX | 6a - Noon  |            |
93  	 * |--------------+----+------------+------------|
94       *
95       *
96       * Aug 31: 2h  : 21:45 - 23:45 (Tue) **
97       *                           [1: 5h 45m]  // [2: 2h 45m] - Not qualifying, min hours must be 3.
98       * Sep  1: 5h  : 00:00 - 05:00 (Wed) **
99       * Sep  1: 6h  : 06:00 - 12:00 (Wed) [4: 6h]
100      *
101      * Sep  1: 2h  : 22:00 - 24:00 (Wed)
102      * Sep  2: 1h  : 00:00 - 01:00 (Thu) [1: 3h]
103      *
104      * Sep  2: 6h  : 17:00 - 22:00 (Thu)
105      *
106      * 1: [22:00,  4:00) (Tue/Wed/Thu) minimum: 3h gap: 15m
107      * 2: [23:00,  2:00) (Tue/Thu)     minimum: 3h gap: 2h
108      * 3: [05:00, 12:00) (Wed/Thu)     minimum: 7h gap: 15m
109      * 4: [05:00, 12:00) (Wed)         minimum: 5h gap: 15m
110      *
111 	 */
112 	@SuppressWarnings("serial")
113 	@Test
114 	public void testProcessTimesheetBoundaryCarryoverOverlapCase() throws Exception {
115         DateTimeZone tz = HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
116 		// Create the Rule    Sun,   Mon,   Tue,  Wed,   Thu,  Fri,  Sat
117 		boolean[] dayArray = {false, false, true, true, true, true, true};
118 		// Matches HR Job ID #1 (job # 30)
119 		Long jobNumber = 30L;
120 		Long workArea = 0L;
121 		this.createShiftDifferentialRule(
122 				"BWS-CAL", "REG", "PRM", "IN", "SD1", "SD1",// // changed from "SD1" to "IN" for changes of adding groupKeyCode to Job
123 				(new LocalTime(22, 0)),
124 				(new LocalTime(4, 0)),
125 				new BigDecimal(3), // minHours
126 				new BigDecimal("15.00"), // maxGap
127 				dayArray);
128 
129 
130 		// Timeblocks
131 
132 		// August
133 		DateTime beginPeriodDate = new DateTime(2010, 8, 15, 0, 0, 0, 0, tz);
134 		CalendarEntry endOfAugust =  HrServiceLocator.getCalendarEntryService().getCalendarEntryByIdAndPeriodEndDate("2", new DateTime(2010, 9, 1, 0, 0, 0, 0));
135 		DateTime start = new DateTime(2010, 8, 31, 21, 45, 0, 0, tz);
136 		List<TimeBlock> blocks = new ArrayList<TimeBlock>();
137 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfAugust);
138         Assignment assignment = HrServiceLocator.getAssignmentService().getAssignment("admin", AssignmentDescriptionKey.get("IU-IN_30_30_30"), beginPeriodDate.toLocalDate());
139 		blocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 1, new BigDecimal(2), BigDecimal.ZERO, "admin"));
140 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, endOfAugust, HrServiceLocator.getCalendarService().getCalendar(endOfAugust.getHrCalendarId()), true);
141 		tdoc.setTimeBlocks(blocks);
142 		TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
143 		TkTestUtils.verifyAggregateHourSumsFlatList("August Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("RGN", new BigDecimal(2));}},aggregate);
144 		TkServiceLocator.getTimeBlockService().saveOrUpdateTimeBlocks(new ArrayList<TimeBlock>(), aggregate.getFlattenedTimeBlockList(), "admin");
145 
146 
147 		// September
148 
149 		start = new DateTime(2010, 9, 1, 0, 0, 0, 0, tz);
150 		CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start.toLocalDate().toDateTimeAtStartOfDay());
151 		tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", payCalendarEntry);
152 		blocks = new ArrayList<TimeBlock>();
153 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start, 1, new BigDecimal("5"), "RGN", jobNumber, workArea));
154 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start.plusHours(6), 1, new BigDecimal("6"), "RGN", jobNumber, workArea));
155         blocks.addAll(TkTestUtils.createUniformTimeBlocks(start.plusHours(22), 1, new BigDecimal("2"), "RGN", jobNumber, workArea));
156         blocks.addAll(TkTestUtils.createUniformTimeBlocks(start.plusDays(1), 1, new BigDecimal("1"), "RGN", jobNumber, workArea));
157 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start.plusDays(1).plusHours(17), 1, new BigDecimal("6"), "RGN", jobNumber, workArea));
158 		blocks = setDocumentIdOnBlocks(blocks, tdoc.getDocumentId());
159         
160 		aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry, HrServiceLocator.getCalendarService().getCalendar(payCalendarEntry.getHrCalendarId()), true);
161 		
162 		TkTestUtils.verifyAggregateHourSumsFlatList("September Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("RGN", new BigDecimal(20));}},aggregate);
163 
164 		// Verify carry over and applied PRM bucket
165 		TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
166 		TkTestUtils.verifyAggregateHourSumsFlatList("September Post-Check", new HashMap<String,BigDecimal>() {{put("PRM", new BigDecimal("8.75"));put("RGN", new BigDecimal(22));}},aggregate);
167 	}
168 
169     private List<TimeBlock> setDocumentIdOnBlocks(List<TimeBlock> blocks, String id) {
170         List<TimeBlock> updatedTimeBlocks = new ArrayList<TimeBlock>();
171         for (TimeBlock b : blocks) {
172             TimeBlock.Builder builder = TimeBlock.Builder.create(b);
173             builder.setDocumentId(id);
174             updatedTimeBlocks.add(builder.build());
175         }
176         return updatedTimeBlocks;
177     }
178 
179 
180 	/**
181 	 * Test where previous time sheet contains hours that should be added to
182 	 * the next pay periods first day shift.
183 	 *
184 	 * Runs on Tu, Th on the interval: [22:00, 4:00)
185 	 * Max Gap: 15 minutes
186 	 * Min Hours: 3
187 	 *
188 	 * |--------------+----+------------+-------------|
189 	 * | Tu : 8/31/10 | XX | W : 9/1/10 | Th : 9/2/10 |
190 	 * |--------------+----+------------+-------------|
191 	 * | 10pm - Mid   | XX | Mid - 5am  | 5pm - 11pm  |
192 	 * |--------------+----+------------+-------------|
193 	 *
194 	 * @throws Exception
195 	 */
196 	@SuppressWarnings("serial")
197 	@Test
198 	public void testProcessShiftTimesheeetBoundaryCarryoverCase() throws Exception {
199 		// Create the Rule    Sun,   Mon,   Tue,  Wed,   Thu,  Fri,  Sat
200 		boolean[] dayArray = {false, false, true, false, true, true, true};
201 		// Matches HR Job ID #1 (job # 30)
202 		Long jobNumber = 30L;
203 		Long workArea = 0L;
204 
205         DateTimeZone tz = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
206 		this.createShiftDifferentialRule(
207 				"BWS-CAL",
208 				"REG",
209 				"PRM",
210 				"IN",  // changed from "SD1" to "IN" for changes of adding groupKeyCode to Job
211 				"SD1",
212 				"SD1",
213 				(new LocalTime(22, 0)),
214 				(new LocalTime(5, 0)),
215 				new BigDecimal(3), // minHours
216 				new BigDecimal("0.25"), // maxGap
217 				dayArray);
218 
219 		// August
220 		DateTime endPeriodDate = new DateTime(2010, 9, 1, 0, 0, 0, 0);
221         CalendarEntry endOfAugust =  HrServiceLocator.getCalendarEntryService().getCalendarEntryByIdAndPeriodEndDate("2", endPeriodDate);
222 		DateTime start = new DateTime(2010, 8, 31, 22, 0, 0, 0, tz);
223 		List<TimeBlock> blocks = new ArrayList<TimeBlock>();
224 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfAugust);
225         Assignment assignment = HrServiceLocator.getAssignmentService().getAssignment("admin", AssignmentDescriptionKey.get("IU-IN_30_30_30"), endOfAugust.getBeginPeriodFullDateTime().toLocalDate());
226 		blocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 1, new BigDecimal(2), BigDecimal.ZERO, "admin"));
227 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, endOfAugust, HrServiceLocator.getCalendarService().getCalendar(endOfAugust.getHrCalendarId()), true);
228 
229 
230 
231 		tdoc.setTimeBlocks(blocks);
232 		TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
233 		TkTestUtils.verifyAggregateHourSumsFlatList("August Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("RGN", new BigDecimal(2));}},aggregate);
234 		TkServiceLocator.getTimeBlockService().saveOrUpdateTimeBlocks(new ArrayList<TimeBlock>(), aggregate.getFlattenedTimeBlockList(), "admin");
235 
236 
237 		// September
238 		start = new DateTime(2010, 9, 1, 0, 0, 0, 0, tz);
239 		CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start.toLocalDate().toDateTimeAtStartOfDay());
240 		tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", payCalendarEntry);
241 		blocks = new ArrayList<TimeBlock>();
242 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start, 1, new BigDecimal("5"), "RGN", jobNumber, workArea));
243 		aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry, HrServiceLocator.getCalendarService().getCalendar(payCalendarEntry.getHrCalendarId()), true);
244 		TkTestUtils.verifyAggregateHourSumsFlatList("September Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("RGN", new BigDecimal(5));}},aggregate);
245 
246 		// Verify carry over and applied PRM bucket
247 		TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
248 		TkTestUtils.verifyAggregateHourSumsFlatList("September Post-Check", new HashMap<String,BigDecimal>() {{put("PRM", new BigDecimal(7));put("RGN", new BigDecimal(7));}},aggregate);
249 	}
250 
251 	@SuppressWarnings("serial")
252 	@Test
253 	/**
254 	 * Runs on every day on the interval: [16:00, 24:00)
255 	 * Max Gap: 15 minutes
256 	 * Min Hours: 4
257 	 *
258 	 * Added some extra time blocks that are not in the shift interval, but
259 	 * close to the time blocks that are.
260 	 *
261 	 * @throws Exception
262 	 */
263 	public void testProcessShiftSimpleNoisyCase() throws Exception {
264 		// Create the Rule
265 		boolean[] dayArray = {true, true, true, true, true, true, true};
266 		// Matches HR Job ID #1 (job # 30)
267 		Long jobNumber = 30L;
268 		Long workArea = 0L;
269         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
270 		this.createShiftDifferentialRule(
271 				"BWS-CAL",
272 				"REG",
273 				"PRM",
274 				"IN", // changed from "SD1" to "IN" for changes of adding groupKeyCode to Job
275 				"SD1",
276 				"SD1",
277 				(new LocalTime(16, 0)),
278 				(new LocalTime(0, 0)),
279 				new BigDecimal(4), // minHours
280 				new BigDecimal("15"), // maxGap
281 				dayArray);
282 
283 		// Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
284 		DateTime start = new DateTime(2010, 3, 29, 14, 0, 0, 0, zone);
285 		List<TimeBlock> blocks = new ArrayList<TimeBlock>();
286 		CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
287 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start, 2, new BigDecimal("4"), "RGN", jobNumber, workArea));
288 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start.plusHours(4).plusMinutes(15), 2, new BigDecimal("2"), "RGN", jobNumber, workArea));
289 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(new DateTime(2010, 3, 29, 12, 58, 0, 0, zone), 2, new BigDecimal(1), "RGN", jobNumber, workArea));
290 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
291 
292 		// Verify pre-Rule Run
293 		TkTestUtils.verifyAggregateHourSums("Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("RGN", new BigDecimal(14));}},aggregate,2);
294 
295 		// Run Rule
296 		TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
297 		tdoc.setTimeBlocks(blocks);
298 		TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
299 
300 		// Verify post-Rule Run
301 		TkTestUtils.verifyAggregateHourSums("Post Rules Check", new HashMap<String,BigDecimal>() {{put("PRM", new BigDecimal(8));put("RGN", new BigDecimal(14));}},aggregate,2);
302 	}
303 
304 	@SuppressWarnings("serial")
305 	@Test
306 	/**
307 	 * Runs on every day on the interval: [16:00, 24:00)
308 	 * Max Gap: 15 minutes
309 	 * Min Hours: 4
310 	 *
311 	 * @throws Exception
312 	 */
313 	public void testProcessShiftSimpleCase() throws Exception {
314 		// Create the Rule
315 		boolean[] dayArray = {true, true, true, true, true, true, true};
316 		// Matches HR Job ID #1 (job # 30)
317 		Long jobNumber = 30L;
318 		Long workArea = 0L;
319         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
320 		this.createShiftDifferentialRule(
321 				"BWS-CAL",
322 				"REG",
323 				"PRM",
324 				"IN", // changed from "SD1" to "IN" for changes of adding groupKeyCode to Job
325 				"SD1",
326 				"SD1",
327 				(new LocalTime(16, 0)), //4pm
328 				(new LocalTime(0, 0)),  //midnight
329 				new BigDecimal(4), // minHours
330 				new BigDecimal("15.00"), // maxGap minutes
331 				dayArray);
332 
333 		// Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
334 		DateTime start = new DateTime(2010, 3, 29, 14, 0, 0, 0, zone);
335 		List<TimeBlock> blocks = new ArrayList<TimeBlock>();
336 		CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
337 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start, 2, new BigDecimal("4"), "REG", jobNumber, workArea));
338 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start.plusHours(4).plusMinutes(15), 2, new BigDecimal("2"), "REG", jobNumber, workArea));
339 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
340 
341 		// Verify pre-Rule Run
342 		TkTestUtils.verifyAggregateHourSums("Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("REG", new BigDecimal(12));}},aggregate,2);
343 
344 		// Run Rule
345 		TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
346 		tdoc.setTimeBlocks(blocks);
347 		TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
348 
349 		// Verify post-Rule Run
350 		TkTestUtils.verifyAggregateHourSums("Post Rules Check", new HashMap<String,BigDecimal>() {{put("PRM", new BigDecimal(8));put("REG", new BigDecimal(12));}},aggregate,2);
351 	}
352 
353 	/**
354 	 * Stores the Shift Differential Rule in the database for testing.
355 	 *
356 	 * dayBooleans[] is a 7 element array of booleans, [0, 6] is [sun, sat]
357 	 */
358 	private void createShiftDifferentialRule(String pyCalendarGroup, String fromEarnGroup, String premiumEarnCode, String location, String payGrade, String hrSalGroup, LocalTime startTime, LocalTime endTime, BigDecimal minHours, BigDecimal maxGap, boolean dayBooleans[]) {
359 		Assert.assertTrue("Wrong number of day booleans", dayBooleans.length == 7);
360 
361 		ShiftDifferentialRuleService service = TkServiceLocator.getShiftDifferentialRuleService();
362 		ShiftDifferentialRule sdr = new ShiftDifferentialRule();
363 
364 		sdr.setBeginTime(new Time(startTime.toDateTimeToday().getMillis()));
365 		sdr.setEndTime(new Time(endTime.toDateTimeToday().getMillis()));
366 		sdr.setMinHours(minHours);
367 		sdr.setMaxGap(maxGap);
368 		sdr.setActive(true);
369 		sdr.setUserPrincipalId(USER_PRINCIPAL_ID);
370 		sdr.setEffectiveLocalDate(JAN_AS_OF_DATE.toLocalDate());
371 		sdr.setLocation(location);
372 		sdr.setPayGrade(payGrade);
373 		sdr.setHrSalGroup(hrSalGroup);
374 		sdr.setFromEarnGroup(fromEarnGroup);
375 		sdr.setPyCalendarGroup(pyCalendarGroup);
376 		sdr.setEarnCode(premiumEarnCode);
377         sdr.setRuleType("default");
378 
379 		for (int i=0; i<dayBooleans.length; i++) {
380 			switch(i) {
381 			case 0:
382 				sdr.setSunday(dayBooleans[i]);
383 				break;
384 			case 1:
385 				sdr.setMonday(dayBooleans[i]);
386 				break;
387 			case 2:
388 				sdr.setTuesday(dayBooleans[i]);
389 				break;
390 			case 3:
391 				sdr.setWednesday(dayBooleans[i]);
392 				break;
393 			case 4:
394 				sdr.setThursday(dayBooleans[i]);
395 				break;
396 			case 5:
397 				sdr.setFriday(dayBooleans[i]);
398 				break;
399 			case 6:
400 				sdr.setSaturday(dayBooleans[i]);
401 				break;
402 			}
403 		}
404 
405 		service.saveOrUpdate(sdr);
406 
407 		ShiftDifferentialRule sdrBack = service.getShiftDifferentialRule(sdr.getTkShiftDiffRuleId());
408 
409         DateTimeZone tz = HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
410         LocalTime orig_start = new LocalTime(sdr.getBeginTime());
411 		LocalTime orig_end = new LocalTime(sdr.getEndTime());
412 
413 		LocalTime stored_start = new LocalTime(sdrBack.getBeginTime());
414 		LocalTime stored_end = new LocalTime(sdrBack.getEndTime());
415 
416 		Assert.assertTrue("Start times not equal.", orig_start.equals(stored_start));
417 		Assert.assertTrue("End times not equal.", orig_end.equals(stored_end));
418 	}
419 
420 
421     @Ignore
422     @Test
423     /**
424      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
425      *
426      * Create a timeblock on two days, one day has normal REG shift eligible
427      * hours, one day has HOL time.
428      *
429      * Modified version of the simple case, SDR from 12:00 to 17:00, every day,
430      * must have at least 4 hours with a maximum 15 minute gap.
431      *
432      */
433     public void simpleCaseWithWorkSchedule() throws Exception {
434 		// Create the Rule
435 		boolean[] dayArray = {true, true, true, true, true, true, true};
436 		// Matches HR Job ID #1 (job # 30)
437 		Long jobNumber = 30L;
438 		Long workArea = 0L;
439 		this.createShiftDifferentialRule(
440 				"BWS-CAL",
441 				"REG",
442 				"PRM",
443 				"IN",  // changed from "SD1" to "IN" for changes of adding groupKeyCode to Job
444 				"SD1",
445 				"SD1",
446 				(new LocalTime(12, 0)),
447 				(new LocalTime(17, 0)),
448 				new BigDecimal(4), // minHours
449 				new BigDecimal("15.00"), // maxGap
450 				dayArray);
451 
452 		// Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
453 		DateTime start = new DateTime(2010, 3, 29, 12, 0, 0, 0, TKUtils.getSystemDateTimeZone());
454         DateTime holtime = new DateTime(2010, 3, 30, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
455 		List<TimeBlock> blocks = new ArrayList<TimeBlock>();
456 		CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
457 		blocks.addAll(TkTestUtils.createUniformTimeBlocks(start,   1, new BigDecimal("4"), "REG", jobNumber, workArea));
458         blocks.addAll(TkTestUtils.createUniformTimeBlocks(holtime, 1, new BigDecimal("4"), "HOL", jobNumber, workArea));
459 
460 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
461 
462 		// Verify pre-Rule Run
463 		TkTestUtils.verifyAggregateHourSums("Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("REG", new BigDecimal(4));put("HOL", new BigDecimal(4));}},aggregate,2);
464 
465 		// Run Rule
466 		TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
467 		tdoc.setTimeBlocks(blocks);
468 		TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
469 
470 		// Verify post-Rule Run
471 		TkTestUtils.verifyAggregateHourSums("Post Rules Check", new HashMap<String,BigDecimal>() {{put("PRM", new BigDecimal(8));put("REG", new BigDecimal(4));}},aggregate,2);
472 
473     }
474 
475 
476 
477     /**
478      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
479      *
480      * Create a single 24 hour timeblock that spans two different shift, both exceeding the min hours
481      */
482     @Test
483     public void overlapMultipleShiftsWithSameTimeBlock() {
484         // Create the Rule
485         boolean[] dayArray = {true, true, true, true, true, true, true};
486         // Matches HR Job ID #1 (job # 30)
487         Long jobNumber = 30L;
488         Long workArea = 0L;
489         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
490 
491         //3pm to 8am, 6 hour minimum, 90 minute max gap
492         this.createShiftDifferentialRule(
493                 "BWS-CAL",
494                 "REG",
495                 "PRM",
496                 "IN",
497                 "SD1",
498                 "SD1",
499                 (new LocalTime(15, 0)),
500                 (new LocalTime(8, 0)),
501                 new BigDecimal(6), // minHours
502                 new BigDecimal("90.00"), // maxGap
503                 dayArray);
504 
505         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
506         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
507         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
508 
509         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
510         DateTime tbStart = new DateTime(2010, 3, 29, 0, 0, 0, 0, zone);
511 
512         //24 time block (midnight to midnight)
513         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart, 1, new BigDecimal("24"), "REG", jobNumber, workArea));
514 
515         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
516 
517         // Verify pre-Rule Run
518         TkTestUtils.verifyAggregateHourSums("Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("REG", new BigDecimal(24));}},aggregate,2);
519 
520         // Run Rule
521         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
522         tdoc.setTimeBlocks(blocks);
523         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
524 
525         // Verify post-Rule Run
526         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new HashMap<String,BigDecimal>() {{put("PRM", new BigDecimal(17));put("REG", new BigDecimal(24));}},aggregate,2);
527 
528     }
529 
530     /**
531      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
532      *
533      * Create a two 24 hour timeblocks that span three different shifts, all exceeding the min hours
534      */
535     @Test
536     public void overlapMultipleShiftsWithMultipleTimeBlocks() {
537         // Create the Rule
538         boolean[] dayArray = {true, true, true, true, true, true, true};
539         // Matches HR Job ID #1 (job # 30)
540         Long jobNumber = 30L;
541         Long workArea = 0L;
542         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
543 
544         //3pm to 8am, 6 hour minimum, 90 minute max gap
545         this.createShiftDifferentialRule(
546                 "BWS-CAL",
547                 "REG",
548                 "PRM",
549                 "IN",
550                 "SD1",
551                 "SD1",
552                 (new LocalTime(15, 0)),
553                 (new LocalTime(8, 0)),
554                 new BigDecimal(6), // minHours
555                 new BigDecimal("90.00"), // maxGap
556                 dayArray);
557 
558         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
559         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
560         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
561 
562         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
563         DateTime tbStart = new DateTime(2010, 3, 29, 0, 0, 0, 0, zone);
564 
565         //24 time block (midnight to midnight)
566         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart, 1, new BigDecimal("24"), "REG", jobNumber, workArea));
567 
568         //24 time block (midnight to midnight)
569         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart.plusDays(1), 1, new BigDecimal("24"), "REG", jobNumber, workArea));
570 
571         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
572 
573         // Verify pre-Rule Run
574         TkTestUtils.verifyAggregateHourSums("Pre-Check", new HashMap<String,BigDecimal>() {{put("PRM", BigDecimal.ZERO);put("REG", new BigDecimal(48));}},aggregate,2);
575 
576         // Run Rule
577         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
578         tdoc.setTimeBlocks(blocks);
579         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
580 
581         // Verify post-Rule Run
582         //overlaps from 12a-8a (8 hours), 3p-8a (17 hours), and 3p - 12a (9 hours) == 34 hours
583         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new HashMap<String,BigDecimal>() {{put("PRM", new BigDecimal(34));put("REG", new BigDecimal(48));}},aggregate,2);
584 
585     }
586 
587     /**
588      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
589      *
590      * Create a single 24 hour timeblock that spans two different shift, both exceeding the min hours
591      */
592     @Test
593     public void overlapMultipleShiftsWithSameTimeBlockExceedingMinOnOneShift() {
594         // Create the Rule
595         boolean[] dayArray = {true, true, true, true, true, true, true};
596         // Matches HR Job ID #1 (job # 30)
597         Long jobNumber = 30L;
598         Long workArea = 0L;
599         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
600 
601         //3pm to 8am, 6 hour minimum, 90 minute max gap
602         this.createShiftDifferentialRule(
603                 "BWS-CAL",
604                 "REG",
605                 "PRM",
606                 "IN",
607                 "SD1",
608                 "SD1",
609                 (new LocalTime(15, 0)),
610                 (new LocalTime(8, 0)),
611                 new BigDecimal(6), // minHours
612                 new BigDecimal("90.00"), // maxGap
613                 dayArray);
614 
615         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
616         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
617         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
618 
619         //reg timeblock 3am - midnight
620         DateTime tbStart = new DateTime(2010, 3, 30, 3, 0, 0, 0, zone);
621         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart,   1, new BigDecimal("21"), "REG", jobNumber, workArea));
622 
623         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
624 
625         // Verify pre-Rule Run
626         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
627                 .put("PRM", BigDecimal.ZERO)
628                 .put("REG", new BigDecimal(21)).build(), aggregate, 2);
629 
630         // Run Rule
631         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
632         tdoc.setTimeBlocks(blocks);
633         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
634 
635         // Verify post-Rule Run
636         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
637                 .put("PRM", new BigDecimal(9))
638                 .put("REG", new BigDecimal(21)).build(),
639                 aggregate,
640                 2);
641 
642     }
643 
644     /**
645      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
646      *
647      * Create a single 24 hour timeblock that spans two different shift, both exceeding the min hours
648      */
649     @Test
650     public void overlapMultipleShiftsWithSameTimeBlockExceedingMinOnFirstShift() {
651         // Create the Rule
652         boolean[] dayArray = {true, true, true, true, true, true, true};
653         // Matches HR Job ID #1 (job # 30)
654         Long jobNumber = 30L;
655         Long workArea = 0L;
656         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
657 
658         //3pm to 8am, 6 hour minimum, 90 minute max gap
659         this.createShiftDifferentialRule(
660                 "BWS-CAL",
661                 "REG",
662                 "PRM",
663                 "IN",
664                 "SD1",
665                 "SD1",
666                 (new LocalTime(15, 0)),
667                 (new LocalTime(8, 0)),
668                 new BigDecimal(6), // minHours
669                 new BigDecimal("90.00"), // maxGap
670                 dayArray);
671 
672         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
673         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
674         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
675 
676         //reg timeblock 1am - 9pm
677         DateTime tbStart = new DateTime(2010, 3, 30, 1, 0, 0, 0, zone);
678         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart,   1, new BigDecimal("19"), "REG", jobNumber, workArea));
679 
680         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
681 
682         // Verify pre-Rule Run
683         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
684                 .put("PRM", BigDecimal.ZERO)
685                 .put("REG", new BigDecimal(19)).build(), aggregate, 2);
686 
687         // Run Rule
688         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
689         tdoc.setTimeBlocks(blocks);
690         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
691 
692         // Verify post-Rule Run
693         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
694                         .put("PRM", new BigDecimal(7))
695                         .put("REG", new BigDecimal(19)).build(),
696                 aggregate,
697                 2);
698 
699     }
700 
701     /**
702      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
703      *
704      * Create a single 17 hour timeblock that spans two different shift, neither exceeding the min hours
705      */
706     @Test
707     public void overlapMultipleShiftsWithSameTimeBlocNeitherExceedingMin() {
708         // Create the Rule
709         boolean[] dayArray = {true, true, true, true, true, true, true};
710         // Matches HR Job ID #1 (job # 30)
711         Long jobNumber = 30L;
712         Long workArea = 0L;
713         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
714 
715         //3pm to 8am, 6 hour minimum, 90 minute max gap
716         this.createShiftDifferentialRule(
717                 "BWS-CAL",
718                 "REG",
719                 "PRM",
720                 "IN",
721                 "SD1",
722                 "SD1",
723                 (new LocalTime(15, 0)),
724                 (new LocalTime(8, 0)),
725                 new BigDecimal(6), // minHours
726                 new BigDecimal("90.00"), // maxGap
727                 dayArray);
728 
729         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
730         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
731         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
732         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
733 
734         //reg timeblock 3am - 8pm
735         DateTime tbStart = new DateTime(2010, 3, 30, 3, 0, 0, 0, zone);
736         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart,   1, new BigDecimal("17"), "REG", jobNumber, workArea));
737 
738         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
739 
740         // Verify pre-Rule Run
741         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
742                 .put("PRM", BigDecimal.ZERO)
743                 .put("REG", new BigDecimal(17)).build(), aggregate, 2);
744 
745         // Run Rule
746         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
747         tdoc.setTimeBlocks(blocks);
748         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
749 
750         // Verify post-Rule Run
751         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
752                         .put("PRM", BigDecimal.ZERO)
753                         .put("REG", new BigDecimal(17)).build(),
754                 aggregate,
755                 2);
756     }
757 
758     /**
759      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
760      *
761      * Create a single 17 hour timeblock that spans two different shift, neither exceeding the min hours
762      */
763     @Test
764     public void multipleTimeBlocksOvernightExceedingMin() {
765         // Create the Rule
766         boolean[] dayArray = {true, true, true, true, true, true, true};
767         // Matches HR Job ID #1 (job # 30)
768         Long jobNumber = 30L;
769         Long workArea = 0L;
770         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
771 
772         //3pm to 8am, 6 hour minimum, 90 minute max gap
773         this.createShiftDifferentialRule(
774                 "BWS-CAL",
775                 "REG",
776                 "PRM",
777                 "IN",
778                 "SD1",
779                 "SD1",
780                 (new LocalTime(15, 0)),
781                 (new LocalTime(8, 0)),
782                 new BigDecimal(6), // minHours
783                 new BigDecimal("90.00"), // maxGap
784                 dayArray);
785 
786         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
787         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
788         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
789         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
790 
791         //reg timeblock 10pm - midnight
792         DateTime tbStart1 = new DateTime(2010, 3, 30, 22, 0, 0, 0, zone);
793         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart1,   1, new BigDecimal("2"), "REG", jobNumber, workArea));
794         //reg timeblock midnight - 5am
795         DateTime tbStart2 = new DateTime(2010, 3, 31, 0, 0, 0, 0, zone);
796         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart2,   1, new BigDecimal("5"), "REG", jobNumber, workArea));
797 
798         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
799 
800         // Verify pre-Rule Run
801         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
802                 .put("PRM", BigDecimal.ZERO)
803                 .put("REG", BigDecimal.valueOf(7)).build(), aggregate, 2);
804 
805         // Run Rule
806         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
807         tdoc.setTimeBlocks(blocks);
808         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
809 
810         // Verify post-Rule Run
811         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
812                         .put("PRM", BigDecimal.valueOf(7))
813                         .put("REG", BigDecimal.valueOf(7)).build(),
814                 aggregate,
815                 2);
816     }
817 
818     /**
819      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
820      *
821      * Create a single 17 hour timeblock that spans two different shift, neither exceeding the min hours
822      */
823     @Test
824     public void multipleTimeBlocksOvernightExceedingMinWithSixtyMinuteGap() {
825         // Create the Rule
826         boolean[] dayArray = {true, true, true, true, true, true, true};
827         // Matches HR Job ID #1 (job # 30)
828         Long jobNumber = 30L;
829         Long workArea = 0L;
830         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
831 
832         //3pm to 8am, 6 hour minimum, 90 minute max gap
833         this.createShiftDifferentialRule(
834                 "BWS-CAL",
835                 "REG",
836                 "PRM",
837                 "IN",
838                 "SD1",
839                 "SD1",
840                 (new LocalTime(15, 0)),
841                 (new LocalTime(8, 0)),
842                 new BigDecimal(6), // minHours
843                 new BigDecimal("90.00"), // maxGap
844                 dayArray);
845 
846         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
847         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
848         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
849         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
850 
851         //reg timeblock 10pm - midnight
852         DateTime tbStart1 = new DateTime(2010, 3, 30, 22, 0, 0, 0, zone);
853         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart1,   1, new BigDecimal("2"), "REG", jobNumber, workArea));
854         //reg timeblock 1am - 5am
855         DateTime tbStart2 = new DateTime(2010, 3, 31, 1, 0, 0, 0, zone);
856         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart2,   1, new BigDecimal("4"), "REG", jobNumber, workArea));
857 
858         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
859 
860         // Verify pre-Rule Run
861         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
862                 .put("PRM", BigDecimal.ZERO)
863                 .put("REG", BigDecimal.valueOf(6)).build(), aggregate, 2);
864 
865         // Run Rule
866         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
867         tdoc.setTimeBlocks(blocks);
868         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
869 
870         // Verify post-Rule Run
871         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
872                         .put("PRM", BigDecimal.valueOf(6))
873                         .put("REG", BigDecimal.valueOf(6)).build(),
874                 aggregate,
875                 2);
876     }
877 
878     /**
879      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
880      *
881      * Create a single 17 hour timeblock that spans two different shift, neither exceeding the min hours
882      */
883     @Test
884     public void multipleTimeBlocksOvernightExceedingMinWithNinetyMinuteGap() {
885         // Create the Rule
886         boolean[] dayArray = {true, true, true, true, true, true, true};
887         // Matches HR Job ID #1 (job # 30)
888         Long jobNumber = 30L;
889         Long workArea = 0L;
890         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
891 
892         //3pm to 8am, 6 hour minimum, 90 minute max gap
893         this.createShiftDifferentialRule(
894                 "BWS-CAL",
895                 "REG",
896                 "PRM",
897                 "IN",
898                 "SD1",
899                 "SD1",
900                 (new LocalTime(15, 0)),
901                 (new LocalTime(8, 0)),
902                 new BigDecimal(6), // minHours
903                 new BigDecimal("90.00"), // maxGap
904                 dayArray);
905 
906         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
907         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
908         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
909         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
910 
911         //reg timeblock 10pm - midnight
912         DateTime tbStart1 = new DateTime(2010, 3, 30, 22, 0, 0, 0, zone);
913         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart1,   1, new BigDecimal("2"), "REG", jobNumber, workArea));
914         //reg timeblock 1:30am - 5:30pm
915         DateTime tbStart2 = new DateTime(2010, 3, 31, 1, 30, 0, 0, zone);
916         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart2,   1, new BigDecimal("4"), "REG", jobNumber, workArea));
917 
918         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
919 
920         // Verify pre-Rule Run
921         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
922                 .put("PRM", BigDecimal.ZERO)
923                 .put("REG", BigDecimal.valueOf(6)).build(), aggregate, 2);
924 
925         // Run Rule
926         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
927         tdoc.setTimeBlocks(blocks);
928         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
929 
930         // Verify post-Rule Run
931         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
932                         .put("PRM", BigDecimal.valueOf(6))
933                         .put("REG", BigDecimal.valueOf(6)).build(),
934                 aggregate,
935                 2);
936     }
937 
938     /**
939      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
940      *
941      * Create a single 17 hour timeblock that spans two different shift, neither exceeding the min hours
942      */
943     @Test
944     public void multipleTimeBlocksOvernightExceedingMinButExceedingGap() {
945         // Create the Rule
946         boolean[] dayArray = {true, true, true, true, true, true, true};
947         // Matches HR Job ID #1 (job # 30)
948         Long jobNumber = 30L;
949         Long workArea = 0L;
950         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
951 
952         //3pm to 8am, 6 hour minimum, 90 minute max gap
953         this.createShiftDifferentialRule(
954                 "BWS-CAL",
955                 "REG",
956                 "PRM",
957                 "IN",
958                 "SD1",
959                 "SD1",
960                 (new LocalTime(15, 0)),
961                 (new LocalTime(8, 0)),
962                 new BigDecimal(6), // minHours
963                 new BigDecimal("90.00"), // maxGap
964                 dayArray);
965 
966         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
967         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
968         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
969         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
970 
971         //reg timeblock 10pm - midnight
972         DateTime tbStart1 = new DateTime(2010, 3, 30, 22, 0, 0, 0, zone);
973         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart1,   1, new BigDecimal("2"), "REG", jobNumber, workArea));
974         //reg timeblock 1:36am - 5:36am
975         DateTime tbStart2 = new DateTime(2010, 3, 31, 1, 36, 0, 0, zone);
976         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart2,   1, new BigDecimal("4"), "REG", jobNumber, workArea));
977 
978         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
979 
980         // Verify pre-Rule Run
981         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
982                 .put("PRM", BigDecimal.ZERO)
983                 .put("REG", BigDecimal.valueOf(6)).build(), aggregate, 2);
984 
985         // Run Rule
986         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
987         tdoc.setTimeBlocks(blocks);
988         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
989 
990         // Verify post-Rule Run
991         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
992                         .put("PRM", BigDecimal.valueOf(0))
993                         .put("REG", BigDecimal.valueOf(6)).build(),
994                 aggregate,
995                 2);
996     }
997 
998 
999     /**
1000      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
1001      *
1002      * Create a single 17 hour timeblock that spans two different shift, neither exceeding the min hours
1003      */
1004     @Test
1005     public void threeBlocksWithinSameShift() {
1006         // Create the Rule
1007         boolean[] dayArray = {true, true, true, true, true, true, true};
1008         // Matches HR Job ID #1 (job # 30)
1009         Long jobNumber = 30L;
1010         Long workArea = 0L;
1011         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
1012 
1013         //3pm to 8am, 6 hour minimum, 90 minute max gap
1014         this.createShiftDifferentialRule(
1015                 "BWS-CAL",
1016                 "REG",
1017                 "PRM",
1018                 "IN",
1019                 "SD1",
1020                 "SD1",
1021                 (new LocalTime(15, 0)),
1022                 (new LocalTime(8, 0)),
1023                 new BigDecimal(6), // minHours
1024                 new BigDecimal("90.00"), // maxGap
1025                 dayArray);
1026 
1027         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
1028         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
1029         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
1030 
1031         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
1032 
1033         //reg timeblock 3pm - 5pm
1034         DateTime tbStart1 = new DateTime(2010, 3, 30, 15, 0, 0, 0, zone);
1035         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart1,   1, new BigDecimal("2"), "REG", jobNumber, workArea));
1036 
1037         // 6pm - 9pm
1038         DateTime tbStart2 = new DateTime(2010, 3, 30, 18, 0, 0, 0, zone);
1039         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart2,   1, new BigDecimal("3"), "REG", jobNumber, workArea));
1040 
1041         // 10pm - 11pm
1042         DateTime tbStart3 = new DateTime(2010, 3, 30, 22, 0, 0, 0, zone);
1043         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart3,   1, new BigDecimal("1"), "REG", jobNumber, workArea));
1044 
1045         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
1046 
1047         // Verify pre-Rule Run
1048         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
1049                 .put("PRM", BigDecimal.ZERO)
1050                 .put("REG", BigDecimal.valueOf(6)).build(), aggregate, 2);
1051 
1052         // Run Rule
1053         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
1054         tdoc.setTimeBlocks(blocks);
1055         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
1056 
1057         // Verify post-Rule Run
1058         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
1059                         .put("PRM", BigDecimal.valueOf(6))
1060                         .put("REG", BigDecimal.valueOf(6)).build(),
1061                 aggregate,
1062                 2);
1063     }
1064 
1065     /**
1066      * Tests WorkSchedules impact on Shift Differential Rule: Simple Case
1067      *
1068      * Create a single 17 hour timeblock that spans two different shift, neither exceeding the min hours
1069      */
1070     @Test
1071     public void fourBlocksWithinSameShiftSpanningTwoDays() {
1072         // Create the Rule
1073         boolean[] dayArray = {true, true, true, true, true, true, true};
1074         // Matches HR Job ID #1 (job # 30)
1075         Long jobNumber = 30L;
1076         Long workArea = 0L;
1077         DateTimeZone zone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
1078 
1079         //3pm to 8am, 6 hour minimum, 90 minute max gap
1080         this.createShiftDifferentialRule(
1081                 "BWS-CAL",
1082                 "REG",
1083                 "PRM",
1084                 "IN",
1085                 "SD1",
1086                 "SD1",
1087                 (new LocalTime(15, 0)),
1088                 (new LocalTime(8, 0)),
1089                 new BigDecimal(6), // minHours
1090                 new BigDecimal("90.00"), // maxGap
1091                 dayArray);
1092 
1093         // Create Time Blocks (2 days, 2 blocks on each day, 15 minute gap between blocks, 4 hours total each.
1094         DateTime start = new DateTime(2010, 3, 29, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
1095         CalendarEntry payCalendarEntry =  HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
1096 
1097         List<TimeBlock> blocks = new ArrayList<TimeBlock>();
1098 
1099         //reg timeblock 5pm - 6pm
1100         DateTime tbStart1 = new DateTime(2010, 3, 30, 17, 0, 0, 0, zone);
1101         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart1,   1, new BigDecimal("1"), "REG", jobNumber, workArea));
1102 
1103         // 7:30pm - 8:30pm
1104         DateTime tbStart2 = new DateTime(2010, 3, 30, 19, 30, 0, 0, zone);
1105         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart2,   1, new BigDecimal("1"), "REG", jobNumber, workArea));
1106 
1107         // 10pm - midnight
1108         DateTime tbStart3 = new DateTime(2010, 3, 30, 22, 0, 0, 0, zone);
1109         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart3,   1, new BigDecimal("2"), "REG", jobNumber, workArea));
1110 
1111         // 1:30am - 4:30am
1112         DateTime tbStart4 = new DateTime(2010, 3, 31, 1, 30, 0, 0, zone);
1113         blocks.addAll(TkTestUtils.createUniformTimeBlocks(tbStart4,   1, new BigDecimal("3"), "REG", jobNumber, workArea));
1114 
1115         TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(blocks, payCalendarEntry);
1116 
1117         // Verify pre-Rule Run
1118         TkTestUtils.verifyAggregateHourSums("Pre-Check", new ImmutableMap.Builder<String, BigDecimal>()
1119                 .put("PRM", BigDecimal.ZERO)
1120                 .put("REG", BigDecimal.valueOf(7)).build(), aggregate, 2);
1121 
1122         // Run Rule
1123         TimesheetDocument tdoc = TkTestUtils.populateBlankTimesheetDocument(start, "admin");
1124         tdoc.setTimeBlocks(blocks);
1125         TkServiceLocator.getShiftDifferentialRuleService().processShiftDifferentialRules(tdoc, aggregate);
1126 
1127         // Verify post-Rule Run
1128         TkTestUtils.verifyAggregateHourSums("Post Rules Check", new ImmutableMap.Builder<String, BigDecimal>()
1129                         .put("PRM", BigDecimal.valueOf(7))
1130                         .put("REG", BigDecimal.valueOf(7)).build(),
1131                 aggregate,
1132                 2);
1133     }
1134 }