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 		CalendarEntries endOfMarch = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(beginPeriodDate, endPeriodDate);
105 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfMarch);
106 		Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment("admin", new AssignmentDescriptionKey("30_30_30"), beginPeriodDate);
107 		timeBlocks = TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 3, BigDecimal.TEN, BigDecimal.ZERO);
108 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(new ArrayList<TimeBlock>(), timeBlocks, TKContext.getPrincipalId());
109 		tdoc.setTimeBlocks(timeBlocks);
110 		
111 		// Verify previous calendar times
112 		CalendarEntries payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
113 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
114 		TkTestUtils.verifyAggregateHourSums("Prior month", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("RGN", new BigDecimal(30));}},aggregate,2);
115 				
116 		
117 		// April time blocks & document
118 		start = new DateTime(2010, 4, 1, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
119 		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 2, BigDecimal.TEN, "REG", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
120 		payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
121 		aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
122 		TkTestUtils.verifyAggregateHourSums("Pre-Rules verification", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("REG", new BigDecimal(20));}},aggregate,0);
123 		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(new Date(start.getMillis()));
124 		timesheetDocument.setTimeBlocks(timeBlocks);
125 		
126 		// Create Rule
127 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT);		
128 
129 		// Apply
130 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);		
131 		
132 		// Verify
133 		TkTestUtils.verifyAggregateHourSums("Overtime processed", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.TEN);put("REG", new BigDecimal(10));}},aggregate,0);
134 	}
135 	
136 	
137 	@SuppressWarnings("serial")
138 	@Test
139 	/**
140 	 * OVT hit on previous month.
141 	 * 
142 	 * |------+------+------+------+----+-----+-----|
143 	 * | 27th | 28th | 29th | 30th | xx | 1st | 2nd |
144 	 * |------+------+------+------+----+-----+-----|
145 	 * |   11 |   11 |   11 |   11 | xx |  11 |  11 |
146 	 * |------+------+------+------+----+-----+-----|
147 	 */
148 	public void testProcessPreviousMonthFlsaOT() throws Exception {
149 		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
150 		DateTime start = new DateTime(2010, 6, 27, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
151 		Date beginPeriodDate = new Date(new DateTime(2010, 6, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
152 		Date endPeriodDate = new Date(new DateTime(2010, 7, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
153 		CalendarEntries endOfJune = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(beginPeriodDate, endPeriodDate);
154 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfJune);
155 		Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment("admin", new AssignmentDescriptionKey("30_30_30"), beginPeriodDate);
156 		timeBlocks = TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 4, new BigDecimal(11), BigDecimal.ZERO);
157 		
158 		tdoc.setTimeBlocks(timeBlocks);
159 		
160 		// Create Rule
161 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT);
162 
163 
164 		// Verify previous calendar times
165 		CalendarEntries payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
166 		
167 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
168 		// Create and Process Previous month to have totals set up correctly
169 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(tdoc, aggregate);
170 		TkTestUtils.verifyAggregateHourSums("Prior month", new HashMap<String,BigDecimal>() {{put("OVT", new BigDecimal(4));put("RGN", new BigDecimal(40));}},aggregate,2);
171 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(new ArrayList<TimeBlock>(), aggregate.getFlattenedTimeBlockList(), TKContext.getPrincipalId());
172 		
173 		// April time blocks & document
174 		start = new DateTime(2010, 7, 1, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
175 		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 2, new BigDecimal(11), "RGN", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
176 		payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
177 		aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
178 		TkTestUtils.verifyAggregateHourSums("Pre-Rules verification", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("RGN", new BigDecimal(22));}},aggregate,0);
179 		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(new Date(start.getMillis()));
180 		timesheetDocument.setTimeBlocks(timeBlocks);		
181 
182 		// Apply
183 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);		
184 		
185 		// Verify
186 		TkTestUtils.verifyAggregateHourSums("Overtime processed", new HashMap<String,BigDecimal>() {{put("OVT", new BigDecimal(22));put("RGN", BigDecimal.ZERO);}},aggregate,0);
187 	}
188 	
189 	@SuppressWarnings("serial")
190 	@Test
191 	/**
192 	 * Three step OVT Test
193 	 * 
194 	 * |------+------+------+------+----+-----+-----|
195 	 * | 27th | 28th | 29th | 30th | xx | 1st | 2nd |
196 	 * |------+------+------+------+----+-----+-----|
197 	 * |  ABC | XYZ  | ABC  |  XYZ | xx | ABC | REG | 
198 	 * |------+------+------+------+----+-----+-----|
199 	 * |   11 |   11 |   11 |   11 | xx |  11 |  11 |
200 	 * |------+------+------+------+----+-----+-----|
201 	 * 
202 	 * Contrived example using 3 steps that convert strangely coded hours to 
203 	 * REG via a multi-step process.  Eventually REG is converted to OVT using
204 	 * normal rules.
205 	 * 
206 	 * Step 1: g:SD3:[XYZ] to [ABC]
207 	 * Step 2: g:SD2:[ABC] to [REG]
208 	 * Step 3: g:REG:[REG] to [OVT]
209 	 * 
210 	 * XYZ -> ABC -> REG -> OVT
211 	 * 
212 	 * 27-30th:
213 	 * ABC: 1    
214 	 * XYZ: 1    
215 	 * REG: 40  
216 	 * OVT: 2    
217 	 * 
218 	 * 1st-2nd:
219 	 * ABC: 0    
220 	 * REG: 0    
221 	 * OVT: 22 
222 	 */
223 	public void testProcessThreeStepOvtRule() throws Exception {
224 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 3, new BigDecimal(40), DEFAULT_EFFDT);
225 		this.setupWeeklyOvertimeRule("SD2", "RGN", "SD2", 2, new BigDecimal(1), DEFAULT_EFFDT);
226 		this.setupWeeklyOvertimeRule("SD3", "ABC", "SD3", 1, new BigDecimal(1), DEFAULT_EFFDT);
227 
228 		
229 		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
230 		DateTime start = new DateTime(2010, 6, 27, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
231 		Date beginPeriodDate = new Date(new DateTime(2010, 6, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
232 		Date endPeriodDate = new Date(new DateTime(2010, 7, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone()).getMillis());
233 		CalendarEntries endOfJune = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(beginPeriodDate, endPeriodDate);
234 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfJune);
235 		Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment("admin", new AssignmentDescriptionKey("30_30_30"), beginPeriodDate);		
236 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "ABC", start, 1, new BigDecimal(11), BigDecimal.ZERO));
237 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "XYZ", start.plusDays(1), 1, new BigDecimal(11), BigDecimal.ZERO));
238 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "ABC", start.plusDays(2), 1, new BigDecimal(11), BigDecimal.ZERO));
239 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "XYZ", start.plusDays(3), 1, new BigDecimal(11), BigDecimal.ZERO));		
240 		tdoc.setTimeBlocks(timeBlocks);
241 
242 		// Verify previous calendar times
243 		CalendarEntries payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
244 		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
245 		// Create and Process Previous month to have totals set up correctly
246 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(tdoc, aggregate);
247 		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);
248 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(new ArrayList<TimeBlock>(), aggregate.getFlattenedTimeBlockList(), TKContext.getPrincipalId());
249 		
250 		// April time blocks & document
251 		start = new DateTime(2010, 7, 1, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
252 		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 2, new BigDecimal(11), "REG", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
253 		payCalendarEntry = TkServiceLocator.getCalendarService().getCurrentCalendarDates("admin", new Date(start.getMillis()));
254 		aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
255 		TkTestUtils.verifyAggregateHourSums("Pre-Rules verification", new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("REG", new BigDecimal(22));}},aggregate,0);
256 		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(new Date(start.getMillis()));
257 		timesheetDocument.setTimeBlocks(timeBlocks);		
258 
259 		// Apply
260 		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);		
261 		
262 		// Verify
263 		TkTestUtils.verifyAggregateHourSums("Overtime processed", new HashMap<String,BigDecimal>() {{put("ABC", BigDecimal.ZERO);put("OVT", new BigDecimal(22));put("REG", BigDecimal.ZERO);}},aggregate,0);		
264 	}
265 	
266 	/**
267 	 * Helper method that creates a weekly overtime rule.
268 	 */
269 	private WeeklyOvertimeRule setupWeeklyOvertimeRule(String fromEarnGroup, String toEarnCode, String maxHoursEarnGroup, int step, BigDecimal maxHours, Date effectiveDate){
270 		WeeklyOvertimeRule weeklyOvertimeRule = new WeeklyOvertimeRule();
271 		weeklyOvertimeRule.setActive(true);
272 		weeklyOvertimeRule.setConvertFromEarnGroup(fromEarnGroup);
273 		weeklyOvertimeRule.setConvertToEarnCode(toEarnCode);
274 		weeklyOvertimeRule.setMaxHoursEarnGroup(maxHoursEarnGroup);
275 		weeklyOvertimeRule.setStep(new BigDecimal(step));
276 		weeklyOvertimeRule.setMaxHours(maxHours);
277 		weeklyOvertimeRule.setEffectiveDate(effectiveDate);
278 		
279 		weeklyOvertimeRule.setTkWeeklyOvertimeRuleGroupId(1L);
280 		
281 		TkServiceLocator.getWeeklyOvertimeRuleService().saveOrUpdate(weeklyOvertimeRule);
282 		return weeklyOvertimeRule;
283 	}
284 
285 	@Override
286 	public void tearDown() throws Exception {
287 		KRADServiceLocator.getBusinessObjectService().deleteMatching(WeeklyOvertimeRule.class, new HashMap());
288 		super.tearDown();
289 	}
290 		
291 }