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.kpme.tklm.time.rule.overtime.weekly;
17  
18  import java.math.BigDecimal;
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  
23  import org.joda.time.DateTime;
24  import org.joda.time.LocalDate;
25  import org.junit.Test;
26  import org.kuali.kpme.core.IntegrationTest;
27  import org.kuali.kpme.core.assignment.Assignment;
28  import org.kuali.kpme.core.assignment.AssignmentDescriptionKey;
29  import org.kuali.kpme.core.calendar.entry.CalendarEntry;
30  import org.kuali.kpme.core.service.HrServiceLocator;
31  import org.kuali.kpme.core.util.TKUtils;
32  import org.kuali.kpme.tklm.TKLMIntegrationTestCase;
33  import org.kuali.kpme.tklm.time.rules.overtime.weekly.WeeklyOvertimeRule;
34  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
35  import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
36  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
37  import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
38  import org.kuali.kpme.tklm.utils.TkTestUtils;
39  import org.kuali.rice.krad.service.KRADServiceLocator;
40  
41  /**
42   * 
43   * @author djunk
44   *
45   */
46  @IntegrationTest
47  public class WeeklyOvertimeRuleServiceTest extends TKLMIntegrationTestCase {
48  	
49  	private static DateTime DEFAULT_EFFDT = new DateTime(2010, 1, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
50  	private static Long DEFAULT_JOB_NUMBER = 30L;
51  	private static Long DEFAULT_WORK_AREA = 30L;
52  	
53  	@SuppressWarnings("serial")
54  	@Test
55  	/**
56  	 * This test should create 10 hours of OVT and leave 40 hours of REG remaining.
57  	 * It operates WITHIN a standard week.
58  	 * 
59  	 */
60  	public void testProcessSimpleStandardWeek() throws Exception {
61  		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
62  		DateTime start = new DateTime(2010, 1, 4, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
63  		timeBlocks = TkTestUtils.createUniformTimeBlocks(start, 5, BigDecimal.TEN, "REG", DEFAULT_JOB_NUMBER, DEFAULT_WORK_AREA);
64  		CalendarEntry payCalendarEntry = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", DEFAULT_EFFDT);
65  
66  		// Check our initial data.
67  		TkTimeBlockAggregate aggregate = new TkTimeBlockAggregate(timeBlocks, payCalendarEntry);
68  		TkTestUtils.verifyAggregateHourSums(new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.ZERO);put("REG", new BigDecimal(50));}},aggregate,1);
69  		
70  		// Create the rule.
71  		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT.toLocalDate());		
72  		TimesheetDocument timesheetDocument = TkTestUtils.populateBlankTimesheetDocument(DEFAULT_EFFDT, "admin");
73  		timesheetDocument.setTimeBlocks(timeBlocks);
74  		TkServiceLocator.getWeeklyOvertimeRuleService().processWeeklyOvertimeRule(timesheetDocument, aggregate);
75  		
76  		// Check the rule for OVT applied data.
77  		TkTestUtils.verifyAggregateHourSums(new HashMap<String,BigDecimal>() {{put("OVT", BigDecimal.TEN);put("REG", new BigDecimal(40));}},aggregate,1);
78  	}
79  	
80  	@SuppressWarnings("serial")
81  	@Test
82  	/**
83  	 * OVT Hit on Current Month
84  	 * 
85  	 * This test should create 10 hours of OVT on the first FLSA week of the
86  	 * current pay period.  The first FLSA week has 20 REG hours, the previous
87  	 * pay period was a partial week and contained 30 REG hours.
88  	 * 
89  	 * march 29-31; april 1-4
90  	 *  
91  	 *  |--------+--------+--------+----+-------+-------|
92  	 *  |   29th |   30th |   31st | xx |   1st |   2nd |
93  	 *  |--------+--------+--------+----+-------+-------|
94  	 *  |     10 |     10 |     10 | xx |    10 |    10 |
95  	 *  |--------+--------+--------+----+-------+-------|
96  	 * 
97  	 *  4/1/2010 starts on a Thursday
98  	 */
99  	public void testProcessPreviousMonthFlsaBoundary() throws Exception {
100 		// March end time blocks: 3/29-3-31 [m, w]
101 		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
102 		DateTime start = new DateTime(2010, 3, 29, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
103 		DateTime beginPeriodDate = new DateTime(2010, 3, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
104 		DateTime endPeriodDate = new DateTime(2010, 4, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
105 		CalendarEntry endOfMarch = HrServiceLocator.getCalendarEntryService().getCalendarEntryByIdAndPeriodEndDate("2", endPeriodDate);
106 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfMarch);
107 		Assignment assignment = HrServiceLocator.getAssignmentService().getAssignment("admin", AssignmentDescriptionKey.get("30_30_30"), beginPeriodDate.toLocalDate());
108 		timeBlocks = TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 3, BigDecimal.TEN, BigDecimal.ZERO, "admin");
109 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(new ArrayList<TimeBlock>(), timeBlocks, "admin");
110 		tdoc.setTimeBlocks(timeBlocks);
111 		
112 		// Verify previous calendar times
113 		CalendarEntry payCalendarEntry = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
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 = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
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(start, "admin");
125 		timesheetDocument.setTimeBlocks(timeBlocks);
126 		
127 		// Create Rule
128 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT.toLocalDate());		
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 		DateTime beginPeriodDate = new DateTime(2010, 6, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
153 		DateTime endPeriodDate = new DateTime(2010, 7, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
154         CalendarEntry endOfJune = HrServiceLocator.getCalendarEntryService().getCalendarEntryByIdAndPeriodEndDate("2", endPeriodDate);
155 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfJune);
156 		Assignment assignment = HrServiceLocator.getAssignmentService().getAssignment("admin", AssignmentDescriptionKey.get("30_30_30"), beginPeriodDate.toLocalDate());
157 		timeBlocks = TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "RGN", start, 4, new BigDecimal(11), BigDecimal.ZERO, "admin");
158 		
159 		tdoc.setTimeBlocks(timeBlocks);
160 		
161 		// Create Rule
162 		this.setupWeeklyOvertimeRule("REG", "OVT", "REG", 1, new BigDecimal(40), DEFAULT_EFFDT.toLocalDate());
163 
164 
165 		// Verify previous calendar times
166 		CalendarEntry payCalendarEntry = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
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(), "admin");
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 = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
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(start, "admin");
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.toLocalDate());
226 		this.setupWeeklyOvertimeRule("SD2", "RGN", "SD2", 2, new BigDecimal(1), DEFAULT_EFFDT.toLocalDate());
227 		this.setupWeeklyOvertimeRule("SD3", "ABC", "SD3", 1, new BigDecimal(1), DEFAULT_EFFDT.toLocalDate());
228 
229 		
230 		List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
231 		DateTime start = new DateTime(2010, 6, 27, 5, 0, 0, 0, TKUtils.getSystemDateTimeZone());
232 		DateTime beginPeriodDate = new DateTime(2010, 6, 15, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
233 		DateTime endPeriodDate = new DateTime(2010, 7, 1, 0, 0, 0, 0, TKUtils.getSystemDateTimeZone());
234         CalendarEntry endOfJune = HrServiceLocator.getCalendarEntryService().getCalendarEntryByIdAndPeriodEndDate("2", endPeriodDate);
235 		TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().openTimesheetDocument("admin", endOfJune);
236 		Assignment assignment = HrServiceLocator.getAssignmentService().getAssignment("admin", AssignmentDescriptionKey.get("30_30_30"), beginPeriodDate.toLocalDate());		
237 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "ABC", start, 1, new BigDecimal(11), BigDecimal.ZERO, "admin"));
238 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "XYZ", start.plusDays(1), 1, new BigDecimal(11), BigDecimal.ZERO, "admin"));
239 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "ABC", start.plusDays(2), 1, new BigDecimal(11), BigDecimal.ZERO, "admin"));
240 		timeBlocks.addAll(TkTestUtils.createUniformActualTimeBlocks(tdoc, assignment, "XYZ", start.plusDays(3), 1, new BigDecimal(11), BigDecimal.ZERO, "admin"));		
241 		tdoc.setTimeBlocks(timeBlocks);
242 
243 		// Verify previous calendar times
244 		CalendarEntry payCalendarEntry = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
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(), "admin");
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 = HrServiceLocator.getCalendarEntryService().getCurrentCalendarDates("admin", start);
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(start, "admin");
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, LocalDate 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.setEffectiveLocalDate(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 }