source: trunk/grails-app/services/TaskSearchService.groovy @ 713

Last change on this file since 713 was 713, checked in by gav, 13 years ago

Dynamic task quick search feature.

File size: 28.4 KB
Line 
1import net.kromhouts.HqlBuilder
2import grails.orm.PagedResultList
3import org.hibernate.FetchMode as FM
4
5/**
6* Service class that encapsulates the business logic for Task searches.
7*/
8class TaskSearchService {
9
10    boolean transactional = false
11
12    def authService
13    def dateUtilService
14    def messageSource
15
16    def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib()
17
18    def paramsMax = 100000
19
20    // Be sure to update taskQuickSearchPane.js if quickSearchSelection is changed.
21    def getQuickSearchSelection() {
22        [ 'allTasks': g.message(code: 'task.search.text.all.tasks'),
23        'budgetPlanned': g.message(code: 'task.search.text.budget.planned'),
24        'budgetUnplanned': g.message(code: 'task.search.text.budget.unplanned'),
25        'personsImmediateCallouts': g.message(code: 'task.search.text.persons.immediate.callouts'),
26        'personsTasks': g.message(code: 'task.search.text.persons.tasks')
27        ]
28    }
29
30    /**
31    * Selects and returns the correct search results based on the supplied quickSearch.
32    * @param params The request params, may contain params.quickSearch string to specify the search.
33    * @param locale The locale to use when generating result.message.
34    */
35    def getQuickSearch(params = [:], locale) {
36        def result = [:]
37        result.quickSearch = params.quickSearch ?: "personsTasks"
38        if(params.person)
39            result.person = Person.get(params.person.id.toLong())
40        else
41            result.person = authService.currentUser
42        result.startDate = params.startDate ?: dateUtilService.today
43        result.endDate = params.endDate ?: dateUtilService.today
44        // Auto swap date range.
45        if(result.startDate > result.endDate) {
46            def tempStartDate = result.startDate
47            result.startDate = result.endDate
48            result.endDate = tempStartDate
49        }
50        result.includeCompleted = params.includeCompleted = params.includeCompleted ? true:false
51
52        def getMessage = { Map m ->
53            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
54        }
55
56        def formatted = { Date d ->
57            g.formatDate(format: "EEE, dd-MMM-yyyy", date: d)
58        }
59
60        def startOfToday = dateUtilService.today
61        def startOfYesterday = dateUtilService.yesterday
62        def startOfTomorrow = dateUtilService.tomorrow
63        def oneWeekAgo = dateUtilService.oneWeekAgo
64
65        def formattedStartOfToday = formatted(startOfToday)
66        def formattedStartOfYesterday = formatted(startOfYesterday)
67        def formattedStartOfTomorrow = formatted(startOfTomorrow)
68        def formattedOneWeekAgo = formatted(oneWeekAgo)
69
70        def allTasks = {
71            result.taskInstanceList = getTasks(params, result.startDate, result.endDate+1)
72            if(result.taskInstanceList.totalCount > 0) {
73                if(result.startDate == result.endDate)
74                    result.message = getMessage(code:"task.search.text.all.tasks.message",
75                                                                    args:[ formatted(result.startDate) ])
76                else
77                    result.message = getMessage(code:"task.search.text.all.tasks.between.message",
78                                                                    args:[ formatted(result.startDate), formatted(result.endDate) ])
79            }
80            else {
81                if(result.startDate == result.endDate)
82                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
83                                                                    args:[ formatted(result.startDate) ])
84                else
85                    result.message = getMessage(code:"task.search.text.all.tasks.between.none.found",
86                                                                    args:[ formatted(result.startDate), formatted(result.endDate) ])
87            }
88
89        }
90
91        def personsTasks = {
92            result.taskInstanceList = getPersonsTasks(params, result.person, result.startDate, result.endDate+1)
93            if(result.taskInstanceList.totalCount > 0) {
94                if(result.startDate == result.endDate)
95                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
96                                                                    args:[ result.person, formatted(result.startDate) ])
97                else
98                    result.message = getMessage(code:"task.search.text.persons.tasks.between.message",
99                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
100            }
101            else {
102                if(result.startDate == result.endDate)
103                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
104                                                                    args:[ result.person, formatted(result.startDate) ])
105                else
106                    result.message = getMessage(code:"task.search.text.persons.tasks.between.none.found",
107                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
108            }
109
110        }
111
112        def personsImmediateCallouts = {
113            result.taskInstanceList = getPersonsImmediateCallouts(params, result.person, result.startDate, result.endDate+1)
114            if(result.taskInstanceList.totalCount > 0) {
115                if(result.startDate == result.endDate)
116                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.message",
117                                                                    args:[ result.person, formatted(result.startDate) ])
118                else
119                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.between.message",
120                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
121            }
122            else {
123                if(result.startDate == result.endDate)
124                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.none.found",
125                                                                    args:[ result.person, formatted(result.startDate) ])
126                else
127                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.between.none.found",
128                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
129            }
130
131        }
132
133        switch (result.quickSearch) {
134            case "myTodays":
135                result.quickSearch = "personsTasks"
136                result.startDate = startOfToday
137                result.endDate = startOfToday
138                personsTasks()
139                break
140            case "myYesterdays":
141                result.quickSearch = "personsTasks"
142                result.startDate = startOfYesterday
143                result.endDate = startOfYesterday
144                personsTasks()
145                break
146            case "myTomorrows":
147                result.quickSearch = "personsTasks"
148                result.startDate = startOfTomorrow
149                result.endDate = startOfTomorrow
150                personsTasks()
151                break
152            case "myPastWeek":
153                result.quickSearch = "personsTasks"
154                result.startDate = oneWeekAgo
155                result.endDate = startOfToday
156                personsTasks()
157                break
158            case "personsTasks":
159                personsTasks()
160                break
161            case "personsImmediateCallouts":
162                personsImmediateCallouts()
163                break
164            case "todays":
165                result.quickSearch = "allTasks"
166                result.startDate = startOfToday
167                result.endDate = startOfToday
168                allTasks()
169                break
170            case "yesterdays":
171                result.quickSearch = "allTasks"
172                result.startDate = startOfYesterday
173                result.endDate = startOfToday
174                allTasks()
175                break
176            case "tomorrows":
177                result.quickSearch = "allTasks"
178                result.startDate = startOfTomorrow
179                result.endDate = startOfTomorrow+1
180                allTasks()
181                break
182            case "pastWeek":
183                result.quickSearch = "allTasks"
184                result.startDate = oneWeekAgo
185                result.endDate = startOfTomorrow
186                allTasks()
187                break
188            case "allTasks":
189                allTasks()
190                break
191            case "budgetUnplanned":
192                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(1), result.startDate, result.endDate+1)
193                if(result.taskInstanceList.totalCount > 0) {
194                    if(result.startDate == result.endDate)
195                        result.message = getMessage(code:"task.search.text.budget.unplanned.message",
196                                                                        args:[ formatted(result.startDate) ])
197                    else
198                        result.message = getMessage(code:"task.search.text.budget.unplanned.between.message",
199                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
200                }
201                else {
202                    if(result.startDate == result.endDate)
203                        result.message = getMessage(code:"task.search.text.budget.unplanned.none.found",
204                                                                        args:[ formatted(result.startDate) ])
205                    else
206                        result.message = getMessage(code:"task.search.text.budget.unplanned.between.none.found",
207                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
208                }
209                break
210            case "budgetPlanned":
211                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(2), result.startDate, result.endDate+1)
212                if(result.taskInstanceList.totalCount > 0) {
213                    if(result.startDate == result.endDate)
214                        result.message = getMessage(code:"task.search.text.budget.planned.message",
215                                                                        args:[ formatted(result.startDate) ])
216                    else
217                        result.message = getMessage(code:"task.search.text.budget.planned.between.message",
218                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
219                }
220                else {
221                    if(result.startDate == result.endDate)
222                        result.message = getMessage(code:"task.search.text.budget.planned.none.found",
223                                                                        args:[ formatted(result.startDate) ])
224                    else
225                        result.message = getMessage(code:"task.search.text.budget.planned.between.none.found",
226                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
227                }
228                break
229            default:
230                //case "plannersRange":
231                result.quickSearch = "allTasks"
232                result.startDate = oneWeekAgo
233                result.endDate = startOfToday+15
234                allTasks()
235                break
236        } // switch.
237
238        // Success.
239        return result
240
241    } // getQuickSearch
242
243    /**
244    * Get all tasks that are not in the trash bin, by default today's tasks.
245    * @param params The request params.
246    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
247    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
248    */
249    def getTasks(params, startDate=null, endDate=null) {
250        def paginateParams = [:]
251        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
252        paginateParams.offset = params?.offset?.toInteger() ?: 0
253
254        def orderBy = ''
255        if(params.sort?.contains('.')) // protect against filterpane bug.
256            params.sort = null
257        if(params.sort && params.order) {
258            def sort = "task." + params.sort
259            def order = (params.order == "asc") ? "asc" : "desc"
260            orderBy = " order by " + sort + ' ' + order
261        }
262        else
263            orderBy = " order by task.taskStatus, task.taskPriority, task.targetStartDate"
264
265        def namedParams = [:]
266        namedParams.startDate = startDate ?: dateUtilService.today
267        namedParams.endDate = endDate ?: dateUtilService.tomorrow
268
269        def baseQuery = "from Task as task \
270                                        where (task.trash = false \
271                                                    and task.targetStartDate < :endDate \
272                                                    and task.targetCompletionDate >= :startDate \
273                                        )"
274
275        def searchQuery = "select distinct task " + baseQuery + orderBy
276        def list = Task.executeQuery(searchQuery, namedParams, paginateParams)
277
278        def countQuery = "select count(distinct task) as taskCount " + baseQuery
279        def totalCount = Task.executeQuery(countQuery, namedParams)[0].toInteger()
280
281        def taskInstanceList = new PagedResultList(list, totalCount)
282        return taskInstanceList
283    } // getPTasks()
284
285    /**
286    * Get a person's tasks, by default current user and today's tasks.
287    * "My tasks and approved tasks that I am assigned to"
288    * @param params The request params.
289    * @param person The person to get tasks for, defaults to current user.
290    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
291    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
292    */
293    def getPersonsTasks(params, person=null, startDate=null, endDate=null) {
294
295        def max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
296        def offset = params?.offset?.toInteger() ?: 0
297
298        def orderBy = ''
299        if(params.sort?.contains('.')) // protect against filterpane bug.
300            params.sort = null
301        if(params.sort && params.order) {
302            def sort = "task." + params.sort
303            def order = (params.order == "asc") ? "asc" : "desc"
304            orderBy = "by " + sort + ' ' + order
305        }
306        else
307            orderBy = "by task.taskStatus, task.taskPriority, task.targetStartDate"
308
309        def q = new HqlBuilder().query {
310
311            select 'count(distinct task) as taskCount'
312            from 'Task as task',
313                    'left join task.assignedPersons as assignedPersonOfTask',
314                    'left join assignedPersonOfTask.person as assignedPerson',
315                    'left join task.assignedGroups as assignedGroupOfTask',
316                    'left join assignedGroupOfTask.personGroup as personGroup',
317                    'left join personGroup.persons as assignedPersonViaGroup',
318                    'left join task.taskModifications as taskModification',
319                    'left join taskModification.person as createdBy',
320                    'left join taskModification.taskModificationType as taskModificationType'
321            where 'task.trash = false'
322                    and 'task.targetStartDate < :endDate'
323                    and 'task.targetCompletionDate >= :startDate'
324                    if(!params.includeCompleted) {
325                        and 'task.taskStatus.id != 3' // Complete.
326                    }
327                    and {
328                        where '(taskModificationType.id = 1 and createdBy = :person and task.leadPerson = :person)' // Created.
329                        or '(task.approved = true and (task.leadPerson = :person or assignedPerson = :person or assignedPersonViaGroup = :person))'
330                    }
331        }
332
333        q.namedParams.person = person ?: authService.currentUser
334        q.namedParams.startDate = startDate ?: dateUtilService.today
335        q.namedParams.endDate = endDate ?: dateUtilService.tomorrow
336
337        def totalCount = Task.executeQuery(q.query, q.namedParams)[0].toInteger()
338
339        q.select = "distinct task"
340        q.order = orderBy
341        def list = Task.executeQuery(q.query, q.namedParams, q.paginateParams)
342
343        def taskInstanceList = new PagedResultList(list, totalCount)
344        return taskInstanceList
345    } // getPersonsTasks()
346
347    /**
348    * Get a person's immediateCallout tasks, by default all users and today's tasks.
349    * @param params The request params.
350    * @param person The person to get tasks for, defaults to null and therefore all immediateCallouts.
351    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
352    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
353    */
354    def getPersonsImmediateCallouts(params, person=null, startDate=null, endDate=null) {
355
356        def max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
357        def offset = params?.offset?.toInteger() ?: 0
358
359        def orderBy = ''
360        if(params.sort?.contains('.')) // protect against filterpane bug.
361            params.sort = null
362        if(params.sort && params.order) {
363            def sort = "task." + params.sort
364            def order = (params.order == "asc") ? "asc" : "desc"
365            orderBy = "by " + sort + ' ' + order
366        }
367        else
368            orderBy = "by task.taskStatus, task.taskPriority, task.targetStartDate"
369
370        def q = new HqlBuilder().query {
371
372            select 'count(distinct task) as taskCount'
373            from 'Task as task',
374                    'left join task.taskModifications as taskModification',
375                    'left join taskModification.person as createdBy',
376                    'left join taskModification.taskModificationType as taskModificationType'
377            where 'task.taskType.id = 1' // Immediate Callout.
378                    and 'task.targetStartDate < :endDate'
379                    and 'task.targetCompletionDate >= :startDate'
380                    if(!params.includeCompleted) {
381                        and 'task.taskStatus.id != 3' // Complete.
382                    }
383                    if(person) {
384                        namedParams.person = person
385                        and '( (taskModificationType.id = 1 and createdBy = :person) or task.leadPerson = :person)' // Created or Lead Person.
386                    }
387                    and 'task.trash = false'
388        }
389
390        q.namedParams.startDate = startDate ?: dateUtilService.today
391        q.namedParams.endDate = endDate ?: dateUtilService.tomorrow
392
393        def totalCount = Task.executeQuery(q.query, q.namedParams)[0].toInteger()
394
395        q.select = "distinct task"
396        q.order = orderBy
397        def list = Task.executeQuery(q.query, q.namedParams, q.paginateParams)
398
399        def taskInstanceList = new PagedResultList(list, totalCount)
400        return taskInstanceList
401    } // getPersonsImmediateCallouts()
402
403    /**
404    * Get tasks by budget status, by default planned in the last week.
405    * @param params The request params.
406    * @param budgetStatus Defaults to planned.
407    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
408    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
409    */
410    def getBudgetTasks(params, taskBudgetStatus=null, startDate=null, endDate=null) {
411        def paginateParams = [:]
412        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
413        paginateParams.offset = params?.offset?.toInteger() ?: 0
414
415        def sort = "task." + (params?.sort ?: "targetStartDate")
416        def order = params?.order == "desc" ? "desc" : "asc"
417        def orderBy = " order by " + sort + ' ' + order
418
419        def namedParams = [:]
420        namedParams.taskBudgetStatus = taskBudgetStatus ?: TaskBudgetStatus.read(2) // Planned.
421        namedParams.startDate = startDate ?: dateUtilService.today
422        namedParams.endDate = endDate ?: dateUtilService.tomorrow
423
424        def baseQuery = "from Task as task \
425                                        where (task.trash = false \
426                                                    and task.taskBudgetStatus = :taskBudgetStatus \
427                                                    and task.targetStartDate < :endDate \
428                                                    and task.targetCompletionDate >= :startDate \
429                                        )"
430
431        def searchQuery = "select distinct task " + baseQuery + orderBy
432        def list = Task.executeQuery(searchQuery, namedParams, paginateParams)
433
434        def countQuery = "select count(distinct task) as taskCount " + baseQuery
435        def totalCount = Task.executeQuery(countQuery, namedParams)[0].toInteger()
436
437        def taskInstanceList = new PagedResultList(list, totalCount)
438        return taskInstanceList
439    } // getBudgetTasks()
440
441    /**
442    * Get work done by person and date.
443    * A person ID and date may be specified in params otherwise the current user and today are used.
444    * @param params The request params.
445    * @returns A map containing entries, totalEntries, startOfDay, person, totalHours, totalMinutes.
446    */
447    def getWorkDone(params, locale) {
448        def result = [:]
449        result.person = params.person?.id ? Person.get(params.person.id.toInteger()) : authService.currentUser
450
451        if(params.date_year && params.date_month && params.date_day)
452            result.startOfDay = dateUtilService.makeDate(params.date_year, params.date_month, params.date_day)
453        else
454            result.startOfDay = dateUtilService.today
455
456        result.startOfNextDay = result.startOfDay + 1
457
458        def formattedStartOfDay = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.startOfDay)
459
460        def getMessage = { Map m ->
461            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
462        }
463
464        result.entries = Entry.createCriteria().list() {
465            eq("enteredBy", result.person)
466            ge("dateDone", result.startOfDay)
467            lt("dateDone", result.startOfNextDay)
468            entryType {
469                eq("id", 3L)
470            }
471        } // createCriteria
472
473        result.totalEntries = result.entries.size()
474
475        if(result.totalEntries > 0)
476            result.message = getMessage(code:"task.search.text.work.done.message",
477                                                                args:[result.person, formattedStartOfDay])
478        else
479            result.message = getMessage(code:"task.search.text.work.done.none.found",
480                                                                args:[result.person, formattedStartOfDay])
481
482        result.totalHours = 0
483        result.totalMinutes = 0
484        result.entries.each() {
485            result.totalMinutes += (it.durationHour*60) + it.durationMinute
486        }
487        result.totalHours = (result.totalMinutes / 60).toInteger()
488        result.totalMinutes = result.totalMinutes % 60
489
490        return result
491    } // getWorkDone()
492
493    /**
494    * Get work load by task group and date.
495    * Group ID's and date range may be specified in params otherwise no group and today are used.
496    * @param params The request params.
497    * @returns A map containing the results.
498    */
499    def getWorkLoad(params, locale) {
500        def result = [:]
501        def max = 1000
502
503        // TaskStatus..
504        result.taskStatusList = []
505        if(params.taskStatusList instanceof String)
506            result.taskStatusList << TaskStatus.get(params.taskStatusList.toInteger())
507        else if(params.taskStatusList)
508            result.taskStatusList = TaskStatus.getAll( params.taskStatusList.collect {it.toInteger()} )
509
510        // TaskGroups.
511        result.taskGroups = []
512        if(params.taskGroups instanceof String)
513            result.taskGroups << TaskGroup.get(params.taskGroups.toInteger())
514        else if(params.taskGroups)
515            result.taskGroups = TaskGroup.getAll( params.taskGroups.collect {it.toInteger()} )
516
517        // Start Date.
518        if(params.startDate_year && params.startDate_month && params.startDate_day)
519            result.startDate = dateUtilService.makeDate(params.startDate_year, params.startDate_month, params.startDate_day)
520        else
521            result.startDate = dateUtilService.today
522
523        // End Date.
524        if(params.endDate_year && params.endDate_month && params.endDate_day)
525            result.endDate = dateUtilService.makeDate(params.endDate_year, params.endDate_month, params.endDate_day)
526        else
527            result.endDate = result.startDate
528
529        // Normalise date range.
530        if(result.endDate < result.startDate)
531            result.endDate = result.startDate
532
533        def formattedStartDate = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.startDate)
534        def formattedEndDate = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.endDate)
535
536        def getMessage = { Map m ->
537            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
538        }
539
540        result.tasks = new PagedResultList([], 0)
541
542        if(result.taskGroups && result.taskStatusList) {
543
544            result.tasks = Task.createCriteria().list(max: max) {
545                eq("trash", false)
546                lt("targetStartDate", result.endDate+1)
547                ge("targetCompletionDate", result.startDate)
548                inList("taskStatus", result.taskStatusList)
549                inList("taskGroup", result.taskGroups)
550                order("taskStatus", "asc")
551                order("taskPriority", "asc")
552                order("targetStartDate", "asc")
553                fetchMode("assignedGroups", FM.EAGER)
554                fetchMode("assignedGroups.personGroup", FM.EAGER)
555            } // createCriteria
556
557        }
558
559        result.tasks.list.unique()
560        result.totalHours = 0
561        result.totalMinutes = 0
562        result.workLoadGroups = [:]
563
564        // Exit early!
565        if(result.tasks.totalCount > result.tasks.size()) {
566            result.errorMessage = getMessage(code:"task.search.text.work.load.too.many.results",
567                                                                args:[result.tasks.size(), result.tasks.totalCount])
568            return result
569        }
570        else if(result.tasks.size() > 0)
571            result.message = getMessage(code:"task.search.text.work.load.message",
572                                                                args:[formattedStartDate, formattedEndDate])
573        else
574            result.message = getMessage(code:"task.search.text.work.load.none.found",
575                                                                args:[formattedStartDate, formattedEndDate])
576
577        // Collect all assignedGroups.
578        def assignedGroups = []
579        for(task in result.tasks) {
580            for(assignedGroup in task.assignedGroups) {
581                assignedGroups << assignedGroup
582            }
583        }
584
585        // Calculate work load for each personGroup and minute totals.
586        def tempHours = 0
587        def tempMinutes = 0
588        def personGroup
589        for(assignedGroup in assignedGroups) {
590            personGroup = assignedGroup.personGroup
591            if(!result.workLoadGroups.containsKey(personGroup)) {
592                result.workLoadGroups[personGroup] = [hours: 0, minutes: 0]
593            }
594
595            tempMinutes = (assignedGroup.estimatedHour*60) + assignedGroup.estimatedMinute
596            result.totalMinutes += tempMinutes
597            result.workLoadGroups[personGroup].minutes += tempMinutes
598        }
599
600        // Resolve totals and sort.
601        result.workLoadGroups.each { workLoadGroup ->
602            workLoadGroup.value.hours =  (workLoadGroup.value.minutes / 60).toInteger()
603            workLoadGroup.value.minutes = workLoadGroup.value.minutes % 60
604        }
605        result.workLoadGroups = result.workLoadGroups.sort { p1, p2 -> p1.key.name.compareToIgnoreCase(p2.key.name) }
606        result.totalHours = (result.totalMinutes / 60).toInteger()
607        result.totalMinutes = result.totalMinutes % 60
608
609        // Success.
610        return result
611    } // getWorkLoad()
612
613} // end class
Note: See TracBrowser for help on using the repository browser.