001 /**
002 * Copyright 2004-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.hr.time.util;
017
018 import java.math.BigDecimal;
019 import java.sql.Time;
020 import java.sql.Timestamp;
021 import java.util.ArrayList;
022 import java.util.Collections;
023 import java.util.Comparator;
024 import java.util.List;
025 import java.util.ListIterator;
026
027 import org.apache.commons.collections.CollectionUtils;
028 import org.joda.time.DateTime;
029 import org.joda.time.DateTimeZone;
030 import org.joda.time.Interval;
031 import org.joda.time.LocalDateTime;
032 import org.joda.time.LocalTime;
033 import org.kuali.hr.lm.leaveblock.LeaveBlock;
034 import org.kuali.hr.lm.util.LeaveBlockAggregate;
035 import org.kuali.hr.time.assignment.Assignment;
036 import org.kuali.hr.time.calendar.Calendar;
037 import org.kuali.hr.time.calendar.CalendarEntries;
038 import org.kuali.hr.time.flsa.FlsaDay;
039 import org.kuali.hr.time.flsa.FlsaWeek;
040 import org.kuali.hr.time.service.base.TkServiceLocator;
041 import org.kuali.hr.time.timeblock.TimeBlock;
042 import org.kuali.hr.time.timeblock.TimeHourDetail;
043 import org.kuali.hr.time.timesheet.TimesheetDocument;
044 import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
045
046 public class TkTimeBlockAggregate {
047 private List<List<TimeBlock>> dayTimeBlockList = new ArrayList<List<TimeBlock>>();
048 private List<List<LeaveBlock>> dayLeaveBlockList = new ArrayList<List<LeaveBlock>>();
049 private CalendarEntries payCalendarEntry;
050 private Calendar payCalendar;
051
052 /**
053 * Defaults to using SYSTEM time zone.
054 *
055 * @param timeBlocks
056 * @param payCalendarEntry
057 */
058 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry){
059 this(timeBlocks, payCalendarEntry, TkServiceLocator.getCalendarService().getCalendar(payCalendarEntry.getHrCalendarId()));
060 }
061
062 /**
063 * Defaults to using SYSTEM time zone.
064 *
065 * @param timeBlocks
066 * @param payCalendarEntry
067 * @param payCalendar
068 */
069 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar) {
070 this(timeBlocks, payCalendarEntry, payCalendar, false);
071 }
072
073 /**
074 * Provides the option to refer to the time zone adjusted time for the current
075 * user.
076 * @param timeBlocks
077 * @param payCalendarEntry
078 * @param payCalendar
079 * @param useUserTimeZone
080 */
081 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar, boolean useUserTimeZone) {
082 this(timeBlocks, payCalendarEntry, payCalendar, useUserTimeZone,
083 useUserTimeZone ? TKUtils.getDaySpanForCalendarEntry(payCalendarEntry) : TKUtils.getDaySpanForCalendarEntry(payCalendarEntry, TKUtils.getSystemDateTimeZone()));
084 }
085
086 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar, boolean useUserTimeZone, List<Interval> dayIntervals) {
087 this.payCalendarEntry = payCalendarEntry;
088 this.payCalendar = payCalendar;
089
090 for(Interval dayInt : dayIntervals){
091 List<TimeBlock> dayTimeBlocks = new ArrayList<TimeBlock>();
092 for(TimeBlock timeBlock : timeBlocks){
093
094 // Assumption: Timezones can only be switched at pay period end boundaries.
095 // If the above assumption becomes false, the logic below will need to
096 // accommodate virtual chopping of time blocks to have them fit nicely
097 // in the "days" that are displayed to users.
098
099 DateTime beginTime = useUserTimeZone ? timeBlock.getBeginTimeDisplay() : new DateTime(timeBlock.getBeginTimestamp(), TKUtils.getSystemDateTimeZone());
100 DateTime endTime = useUserTimeZone ? timeBlock.getEndTimeDisplay() : new DateTime(timeBlock.getEndTimestamp(), TKUtils.getSystemDateTimeZone());
101 if(dayInt.contains(beginTime)){
102 if(dayInt.contains(endTime) || endTime.compareTo(dayInt.getEnd()) == 0){
103 // determine if the time block needs to be pushed forward / backward
104 if(beginTime.getHourOfDay() < dayInt.getStart().getHourOfDay()) {
105 timeBlock.setPushBackward(true);
106 }
107
108 dayTimeBlocks.add(timeBlock);
109 }
110 }
111 }
112 dayTimeBlockList.add(dayTimeBlocks);
113 }
114
115 }
116
117 /**
118 * Defaults to using SYSTEM time zone.
119 *
120 * @param timeBlocks
121 * @param leaveBlocks
122 * @param payCalendarEntry
123 */
124 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks, CalendarEntries payCalendarEntry){
125 this(timeBlocks, leaveBlocks, payCalendarEntry, TkServiceLocator.getCalendarService().getCalendar(payCalendarEntry.getHrCalendarId()));
126 }
127
128 /**
129 * Defaults to using SYSTEM time zone.
130 *
131 * @param timeBlocks
132 * @param leaveBlocks
133 * @param payCalendarEntry
134 * @param payCalendar
135 */
136 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar) {
137 this(timeBlocks, leaveBlocks, payCalendarEntry, payCalendar, false);
138 }
139
140 /**
141 * Provides the option to refer to the time zone adjusted time for the current
142 * user.
143 * @param timeBlocks
144 * @param payCalendarEntry
145 * @param payCalendar
146 * @param useUserTimeZone
147 */
148 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar, boolean useUserTimeZone) {
149 this(timeBlocks, leaveBlocks, payCalendarEntry, payCalendar, useUserTimeZone,
150 useUserTimeZone ? TKUtils.getDaySpanForCalendarEntry(payCalendarEntry) : TKUtils.getDaySpanForCalendarEntry(payCalendarEntry, TKUtils.getSystemDateTimeZone()));
151 }
152
153 public TkTimeBlockAggregate(List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks, CalendarEntries payCalendarEntry, Calendar payCalendar, boolean useUserTimeZone, List<Interval> dayIntervals) {
154 this.payCalendarEntry = payCalendarEntry;
155 this.payCalendar = payCalendar;
156
157 for(Interval dayInt : dayIntervals){
158 List<TimeBlock> dayTimeBlocks = new ArrayList<TimeBlock>();
159 for(TimeBlock timeBlock : timeBlocks){
160
161 // Assumption: Timezones can only be switched at pay period end boundaries.
162 // If the above assumption becomes false, the logic below will need to
163 // accommodate virtual chopping of time blocks to have them fit nicely
164 // in the "days" that are displayed to users.
165
166 DateTime beginTime = useUserTimeZone ? timeBlock.getBeginTimeDisplay() : new DateTime(timeBlock.getBeginTimestamp(), TKUtils.getSystemDateTimeZone());
167 DateTime endTime = useUserTimeZone ? timeBlock.getEndTimeDisplay() : new DateTime(timeBlock.getEndTimestamp(), TKUtils.getSystemDateTimeZone());
168 if(dayInt.contains(beginTime)){
169 if(dayInt.contains(endTime) || endTime.compareTo(dayInt.getEnd()) == 0){
170 // determine if the time block needs to be pushed forward / backward
171 if(beginTime.getHourOfDay() < dayInt.getStart().getHourOfDay()) {
172 timeBlock.setPushBackward(true);
173 }
174
175 dayTimeBlocks.add(timeBlock);
176 }
177 }
178 }
179 dayTimeBlockList.add(dayTimeBlocks);
180
181 List<LeaveBlock> dayLeaveBlocks = new ArrayList<LeaveBlock>();
182 for(LeaveBlock leaveBlock : leaveBlocks){
183
184 // Assumption: Timezones can only be switched at pay period end boundaries.
185 // If the above assumption becomes false, the logic below will need to
186 // accommodate virtual chopping of leave blocks to have them fit nicely
187 // in the "days" that are displayed to users.
188
189 DateTimeZone dateTimeZone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
190 DateTime beginTime = new DateTime(leaveBlock.getLeaveDate(), useUserTimeZone ? dateTimeZone : TKUtils.getSystemDateTimeZone());
191 DateTime endTime = new DateTime(leaveBlock.getLeaveDate(), useUserTimeZone ? dateTimeZone : TKUtils.getSystemDateTimeZone());
192 if(dayInt.contains(beginTime)){
193 if(dayInt.contains(endTime) || endTime.compareTo(dayInt.getEnd()) == 0){
194 dayLeaveBlocks.add(leaveBlock);
195 }
196 }
197 }
198 dayLeaveBlockList.add(dayLeaveBlocks);
199 }
200 }
201
202 public List<TimeBlock> getFlattenedTimeBlockList(){
203 List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>();
204 for(List<TimeBlock> timeBlocks : dayTimeBlockList){
205 lstTimeBlocks.addAll(timeBlocks);
206 }
207
208 Collections.sort(lstTimeBlocks, new Comparator<TimeBlock>() { // Sort the Time Blocks
209 public int compare(TimeBlock tb1, TimeBlock tb2) {
210 if (tb1 != null && tb2 != null)
211 return tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
212 return 0;
213 }
214 });
215
216 return lstTimeBlocks;
217 }
218
219 public List<LeaveBlock> getFlattenedLeaveBlockList(){
220 List<LeaveBlock> lstLeaveBlocks = new ArrayList<LeaveBlock>();
221 for(List<LeaveBlock> leaveBlocks : dayLeaveBlockList){
222 lstLeaveBlocks.addAll(leaveBlocks);
223 }
224
225 Collections.sort(lstLeaveBlocks, new Comparator<LeaveBlock>() { // Sort the Leave Blocks
226 public int compare(LeaveBlock lb1, LeaveBlock lb2) {
227 if (lb1 != null && lb2 != null)
228 return lb1.getLeaveDate().compareTo(lb2.getLeaveDate());
229 return 0;
230 }
231 });
232
233 return lstLeaveBlocks;
234 }
235
236 /**
237 * Provides a way to access all of the time blocks for a given week.
238 *
239 * Outer list is 0 indexed list representing days in a week.
240 * Inner List are all of the time blocks for that day.
241 *
242 * Ex.
243 *
244 * List<List<TimeBlock>> week0 = getWeekTimeBlocks(0);
245 * List<TimeBlock> day0 = week0.get(0);
246 *
247 * @param week
248 * @return
249 */
250 public List<List<TimeBlock>> getWeekTimeBlocks(int week){
251 int startIndex = week*7;
252 int endIndex = (week*7)+7;
253 endIndex = endIndex > dayTimeBlockList.size() ? dayTimeBlockList.size() : endIndex;
254
255 // Need to sort each day by clock time.
256 List<List<TimeBlock>> wList = dayTimeBlockList.subList(startIndex, endIndex);
257 for (List<TimeBlock> dList : wList) {
258 Collections.sort(dList, new Comparator<TimeBlock>() { // Sort the Time Blocks
259 public int compare(TimeBlock tb1, TimeBlock tb2) {
260 if (tb1 != null && tb2 != null)
261 return tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
262 return 0;
263 }
264 });
265 }
266
267 return wList;
268 }
269
270 /**
271 * Provides a way to access all of the leave blocks for a given week.
272 *
273 * Outer list is 0 indexed list representing days in a week.
274 * Inner List are all of the leave blocks for that day.
275 *
276 * Ex.
277 *
278 * List<List<LeaveBlock>> week0 = getWeekLeaveBlocks(0);
279 * List<LeaveBlock> day0 = week0.get(0);
280 *
281 * @param week
282 * @return
283 */
284 public List<List<LeaveBlock>> getWeekLeaveBlocks(int week){
285 int startIndex = week*7;
286 int endIndex = (week*7)+7;
287 endIndex = endIndex > dayLeaveBlockList.size() ? dayLeaveBlockList.size() : endIndex;
288
289 // Need to sort each day by clock time.
290 List<List<LeaveBlock>> wList = dayLeaveBlockList.subList(startIndex, endIndex);
291 for (List<LeaveBlock> dList : wList) {
292 Collections.sort(dList, new Comparator<LeaveBlock>() { // Sort the Leave Blocks
293 public int compare(LeaveBlock lb1, LeaveBlock lb2) {
294 if (lb1 != null && lb2 != null)
295 return lb1.getLeaveDate().compareTo(lb2.getLeaveDate());
296 return 0;
297 }
298 });
299 }
300
301 return wList;
302 }
303
304 /**
305 * When consuming these weeks, you must be aware that you could be on a
306 * virtual day, ie noon to noon schedule and have your FLSA time start
307 * before the virtual day start,
308 * but still have a full 7 days for your week.
309 *
310 * @param zone The TimeZone to use when constructing this relative sorting.
311 */
312 public List<FlsaWeek> getFlsaWeeks(DateTimeZone zone){
313 int flsaDayConstant = payCalendar.getFlsaBeginDayConstant();
314 Time flsaBeginTime = payCalendar.getFlsaBeginTime();
315
316 // We can use these to build our interval, we have to make sure we
317 // place them on the proper day when we construct it.
318 LocalTime flsaBeginLocalTime = LocalTime.fromDateFields(flsaBeginTime);
319
320 // Defines both the start date and the start virtual time.
321 // We will add 1 day to this to move over all days.
322 //
323 // FLSA time is set. This is an FLSA start date.
324 LocalDateTime startLDT = payCalendarEntry.getBeginLocalDateTime();
325 // DateTime startDate = new DateTime(payCalendarEntry.getBeginPeriodDateTime());
326 // startDate = startDate.toLocalDate().toDateTime(flsaBeginLocalTime,TKUtils.getSystemDateTimeZone());
327
328 List<FlsaWeek> flsaWeeks = new ArrayList<FlsaWeek>();
329 List<TimeBlock> flattenedTimeBlocks = getFlattenedTimeBlockList();
330 List<LeaveBlock> flattenedLeaveBlocks = getFlattenedLeaveBlockList();
331 FlsaWeek currentWeek = new FlsaWeek(flsaDayConstant, flsaBeginLocalTime, LocalTime.fromDateFields(payCalendarEntry.getBeginPeriodDateTime()));
332 FlsaDay flsaDay = new FlsaDay(startLDT, flattenedTimeBlocks, flattenedLeaveBlocks, zone);
333 currentWeek.addFlsaDay(flsaDay);
334 flsaWeeks.add(currentWeek);
335
336 for (int i = 1; i < dayTimeBlockList.size(); i++) {
337 LocalDateTime currentDate = startLDT.plusDays(i);
338 flsaDay = new FlsaDay(currentDate, flattenedTimeBlocks, flattenedLeaveBlocks, zone);
339
340 if (currentDate.getDayOfWeek() == flsaDayConstant) {
341 currentWeek = new FlsaWeek(flsaDayConstant, flsaBeginLocalTime, flsaBeginLocalTime);
342 flsaWeeks.add(currentWeek);
343 }
344
345 currentWeek.addFlsaDay(flsaDay);
346 }
347
348 return flsaWeeks;
349 }
350
351 public List<List<FlsaWeek>> getFlsaWeeks(DateTimeZone zone, String principalId) {
352 List<List<FlsaWeek>> flsaWeeks = new ArrayList<List<FlsaWeek>>();
353
354 List<FlsaWeek> currentWeeks = getFlsaWeeks(zone);
355
356 for (ListIterator<FlsaWeek> weekIterator = currentWeeks.listIterator(); weekIterator.hasNext(); ) {
357 List<FlsaWeek> flsaWeek = new ArrayList<FlsaWeek>();
358
359 int index = weekIterator.nextIndex();
360 FlsaWeek currentWeek = weekIterator.next();
361
362 if (index == 0 && !currentWeek.isFirstWeekFull()) {
363 CalendarEntries previousCalendarEntry = TkServiceLocator.getCalendarEntriesService().getPreviousCalendarEntriesByCalendarId(payCalendar.getHrCalendarId(), payCalendarEntry);
364 if (previousCalendarEntry != null) {
365 TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, previousCalendarEntry.getBeginPeriodDateTime(), previousCalendarEntry.getEndPeriodDateTime());
366 if (timesheetDocumentHeader != null) {
367 TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentHeader.getDocumentId());
368 List<String> assignmentKeys = new ArrayList<String>();
369 for(Assignment assignment : timesheetDocument.getAssignments()) {
370 assignmentKeys.add(assignment.getAssignmentKey());
371 }
372
373 List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
374 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, previousCalendarEntry.getBeginPeriodDateTime(), previousCalendarEntry.getEndPeriodDateTime(), assignmentKeys);
375 if (CollectionUtils.isNotEmpty(timeBlocks)) {
376 TkTimeBlockAggregate previousAggregate = new TkTimeBlockAggregate(timeBlocks, leaveBlocks, previousCalendarEntry, payCalendar, true);
377 List<FlsaWeek> previousWeek = previousAggregate.getFlsaWeeks(zone);
378 if (CollectionUtils.isNotEmpty(previousWeek)) {
379 flsaWeek.add(previousWeek.get(previousWeek.size() - 1));
380 }
381 }
382 }
383 }
384 }
385
386 flsaWeek.add(currentWeek);
387
388 if (index == currentWeeks.size() - 1 && !currentWeek.isLastWeekFull()) {
389 CalendarEntries nextCalendarEntry = TkServiceLocator.getCalendarEntriesService().getNextCalendarEntriesByCalendarId(payCalendar.getHrCalendarId(), payCalendarEntry);
390 if (nextCalendarEntry != null) {
391 TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, nextCalendarEntry.getBeginPeriodDateTime(), nextCalendarEntry.getEndPeriodDateTime());
392 if (timesheetDocumentHeader != null) {
393 TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentHeader.getDocumentId());
394 List<String> assignmentKeys = new ArrayList<String>();
395 for(Assignment assignment : timesheetDocument.getAssignments()) {
396 assignmentKeys.add(assignment.getAssignmentKey());
397 }
398
399 List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
400 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, nextCalendarEntry.getBeginPeriodDateTime(), nextCalendarEntry.getEndPeriodDateTime(), assignmentKeys);
401 if (CollectionUtils.isNotEmpty(timeBlocks)) {
402 TkTimeBlockAggregate nextAggregate = new TkTimeBlockAggregate(timeBlocks, leaveBlocks, nextCalendarEntry, payCalendar, true);
403 List<FlsaWeek> nextWeek = nextAggregate.getFlsaWeeks(zone);
404 if (CollectionUtils.isNotEmpty(nextWeek)) {
405 flsaWeek.add(nextWeek.get(0));
406 }
407 }
408 }
409 }
410 }
411
412 flsaWeeks.add(flsaWeek);
413 }
414
415 return flsaWeeks;
416 }
417
418 /**
419 * @return the total number of weeks that this object represents.
420 */
421 public int numberOfAggregatedWeeks() {
422 int weeks = 0;
423
424 if (this.dayTimeBlockList.size() > 0) {
425 weeks = this.dayTimeBlockList.size() / 7;
426 if (this.dayTimeBlockList.size() % 7 > 0)
427 weeks++;
428 }
429
430 return weeks;
431 }
432
433 public List<List<TimeBlock>> getDayTimeBlockList() {
434 return dayTimeBlockList;
435 }
436
437 public List<List<LeaveBlock>> getDayLeaveBlockList() {
438 return dayLeaveBlockList;
439 }
440
441 public CalendarEntries getPayCalendarEntry() {
442 return payCalendarEntry;
443 }
444
445 public void setPayCalendarEntry(CalendarEntries payCalendarEntry) {
446 this.payCalendarEntry = payCalendarEntry;
447 }
448
449 public Calendar getPayCalendar() {
450 return payCalendar;
451 }
452
453 public void setPayCalendar(Calendar payCalendar) {
454 this.payCalendar = payCalendar;
455 }
456
457 //Very much a hack to add valid leave blocks to a time block aggregate...
458 public static TkTimeBlockAggregate combineTimeAndLeaveAggregates(TkTimeBlockAggregate tbAggregate, LeaveBlockAggregate lbAggregate) {
459 if (tbAggregate != null
460 && lbAggregate != null
461 && tbAggregate.getDayTimeBlockList().size() == lbAggregate.getDayLeaveBlockList().size()) {
462 DateTimeZone dtz = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
463 for (int i = 0; i < tbAggregate.getDayTimeBlockList().size(); i++) {
464 List<LeaveBlock> leaveBlocks = lbAggregate.getDayLeaveBlockList().get(i);
465 if (CollectionUtils.isNotEmpty(leaveBlocks)) {
466 for (LeaveBlock lb : leaveBlocks) {
467 //convert leave block to generic time block and add to list
468 //conveniently, we only really need the hours amount
469 TimeBlock timeBlock = new TimeBlock();
470 timeBlock.setHours(lb.getLeaveAmount().negate());
471 //DateTime beginTime = new DateTime(lb.getLeaveDate().getTime());
472 //beginTime = beginTime.
473 DateTime beginTime = new DateTime(lb.getLeaveDate()).toLocalDate().toDateTimeAtStartOfDay().withZoneRetainFields(dtz);
474 timeBlock.setBeginTimestamp(new Timestamp(beginTime.getMillis()));
475 timeBlock.setEndTimestamp(new Timestamp(beginTime.plusMinutes(timeBlock.getHours().intValue()).getMillis()));
476 timeBlock.setAssignmentKey(lb.getAssignmentKey());
477 timeBlock.setEarnCode(lb.getEarnCode());
478 timeBlock.setPrincipalId(lb.getPrincipalId());
479 timeBlock.setWorkArea(lb.getWorkArea());
480 TimeHourDetail timeHourDetail = new TimeHourDetail();
481 timeHourDetail.setEarnCode(timeBlock.getEarnCode());
482 timeHourDetail.setHours(timeBlock.getHours());
483 timeHourDetail.setAmount(BigDecimal.ZERO);
484 timeBlock.addTimeHourDetail(timeHourDetail);
485 tbAggregate.getDayTimeBlockList().get(i).add(timeBlock);
486 }
487 }
488
489 }
490 }
491 return tbAggregate;
492 }
493
494 }