Index: trunk/grails-app/services/TaskSearchService.groovy
===================================================================
--- trunk/grails-app/services/TaskSearchService.groovy	(revision 701)
+++ trunk/grails-app/services/TaskSearchService.groovy	(revision 713)
@@ -1,2 +1,3 @@
+import net.kromhouts.HqlBuilder
 import grails.orm.PagedResultList
 import org.hibernate.FetchMode as FM
@@ -16,4 +17,14 @@
 
     def paramsMax = 100000
+
+    // Be sure to update taskQuickSearchPane.js if quickSearchSelection is changed.
+    def getQuickSearchSelection() {
+        [ 'allTasks': g.message(code: 'task.search.text.all.tasks'),
+        'budgetPlanned': g.message(code: 'task.search.text.budget.planned'),
+        'budgetUnplanned': g.message(code: 'task.search.text.budget.unplanned'),
+        'personsImmediateCallouts': g.message(code: 'task.search.text.persons.immediate.callouts'),
+        'personsTasks': g.message(code: 'task.search.text.persons.tasks')
+        ]
+    }
 
     /**
@@ -22,9 +33,29 @@
     * @param locale The locale to use when generating result.message.
     */
-    def getQuickSearch(params, locale) {
+    def getQuickSearch(params = [:], locale) {
         def result = [:]
-        result.quickSearch = params.quickSearch ?: "plannersRange"
-
-        def currentUser = authService.currentUser
+        result.quickSearch = params.quickSearch ?: "personsTasks"
+        if(params.person)
+            result.person = Person.get(params.person.id.toLong())
+        else
+            result.person = authService.currentUser
+        result.startDate = params.startDate ?: dateUtilService.today
+        result.endDate = params.endDate ?: dateUtilService.today
+        // Auto swap date range.
+        if(result.startDate > result.endDate) {
+            def tempStartDate = result.startDate
+            result.startDate = result.endDate
+            result.endDate = tempStartDate
+        }
+        result.includeCompleted = params.includeCompleted = params.includeCompleted ? true:false
+
+        def getMessage = { Map m ->
+            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
+        }
+
+        def formatted = { Date d ->
+            g.formatDate(format: "EEE, dd-MMM-yyyy", date: d)
+        }
+
         def startOfToday = dateUtilService.today
         def startOfYesterday = dateUtilService.yesterday
@@ -32,115 +63,174 @@
         def oneWeekAgo = dateUtilService.oneWeekAgo
 
-        def formattedStartOfToday = g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfToday)
-        def formattedStartOfYesterday = g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfYesterday)
-        def formattedStartOfTomorrow = g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfTomorrow)
-        def formattedOneWeekAgo = g.formatDate(format: "EEE, dd-MMM-yyyy", date: oneWeekAgo)
-
-        def getMessage = { Map m ->
-            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
+        def formattedStartOfToday = formatted(startOfToday)
+        def formattedStartOfYesterday = formatted(startOfYesterday)
+        def formattedStartOfTomorrow = formatted(startOfTomorrow)
+        def formattedOneWeekAgo = formatted(oneWeekAgo)
+
+        def allTasks = {
+            result.taskInstanceList = getTasks(params, result.startDate, result.endDate+1)
+            if(result.taskInstanceList.totalCount > 0) {
+                if(result.startDate == result.endDate)
+                    result.message = getMessage(code:"task.search.text.all.tasks.message",
+                                                                    args:[ formatted(result.startDate) ])
+                else
+                    result.message = getMessage(code:"task.search.text.all.tasks.between.message",
+                                                                    args:[ formatted(result.startDate), formatted(result.endDate) ])
+            }
+            else {
+                if(result.startDate == result.endDate)
+                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
+                                                                    args:[ formatted(result.startDate) ])
+                else
+                    result.message = getMessage(code:"task.search.text.all.tasks.between.none.found",
+                                                                    args:[ formatted(result.startDate), formatted(result.endDate) ])
+            }
+
+        }
+
+        def personsTasks = {
+            result.taskInstanceList = getPersonsTasks(params, result.person, result.startDate, result.endDate+1)
+            if(result.taskInstanceList.totalCount > 0) {
+                if(result.startDate == result.endDate)
+                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
+                                                                    args:[ result.person, formatted(result.startDate) ])
+                else
+                    result.message = getMessage(code:"task.search.text.persons.tasks.between.message",
+                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
+            }
+            else {
+                if(result.startDate == result.endDate)
+                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
+                                                                    args:[ result.person, formatted(result.startDate) ])
+                else
+                    result.message = getMessage(code:"task.search.text.persons.tasks.between.none.found",
+                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
+            }
+
+        }
+
+        def personsImmediateCallouts = {
+            result.taskInstanceList = getPersonsImmediateCallouts(params, result.person, result.startDate, result.endDate+1)
+            if(result.taskInstanceList.totalCount > 0) {
+                if(result.startDate == result.endDate)
+                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.message",
+                                                                    args:[ result.person, formatted(result.startDate) ])
+                else
+                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.between.message",
+                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
+            }
+            else {
+                if(result.startDate == result.endDate)
+                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.none.found",
+                                                                    args:[ result.person, formatted(result.startDate) ])
+                else
+                    result.message = getMessage(code:"task.search.text.persons.immediate.callouts.between.none.found",
+                                                                    args:[ result.person, formatted(result.startDate), formatted(result.endDate) ])
+            }
+
         }
 
         switch (result.quickSearch) {
             case "myTodays":
-                result.taskInstanceList = getPersonsTasks(params)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
-                                                                    args:[currentUser, formattedStartOfToday])
-                else
-                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
-                                                                    args:[currentUser, formattedStartOfToday])
+                result.quickSearch = "personsTasks"
+                result.startDate = startOfToday
+                result.endDate = startOfToday
+                personsTasks()
                 break
             case "myYesterdays":
-                result.taskInstanceList = getPersonsTasks(params, currentUser, startOfYesterday, startOfToday)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
-                                                                    args:[currentUser, formattedStartOfYesterday])
-                else
-                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
-                                                                    args:[currentUser, formattedStartOfYesterday])
+                result.quickSearch = "personsTasks"
+                result.startDate = startOfYesterday
+                result.endDate = startOfYesterday
+                personsTasks()
                 break
             case "myTomorrows":
-                result.taskInstanceList = getPersonsTasks(params, currentUser, startOfTomorrow, startOfTomorrow+1)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.persons.tasks.message",
-                                                                    args:[currentUser, formattedStartOfTomorrow])
-                else
-                    result.message = getMessage(code:"task.search.text.persons.tasks.none.found",
-                                                                    args:[currentUser, formattedStartOfTomorrow])
+                result.quickSearch = "personsTasks"
+                result.startDate = startOfTomorrow
+                result.endDate = startOfTomorrow
+                personsTasks()
                 break
             case "myPastWeek":
-                result.taskInstanceList = getPersonsTasks(params, currentUser, oneWeekAgo, startOfTomorrow)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.persons.tasks.between.message",
-                                                                    args:[currentUser, formattedOneWeekAgo, formattedStartOfToday])
-                else
-                    result.message = getMessage(code:"task.search.text.persons.tasks.between.none.found",
-                                                                    args:[currentUser, formattedOneWeekAgo, formattedStartOfToday])
+                result.quickSearch = "personsTasks"
+                result.startDate = oneWeekAgo
+                result.endDate = startOfToday
+                personsTasks()
+                break
+            case "personsTasks":
+                personsTasks()
+                break
+            case "personsImmediateCallouts":
+                personsImmediateCallouts()
                 break
             case "todays":
-                result.taskInstanceList = getTasks(params)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.all.tasks.message",
-                                                                    args:[formattedStartOfToday])
-                else
-                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
-                                                                    args:[formattedStartOfToday])
+                result.quickSearch = "allTasks"
+                result.startDate = startOfToday
+                result.endDate = startOfToday
+                allTasks()
                 break
             case "yesterdays":
-                result.taskInstanceList = getTasks(params, startOfYesterday, startOfToday)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.all.tasks.message",
-                                                                    args:[formattedStartOfYesterday])
-                else
-                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
-                                                                    args:[formattedStartOfYesterday])
+                result.quickSearch = "allTasks"
+                result.startDate = startOfYesterday
+                result.endDate = startOfToday
+                allTasks()
                 break
             case "tomorrows":
-                result.taskInstanceList = getTasks(params, startOfTomorrow, startOfTomorrow+1)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.all.tasks.message",
-                                                                    args:[formattedStartOfTomorrow])
-                else
-                    result.message = getMessage(code:"task.search.text.all.tasks.none.found",
-                                                                    args:[formattedStartOfTomorrow])
+                result.quickSearch = "allTasks"
+                result.startDate = startOfTomorrow
+                result.endDate = startOfTomorrow+1
+                allTasks()
                 break
             case "pastWeek":
-                result.taskInstanceList = getTasks(params, oneWeekAgo, startOfTomorrow)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.all.tasks.between.message",
-                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
-                else
-                    result.message = getMessage(code:"task.search.text.all.tasks.between.none.found",
-                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
+                result.quickSearch = "allTasks"
+                result.startDate = oneWeekAgo
+                result.endDate = startOfTomorrow
+                allTasks()
+                break
+            case "allTasks":
+                allTasks()
                 break
             case "budgetUnplanned":
-                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(1), oneWeekAgo, startOfTomorrow)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.budget.unplanned.message",
-                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
-                else
-                    result.message = getMessage(code:"task.search.text.budget.unplanned.none.found",
-                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
+                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(1), result.startDate, result.endDate+1)
+                if(result.taskInstanceList.totalCount > 0) {
+                    if(result.startDate == result.endDate)
+                        result.message = getMessage(code:"task.search.text.budget.unplanned.message",
+                                                                        args:[ formatted(result.startDate) ])
+                    else
+                        result.message = getMessage(code:"task.search.text.budget.unplanned.between.message",
+                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
+                }
+                else {
+                    if(result.startDate == result.endDate)
+                        result.message = getMessage(code:"task.search.text.budget.unplanned.none.found",
+                                                                        args:[ formatted(result.startDate) ])
+                    else
+                        result.message = getMessage(code:"task.search.text.budget.unplanned.between.none.found",
+                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
+                }
                 break
             case "budgetPlanned":
-                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(2), oneWeekAgo, startOfTomorrow)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.budget.planned.message",
-                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
-                else
-                    result.message = getMessage(code:"task.search.text.budget.planned.none.found",
-                                                                    args:[formattedOneWeekAgo, formattedStartOfToday])
+                result.taskInstanceList = getBudgetTasks(params, TaskBudgetStatus.read(2), result.startDate, result.endDate+1)
+                if(result.taskInstanceList.totalCount > 0) {
+                    if(result.startDate == result.endDate)
+                        result.message = getMessage(code:"task.search.text.budget.planned.message",
+                                                                        args:[ formatted(result.startDate) ])
+                    else
+                        result.message = getMessage(code:"task.search.text.budget.planned.between.message",
+                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
+                }
+                else {
+                    if(result.startDate == result.endDate)
+                        result.message = getMessage(code:"task.search.text.budget.planned.none.found",
+                                                                        args:[ formatted(result.startDate) ])
+                    else
+                        result.message = getMessage(code:"task.search.text.budget.planned.between.none.found",
+                                                                        args:[ formatted(result.startDate), formatted(result.endDate) ])
+                }
                 break
             default:
                 //case "plannersRange":
-                result.taskInstanceList = getTasks(params, oneWeekAgo, startOfToday+15)
-                if(result.taskInstanceList.totalCount > 0)
-                    result.message = getMessage(code:"task.search.text.all.tasks.between.message",
-                                                                    args:[formattedOneWeekAgo,
-                                                                            g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfToday+14)])
-                else
-                    result.message = getMessage(code:"task.search.text.all.tasks.between.none.found",
-                                                                    args:[formattedOneWeekAgo,
-                                                                            g.formatDate(format: "EEE, dd-MMM-yyyy", date: startOfToday+14)])
+                result.quickSearch = "allTasks"
+                result.startDate = oneWeekAgo
+                result.endDate = startOfToday+15
+                allTasks()
                 break
         } // switch.
@@ -202,7 +292,7 @@
     */
     def getPersonsTasks(params, person=null, startDate=null, endDate=null) {
-        def paginateParams = [:]
-        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
-        paginateParams.offset = params?.offset?.toInteger() ?: 0
+
+        def max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
+        def offset = params?.offset?.toInteger() ?: 0
 
         def orderBy = ''
@@ -212,50 +302,102 @@
             def sort = "task." + params.sort
             def order = (params.order == "asc") ? "asc" : "desc"
-            orderBy = " order by " + sort + ' ' + order
-        }
-        else
-            orderBy = " order by task.taskStatus, task.taskPriority, task.targetStartDate"
-
-        def namedParams = [:]
-        namedParams.person = person ?: authService.currentUser
-        namedParams.startDate = startDate ?: dateUtilService.today
-        namedParams.endDate = endDate ?: dateUtilService.tomorrow
-
-        def baseQuery = "from Task as task \
-                                        left join task.assignedPersons as assignedPersonOfTask \
-                                        left join assignedPersonOfTask.person as assignedPerson \
-                                        left join task.assignedGroups as assignedGroupOfTask \
-                                        left join assignedGroupOfTask.personGroup as personGroup \
-                                        left join personGroup.persons as assignedPersonViaGroup \
-                                        left join task.taskModifications as taskModification \
-                                        left join taskModification.person as createdBy \
-                                        left join taskModification.taskModificationType as taskModificationType \
-                                        where (task.trash = false \
-                                                    and task.targetStartDate < :endDate \
-                                                    and task.targetCompletionDate >= :startDate \
-                                                    and ( \
-                                                        (taskModificationType.id = 1 \
-                                                        and createdBy = :person \
-                                                        and task.leadPerson = :person) \
-                                                        or ( \
-                                                            task.approved = true \
-                                                            and ( \
-                                                                task.leadPerson = :person \
-                                                                or assignedPerson = :person \
-                                                                or assignedPersonViaGroup = :person \
-                                                            ) \
-                                                        ) \
-                                                    ) \
-                                        )"
-
-        def searchQuery = "select distinct task " + baseQuery + orderBy
-        def list = Task.executeQuery(searchQuery, namedParams, paginateParams)
-
-        def countQuery = "select count(distinct task) as taskCount " + baseQuery
-        def totalCount = Task.executeQuery(countQuery, namedParams)[0].toInteger()
+            orderBy = "by " + sort + ' ' + order
+        }
+        else
+            orderBy = "by task.taskStatus, task.taskPriority, task.targetStartDate"
+
+        def q = new HqlBuilder().query {
+
+            select 'count(distinct task) as taskCount'
+            from 'Task as task',
+                    'left join task.assignedPersons as assignedPersonOfTask',
+                    'left join assignedPersonOfTask.person as assignedPerson',
+                    'left join task.assignedGroups as assignedGroupOfTask',
+                    'left join assignedGroupOfTask.personGroup as personGroup',
+                    'left join personGroup.persons as assignedPersonViaGroup',
+                    'left join task.taskModifications as taskModification',
+                    'left join taskModification.person as createdBy',
+                    'left join taskModification.taskModificationType as taskModificationType'
+            where 'task.trash = false'
+                    and 'task.targetStartDate < :endDate'
+                    and 'task.targetCompletionDate >= :startDate'
+                    if(!params.includeCompleted) {
+                        and 'task.taskStatus.id != 3' // Complete.
+                    }
+                    and {
+                        where '(taskModificationType.id = 1 and createdBy = :person and task.leadPerson = :person)' // Created.
+                        or '(task.approved = true and (task.leadPerson = :person or assignedPerson = :person or assignedPersonViaGroup = :person))'
+                    }
+        }
+
+        q.namedParams.person = person ?: authService.currentUser
+        q.namedParams.startDate = startDate ?: dateUtilService.today
+        q.namedParams.endDate = endDate ?: dateUtilService.tomorrow
+
+        def totalCount = Task.executeQuery(q.query, q.namedParams)[0].toInteger()
+
+        q.select = "distinct task"
+        q.order = orderBy
+        def list = Task.executeQuery(q.query, q.namedParams, q.paginateParams)
 
         def taskInstanceList = new PagedResultList(list, totalCount)
         return taskInstanceList
     } // getPersonsTasks()
+
+    /**
+    * Get a person's immediateCallout tasks, by default all users and today's tasks.
+    * @param params The request params.
+    * @param person The person to get tasks for, defaults to null and therefore all immediateCallouts.
+    * @param startDate The start date to get tasks for, defaults to the start of today and is inclusive (greater than or equal to).
+    * @param endDate The end date to get tasks for, defaults to the start of tomorrow and is exclusive (less than).
+    */
+    def getPersonsImmediateCallouts(params, person=null, startDate=null, endDate=null) {
+
+        def max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
+        def offset = params?.offset?.toInteger() ?: 0
+
+        def orderBy = ''
+        if(params.sort?.contains('.')) // protect against filterpane bug.
+            params.sort = null
+        if(params.sort && params.order) {
+            def sort = "task." + params.sort
+            def order = (params.order == "asc") ? "asc" : "desc"
+            orderBy = "by " + sort + ' ' + order
+        }
+        else
+            orderBy = "by task.taskStatus, task.taskPriority, task.targetStartDate"
+
+        def q = new HqlBuilder().query {
+
+            select 'count(distinct task) as taskCount'
+            from 'Task as task',
+                    'left join task.taskModifications as taskModification',
+                    'left join taskModification.person as createdBy',
+                    'left join taskModification.taskModificationType as taskModificationType'
+            where 'task.taskType.id = 1' // Immediate Callout.
+                    and 'task.targetStartDate < :endDate'
+                    and 'task.targetCompletionDate >= :startDate'
+                    if(!params.includeCompleted) {
+                        and 'task.taskStatus.id != 3' // Complete.
+                    }
+                    if(person) {
+                        namedParams.person = person
+                        and '( (taskModificationType.id = 1 and createdBy = :person) or task.leadPerson = :person)' // Created or Lead Person.
+                    }
+                    and 'task.trash = false'
+        }
+
+        q.namedParams.startDate = startDate ?: dateUtilService.today
+        q.namedParams.endDate = endDate ?: dateUtilService.tomorrow
+
+        def totalCount = Task.executeQuery(q.query, q.namedParams)[0].toInteger()
+
+        q.select = "distinct task"
+        q.order = orderBy
+        def list = Task.executeQuery(q.query, q.namedParams, q.paginateParams)
+
+        def taskInstanceList = new PagedResultList(list, totalCount)
+        return taskInstanceList
+    } // getPersonsImmediateCallouts()
 
     /**
