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