Index: /trunk/grails-app/controllers/TaskDetailedController.groovy
===================================================================
--- /trunk/grails-app/controllers/TaskDetailedController.groovy	(revision 700)
+++ /trunk/grails-app/controllers/TaskDetailedController.groovy	(revision 701)
@@ -835,4 +835,24 @@
                     totalMinutes: result.totalMinutes]
     } // workDone
+
+    /**
+    * Render work load hours.
+    */
+    @Secured(['ROLE_AppAdmin', 'ROLE_Manager', 'ROLE_TaskManager', 'ROLE_TaskUser'])
+    def workLoad= {
+        def result = taskSearchService.getWorkLoad(params, RCU.getLocale(request))
+
+        params.message = result.message
+        params.errorMessage = result.errorMessage
+
+        return[tasks: result.tasks,
+                    startDate: result.startDate,
+                    endDate: result.endDate,
+                    taskGroups: result.taskGroups,
+                    taskStatusList: result.taskStatusList,
+                    workLoadGroups: result.workLoadGroups,
+                    totalHours: result.totalHours,
+                    totalMinutes: result.totalMinutes]
+    } // workLoad
 
     /**
Index: /trunk/grails-app/i18n/messages.properties
===================================================================
--- /trunk/grails-app/i18n/messages.properties	(revision 700)
+++ /trunk/grails-app/i18n/messages.properties	(revision 701)
@@ -406,4 +406,9 @@
 task.search.text.work.done.message=Work done by {0} on {1}.
 task.search.text.work.done.none.found=No entries found for {0} on {1}.
+task.search.text.work.load=Work Load
+task.search.text.work.load.description=Work load by group and date.
+task.search.text.work.load.message=Work load between {0} and {1}.
+task.search.text.work.load.none.found=No tasks found between {0} and {1}.
+task.search.text.work.load.too.many.results=Too many tasks found to perform calculatation, {0} / {1} shown.
 
 #
Index: /trunk/grails-app/services/TaskSearchService.groovy
===================================================================
--- /trunk/grails-app/services/TaskSearchService.groovy	(revision 700)
+++ /trunk/grails-app/services/TaskSearchService.groovy	(revision 701)
@@ -1,3 +1,4 @@
 import grails.orm.PagedResultList
+import org.hibernate.FetchMode as FM
 
 /**
@@ -348,3 +349,123 @@
     } // getWorkDone()
 
+    /**
+    * Get work load by task group and date.
+    * Group ID's and date range may be specified in params otherwise no group and today are used.
+    * @param params The request params.
+    * @returns A map containing the results.
+    */
+    def getWorkLoad(params, locale) {
+        def result = [:]
+        def max = 1000
+
+        // TaskStatus..
+        result.taskStatusList = []
+        if(params.taskStatusList instanceof String)
+            result.taskStatusList << TaskStatus.get(params.taskStatusList.toInteger())
+        else if(params.taskStatusList)
+            result.taskStatusList = TaskStatus.getAll( params.taskStatusList.collect {it.toInteger()} )
+
+        // TaskGroups.
+        result.taskGroups = []
+        if(params.taskGroups instanceof String)
+            result.taskGroups << TaskGroup.get(params.taskGroups.toInteger())
+        else if(params.taskGroups)
+            result.taskGroups = TaskGroup.getAll( params.taskGroups.collect {it.toInteger()} )
+
+        // Start Date.
+        if(params.startDate_year && params.startDate_month && params.startDate_day)
+            result.startDate = dateUtilService.makeDate(params.startDate_year, params.startDate_month, params.startDate_day)
+        else
+            result.startDate = dateUtilService.today
+
+        // End Date.
+        if(params.endDate_year && params.endDate_month && params.endDate_day)
+            result.endDate = dateUtilService.makeDate(params.endDate_year, params.endDate_month, params.endDate_day)
+        else
+            result.endDate = result.startDate
+
+        // Normalise date range.
+        if(result.endDate < result.startDate)
+            result.endDate = result.startDate
+
+        def formattedStartDate = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.startDate)
+        def formattedEndDate = g.formatDate(format: "EEE, dd-MMM-yyyy", date: result.endDate)
+
+        def getMessage = { Map m ->
+            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
+        }
+
+        result.tasks = new PagedResultList([], 0)
+
+        if(result.taskGroups && result.taskStatusList) {
+
+            result.tasks = Task.createCriteria().list(max: max) {
+                eq("trash", false)
+                lt("targetStartDate", result.endDate+1)
+                ge("targetCompletionDate", result.startDate)
+                inList("taskStatus", result.taskStatusList)
+                inList("taskGroup", result.taskGroups)
+                order("taskStatus", "asc")
+                order("taskPriority", "asc")
+                order("targetStartDate", "asc")
+                fetchMode("assignedGroups", FM.EAGER)
+                fetchMode("assignedGroups.personGroup", FM.EAGER)
+            } // createCriteria
+
+        }
+
+        result.tasks.list.unique()
+        result.totalHours = 0
+        result.totalMinutes = 0
+        result.workLoadGroups = [:]
+
+        // Exit early!
+        if(result.tasks.totalCount > result.tasks.size()) {
+            result.errorMessage = getMessage(code:"task.search.text.work.load.too.many.results",
+                                                                args:[result.tasks.size(), result.tasks.totalCount])
+            return result
+        }
+        else if(result.tasks.size() > 0)
+            result.message = getMessage(code:"task.search.text.work.load.message",
+                                                                args:[formattedStartDate, formattedEndDate])
+        else
+            result.message = getMessage(code:"task.search.text.work.load.none.found",
+                                                                args:[formattedStartDate, formattedEndDate])
+
+        // Collect all assignedGroups.
+        def assignedGroups = []
+        for(task in result.tasks) {
+            for(assignedGroup in task.assignedGroups) {
+                assignedGroups << assignedGroup
+            }
+        }
+
+        // Calculate work load for each personGroup and minute totals.
+        def tempHours = 0
+        def tempMinutes = 0
+        def personGroup
+        for(assignedGroup in assignedGroups) {
+            personGroup = assignedGroup.personGroup
+            if(!result.workLoadGroups.containsKey(personGroup)) {
+                result.workLoadGroups[personGroup] = [hours: 0, minutes: 0]
+            }
+
+            tempMinutes = (assignedGroup.estimatedHour*60) + assignedGroup.estimatedMinute
+            result.totalMinutes += tempMinutes
+            result.workLoadGroups[personGroup].minutes += tempMinutes
+        }
+
+        // Resolve totals and sort.
+        result.workLoadGroups.each { workLoadGroup ->
+            workLoadGroup.value.hours =  (workLoadGroup.value.minutes / 60).toInteger()
+            workLoadGroup.value.minutes = workLoadGroup.value.minutes % 60
+        }
+        result.workLoadGroups = result.workLoadGroups.sort { p1, p2 -> p1.key.name.compareToIgnoreCase(p2.key.name) }
+        result.totalHours = (result.totalMinutes / 60).toInteger()
+        result.totalMinutes = result.totalMinutes % 60
+
+        // Success.
+        return result
+    } // getWorkLoad()
+
 } // end class
Index: /trunk/grails-app/views/taskDetailed/_quickSearchPane.gsp
===================================================================
--- /trunk/grails-app/views/taskDetailed/_quickSearchPane.gsp	(revision 700)
+++ /trunk/grails-app/views/taskDetailed/_quickSearchPane.gsp	(revision 701)
@@ -103,4 +103,8 @@
                         </g:link> - <g:message code="task.search.text.work.done.description" />
                         <br />
+                        <g:link controller="taskDetailed"
+                                        action="workLoad">
+                                        <g:message code="task.search.text.work.load" />
+                        </g:link> - <g:message code="task.search.text.work.load.description" />
                     </td>
                 </tr>
Index: /trunk/grails-app/views/taskDetailed/workLoad.gsp
===================================================================
--- /trunk/grails-app/views/taskDetailed/workLoad.gsp	(revision 701)
+++ /trunk/grails-app/views/taskDetailed/workLoad.gsp	(revision 701)
@@ -0,0 +1,226 @@
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+        <meta name="layout" content="main" />
+        <title>Work Load</title>
+        <resource:dateChooser />
+    </head>
+    <body>
+        <div class="nav">
+            <h1>Work Load</h1>
+        </div>
+        <div class="body">
+            <g:render template="/shared/messages" />
+
+            <div class="paginateButtons">
+                <span class="searchButtons">
+                    <a href='' onclick="showElement('searchPane'); return false;">Search</a>
+                </span>
+            </div>
+
+            <br />
+
+
+            <g:if test="${workLoadGroups.size() > 0}">
+                <h1>Assigned Groups</h1>
+            </g:if>
+            <g:else>
+                <h1>No Assigned Groups Found</h1>
+            </g:else>
+                    <div class="list">
+                        <table>
+                            <thead>
+                                <tr>
+                                    <th>Assigned Group</th>
+                                    <th>Duration</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <g:each in="${workLoadGroups}" status="i" var="workLoadGroup">
+                                        <tr class="${(i % 2) == 0 ? 'clickableOdd' : 'clickableEven'}"/>
+
+                                            <td class="notClickable">
+                                                ${workLoadGroup.key}
+                                            </td>
+
+                                            <td class="notClickable">
+                                                ${workLoadGroup.value.hours}:<g:formatNumber number="${workLoadGroup.value.minutes}" type="number" minIntegerDigits="2" />
+                                            </td>
+
+                                        </tr>
+                                </g:each>
+                            </tbody>
+                                <tr class="total">
+                                    <td>
+                                        Total
+                                    </td>
+                                    <td>
+                                        ${totalHours}:<g:formatNumber number="${totalMinutes}" type="number" minIntegerDigits="2" />
+                                    </td>
+                                </tr>
+                        </table>
+                    </div>
+
+            <br />
+
+            <h1>Task Results: ${tasks.size()} / ${tasks.totalCount}</h1>
+
+            <g:if test="${tasks.size() > 0}">
+                <div class="list">
+                    <table>
+                        <thead>
+                            <tr>
+
+                                <th>
+                                        <img  src="${resource(dir:'images/skin',file:'flag_red.png')}" alt="Flag" />
+                                </th>
+
+                                <th>Target Start Date</th>
+
+                                <th>Description</th>
+
+                                <th>Group</th>
+
+                                <th>Type</th>
+
+                                <th>Priority</th>
+
+                                <th>Status</th>
+
+                                <th></th>
+
+                            </tr>
+                        </thead>
+                        <tbody>
+                        <g:each in="${tasks}" status="i" var="taskInstance">
+                        <tr class="${(i % 2) == 0 ? 'clickableOdd' : 'clickableEven'}"/>
+
+                                <td class="idColumn" onclick='window.location = "${request.getContextPath()}/taskDetailed/show/${taskInstance.id}"'>
+                                    <g:if test="${taskInstance.attentionFlag}">
+                                        <img  src="${resource(dir:'images/skin',file:'flag_red.png')}" alt="Flag" />
+                                    </g:if>
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/taskDetailed/show/${taskInstance.id}"'>
+                                    <g:formatDate date="${taskInstance.targetStartDate}" format="EEE, dd-MMM-yyyy"/>
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/taskDetailed/show/${taskInstance.id}"'>
+                                    <b>
+                                        Task #${fieldValue(bean:taskInstance, field:'id')}<br />
+                                        ${fieldValue(bean:taskInstance, field:'description')}
+                                    </b>
+                                    <br />
+                                    <br />
+                                    <g:if test="${taskInstance.primaryAsset}">
+                                        ${fieldValue(bean:taskInstance, field:'primaryAsset')}<br />
+                                    </g:if>
+                                    <g:each in="${taskInstance.assignedGroups}" status="j" var="assignedGroup">
+                                        ${assignedGroup.encodeAsHTML()}<br />
+                                    </g:each>
+                                    <g:each in="${taskInstance.assignedPersons}" status="k" var="assignedPerson">
+                                        ${assignedPerson.encodeAsHTML()}<br />
+                                    </g:each>
+                                    <g:each in="${taskInstance.entries}" status="m" var="entry">
+                                        <g:if test="${entry.entryType.id == 3}">
+                                            <em>${'WD: ' +entry.toShortString().encodeAsHTML()}</em><br />
+                                        </g:if>
+                                    </g:each>
+                                    <g:if test="${taskInstance.subTasks}">
+                                        Sub Tasks: ${taskInstance.subTasks.size()}<br />
+                                    </g:if>
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/taskDetailed/show/${taskInstance.id}"'>
+                                    ${fieldValue(bean:taskInstance, field:'taskGroup')}
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/taskDetailed/show/${taskInstance.id}"'>
+                                    ${fieldValue(bean:taskInstance, field:'taskType')}
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/taskDetailed/show/${taskInstance.id}"'>
+                                    ${fieldValue(bean:taskInstance, field:'taskPriority')}
+                                </td>
+
+                                <td onclick='window.location = "${request.getContextPath()}/taskDetailed/show/${taskInstance.id}"'>
+                                    ${fieldValue(bean:taskInstance, field:'taskStatus')}
+                                </td>
+
+                                <td class="notClickable">
+                                    <g:link action="show" id="${taskInstance.id}">
+                                        <img  src="${resource(dir:'images/skin',file:'database_go.png')}" alt="Show" />
+                                    </g:link>
+                                </td>
+
+                            </tr>
+                        </g:each>
+                        </tbody>
+                    </table>
+                </div>
+            </g:if>
+
+            <!-- Start Search Pane -->
+            <div class="overlayPane" id="searchPane" style="display:none;">
+                <h2>Search</h2>
+                <g:form method="post" controller="taskDetailed">
+                    <table>
+                        <tbody>
+
+                            <tr class="prop">
+                                <td valign="top" class="name">
+                                    <label for="date">Date:</label>
+                                </td>
+                                <td valign="top" class="value">
+                                    <richui:dateChooser name="startDate" format="dd-MM-yyyy" value="${startDate}" />
+                                    to
+                                    <richui:dateChooser name="endDate" format="dd-MM-yyyy" value="${endDate}" />
+                                </td>
+                            </tr>
+
+                            <tr class="prop">
+                                <td valign="top" class="name">
+                                    <label>Task Status:</label>
+                                </td>
+                                <td valign="top" class="value">
+                                    <custom:checkBoxList name="taskStatusList"
+                                                                            from="${TaskStatus.findAllByIsActive(true)}"
+                                                                            value="${taskStatusList?.collect{it.id}}"
+                                                                            optionKey="id"
+                                                                            sortBy="name"
+                                                                            linkController="taskStatusDetailed"
+                                                                            linkAction="show"
+                                                                            height="150px"/>
+                                </td>
+                            </tr>
+
+                            <tr class="prop">
+                                <td valign="top" class="name">
+                                    <label>Task Groups:</label>
+                                </td>
+                                <td valign="top" class="value">
+                                    <custom:checkBoxList name="taskGroups"
+                                                                            from="${TaskGroup.findAllByIsActive(true)}"
+                                                                            value="${taskGroups?.collect{it.id}}"
+                                                                            optionKey="id"
+                                                                            sortBy="name"
+                                                                            linkController="taskGroupDetailed"
+                                                                            linkAction="show"
+                                                                            height="150px"/>
+                                </td>
+                            </tr>
+
+                        </tbody>
+                    </table>
+                    <div class="buttons">
+                        <span class="button">
+                            <g:actionSubmit class="save" value="Update" action="workLoad" />
+                            <g:actionSubmit class="cancel" value="${g.message(code:'fp.tag.filterPane.button.cancel.text', default:'Cancel')}" onclick="return hideElement('searchPane');" />
+                        </span>
+                    </div>
+                </g:form>
+            </div> <!-- end search pane -->
+
+        </div> <!--body-->
+    </body>
+</html>
