import grails.util.Environment

/**
* Provides a service class for the Task domain class.
*
*/
class TaskService {

    boolean transactional = false

    def authService
    def dateUtilService
    def assignedGroupService
    def assignedPersonService

    /**
    * Determines and returns a possible parent list for a task.
    * @todo Create and use another method that limits the results to say the latest 20 or 100 tasks?
    * @param taskInstance The task to use when determining the possible parent list.
    * @returns A list of the possible parents.
    */
    def possibleParentList(taskInstance) {
        def criteria = taskInstance.createCriteria()
        def possibleParentList = criteria {
            and {
                notEqual('trash', true)
                notEqual('id', taskInstance.id)
                taskInstance.subTasks.each() { notEqual('id', it.id) }
                }
        }
    }

    /**
    * Determines and returns a list of possible task types for scheduled tasks.
    * @returns A list of the possible task types.
    */
    def getScheduledTaskTypes() {
        def criteria = TaskType.createCriteria()
        def scheduledTaskTypes = criteria {
            and {
                eq('isActive', true)
                gt('id', 2L)
                }
        }
    }

    /**
    * Determines and returns a list of possible task priorites for Scheduled tasks.
    * @returns A list of the possible task priorites.
    */
    def getScheduledTaskPriorities() {
        def criteria = TaskPriority.createCriteria()
        def scheduledTaskPriorities = [:]
        scheduledTaskPriorities.list = criteria {
            and {
                eq('isActive', true)
                gt('id', 1L)
                }
        }
        scheduledTaskPriorities.default = scheduledTaskPriorities.list.find { it.id == 4L } //  1-Normal.
        return scheduledTaskPriorities
    }

    /**
    * Determines and returns a list of possible task priorites for Unscheduled tasks.
    * @returns A map containing a list of the possible task priorites and the default priority.
    */
    def getUnscheduledTaskPriorities() {
        def criteria = TaskPriority.createCriteria()
        def unscheduledTaskPriorities = [:]
        unscheduledTaskPriorities.list = criteria {
            and {
                eq('isActive', true)
                lt('id', 5L)
                ne('id', 1L)
            }
        }
        unscheduledTaskPriorities.default = unscheduledTaskPriorities.list.find { it.id == 3L } // 2-High.
        return unscheduledTaskPriorities
    }

    /**
    * Creates a new task with the given params.
    * @param params The params to use when creating the new task.
    * @returns A map containing result.error (if any error) and result.taskInstance.
    */
    def save(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            def taskInstance = new Task(params)
            result.taskInstance = taskInstance

            // Set taskStatus if not supplied.
            if(!params.taskStatus)
                taskInstance.taskStatus = TaskStatus.read(1) // Not Started

            // Set budgetStatus if not supplied.
            if(!params.taskBudgetStatus) {
                if(taskInstance.taskType?.id == 1 || taskInstance.taskType?.id == 2) // Immediate Callout or Unsheduled Breakin.
                    taskInstance.taskBudgetStatus = TaskBudgetStatus.read(1) // Unplanned.
                else
                    taskInstance.taskBudgetStatus = TaskBudgetStatus.read(2) // Planned.
            }

            if(result.taskInstance.parentTask?.trash)
                return fail(field:"parentTask", code:"task.operationNotPermittedOnTaskInTrash")

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.create.failure")

            def taskModification = new TaskModification(person: authService.currentUser,
                                                taskModificationType: TaskModificationType.get(1),
                                                task: taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(field:"taskModifications", code:"task.modifications.failedToSave")

            //Add the assignedGroups, provided by a new ArrayList(task.assignedGroups)
            if(params.assignedGroups) {
                def assignedGroupsResult
                def assignedGroupParams = [:]
                params.assignedGroups.each() {

                    assignedGroupParams = [personGroup: it.personGroup,
                                                                task: taskInstance,
                                                                estimatedHour: it.estimatedHour,
                                                                estimatedMinute: it.estimatedMinute]

                    assignedGroupsResult = assignedGroupService.save(assignedGroupParams)

                    if(assignedGroupsResult.error)
                        return fail(field:"assignedGroups", code:"task.assignedGroups.failedToSave")

                }
            }

            //Add the assignedPersons, provided by a new ArrayList(task.assignedPersons)
            if(params.assignedPersons) {
                def assignedPersonsResult
                def assignedPersonsParams = [:]
                params.assignedPersons.each() {

                    assignedPersonsParams = [person: it.person,
                                                                task: taskInstance,
                                                                estimatedHour: it.estimatedHour,
                                                                estimatedMinute: it.estimatedMinute]

                    assignedPersonsResult = assignedPersonService.save(assignedPersonsParams)

                    if(assignedPersonsResult.error)
                        return fail(field:"assignedPersons", code:"task.assignedPersons.failedToSave")

                }
            }

            // Success.
            return result

        } //end withTransaction
    } // end save()

    /**
    * Creates a subTask copying sane attributes from the parentTask unless otherwise specified in params.
    * The targetStartDate and targetCompletionDate default to today since that is the sane thing to do.
    * The taskProcedure is only assigned to the sub task if supplied in params.
    * The assignedPersons and assignedGroups are only added to the sub task if supplied in params.
    * The positiveFault property is never set on the subTask.
    * Collections in params must be supplied as new ArrayList's.
    * This method is not intended to be a copyTask method.
    * There should be no reason to copy tasks, recurrence can be used to create similar tasks.
    * @param parentTask The parent task to get attributes from, also set as the parent.
    * @param params Overrides the parent task values if specified.
    * @returns A map containing result.error=true (if any error) and result.taskInstance.
    */
    def createSubTask(parentTask, params = [:]) {

        def result = [:]

        //Make our new Task a subTask and set the required properties.
        def p = [:]
        p.parentTask = parentTask
        p.description = params.description ?: parentTask.description
        p.comment = params.comment ?: parentTask.comment
        p.targetStartDate = params.targetStartDate ?: dateUtilService.today
        p.targetCompletionDate = params.targetCompletionDate ?: dateUtilService.today

        p.safetyRequirement = params.safetyRequirement ?: parentTask.safetyRequirement
        p.mandatoryRegulatory = params.mandatoryRegulatory ?: parentTask.mandatoryRegulatory

        p.taskGroup = params.taskGroup ?: parentTask.taskGroup
        p.taskStatus = TaskStatus.get(1) // A new subTask must always be "Not Started".
        p.taskPriority = parentTask.taskPriority
        p.taskType = params.taskType ?: parentTask.taskType
        p.leadPerson = params.leadPerson ?: parentTask.leadPerson
        p.primaryAsset = params.primaryAsset ?: parentTask.primaryAsset
        p.associatedAssets = params.associatedAssets ?: new ArrayList(parentTask.associatedAssets) // Collection.

        // Supplied by recurring tasks.
        if(params.taskProcedure) p.taskProcedure = params.taskProcedure
        if(params.assignedGroups) p.assignedGroups = params.assignedGroups // Collection.
        if(params.assignedPersons) p.assignedPersons = params.assignedPersons // Collection.

        // trash: A new subTask must always have trash=false, which is already the domain class default.

        // These would be considered copying, hence not done.
        // taskRecurringSchedule, entries, taskModifications, subTasks, inventoryMovements.

        // Create the sub task and return the result.
        result = save(p)

        // Approve.
        if(!result.error && parentTask.approved) {
            p = [:]
            p.id = result.taskInstance.id
            approve(p)
        }

        // Success.
        return result

    } // end createSubTask()

    /**
    * In production tasks are NEVER deleted, only the trash flag is set!
    * However during testing it may be required to delete a task and that
    * is why this method exists.
    */
    def delete(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            if(Environment.current == Environment.PRODUCTION)
                return fail(code:"task.delete.failure.production")

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Handle taskModifications.
            def taskModifications = TaskModification.findAllByTask(result.taskInstance)
            taskModifications.each() {
                result.taskInstance.removeFromTaskModifications(it)
                it.delete()
            }

            // Handle assignedPersons.
            def taskAssignedPersons = AssignedPerson.findAllByTask(result.taskInstance)
            taskAssignedPersons.each() {
                result.taskInstance.removeFromAssignedPersons(it)
                it.delete()
            }

            // Handle assignedGroups.
            def taskAssignedGroups = AssignedGroup.findAllByTask(result.taskInstance)
            taskAssignedGroups.each() {
                result.taskInstance.removeFromAssignedGroups(it)
                it.delete()
            }

            if(result.error)
                return result

            try {
                result.taskInstance.delete(flush:true)
                return result //Success.
            }
            catch(org.springframework.dao.DataIntegrityViolationException e) {
                return fail(code:"default.delete.failure")
            }

        } // end withTransaction
    } // delete()

    /**
    * Creates a new task entry.
    * @param params The params to use when creating the new entry.
    * @returns A map containing result.error=true (if any error), result.entryInstance and result.taskId.
    */
    def saveEntry(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Entry", params.id] ]
                return result
            }

            result.entryInstance = new Entry(params)
            result.entryInstance.enteredBy = authService.currentUser

            def taskInstance
            if(result.entryInstance.task?.id) {
                result.taskId = result.entryInstance.task.id
                taskInstance = Task.lock(result.entryInstance.task.id)
            }

            if(!taskInstance)
                return fail(field:"task", code:"task.notFound")

            if(result.entryInstance.hasErrors() || !result.entryInstance.save())
                return fail(code:"default.create.failure")

            if(taskInstance.taskStatus.id == 3)
                return fail(field:"task", code:"task.operationNotPermittedOnCompleteTask")

            // If task status is "Not Started" and entry type is "Work Done" and time has been booked.
            // Then we create the started modification and set task status.
            if(taskInstance.taskStatus.id == 1 && result.entryInstance.entryType.id == 3
                && (result.entryInstance.durationHour + result.entryInstance.durationMinute > 0)) {

                // Create the "Started" task modification, this provides the "Actual Started Date".
                def taskModification = new TaskModification(person: authService.currentUser,
                                                        taskModificationType: TaskModificationType.read(2),
                                                        task: taskInstance)

                if(taskModification.hasErrors() || !taskModification.save())
                    return fail(field:"task", code:"task.modifications.failedToSave")

                // Set task status to "In Progress".
                taskInstance.taskStatus = TaskStatus.read(2)

                if(taskInstance.hasErrors() || !taskInstance.save())
                    return fail(field:"task", code:"task.failedToSave")
            }

            // Success.
            return result

        } // end withTransaction
    } // end saveEntry()

    /**
    * Updates an existing task.
    * @param params The params to update for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def update(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail('id', "task.notFound")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.properties = params

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(3),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end update()

    /**
    * Completes an existing task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def complete(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.taskStatus = TaskStatus.get(3)
            result.taskInstance.attentionFlag = false
            result.taskInstance.taskRecurringSchedule?.enabled = false

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(4),
                                                    task: result.taskInstance)


            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end complete()

    /**
    * Sets the attentionFlag on an existing task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def setAttentionFlag(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.attentionFlag = true

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(12),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end flag()

    /**
    * Clears the attentionFlag on an existing task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def clearAttentionFlag(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.attentionFlag = false

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(13),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end clearFlag()

    /**
    * Reopens an existing task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def reopen(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            def isInProgress = false
            result.taskInstance.entries.each() {
                if(it.entryType.id == 3 && (it.durationHour + it.durationMinute > 0) )
                    isInProgress = true
            }

            if(isInProgress)
                result.taskInstance.taskStatus = TaskStatus.read(2) // In Progress
            else
                result.taskInstance.taskStatus = TaskStatus.read(1) // Not Started

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(5),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end reopen()

    /**
    * Move a task to the trash.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def trash(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.trash = true
            result.taskInstance.attentionFlag = false
            result.taskInstance.taskRecurringSchedule?.enabled = false

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(6),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end trash()

    /**
    * Restore a task from the trash.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def restore(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.trash = false

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(7),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end restore()

    /**
    * Approve a task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def approve(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.approved = true

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(8),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end approve()

    /**
    * Remove a previously given approval from a task.
    * @param params The params for task with id of params.id.
    * @returns A map containing result.error (if any error) and result.taskInstance (if available).
    */
    def renegeApproval(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            result.taskInstance = Task.get(params.id)

            if(!result.taskInstance)
                return fail(code:"default.not.found")

            // Optimistic locking check.
            if(params.version) {
                if(result.taskInstance.version > params.version.toLong())
                    return fail(field:"version", code:"default.optimistic.locking.failure")
            }

            result.taskInstance.approved = false

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                return fail(code:"default.update.failure")

            def taskModification = new TaskModification(person:authService.currentUser,
                                                    taskModificationType: TaskModificationType.get(9),
                                                    task: result.taskInstance)

            if(taskModification.hasErrors() || !taskModification.save())
                return fail(code:"task.modifications.failedToSave")

            // Success.
            return result

        } //end withTransaction
    }  // end renegeApproval()

    /**
    * Creates a new unscheduled breakin task with the given params.
    * @param params The params to use when creating the new task.
    * @returns A map containing result.error (if any error) and result.taskInstance.
    */
    def saveUnscheduled(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            // If not supplied.
            if(!params.taskStatus)
                params.taskStatus = TaskStatus.get(1) // Not Started.

            result.taskInstance = new Task(params)

            // Always for an unscheduled breakin..
            result.taskInstance.taskType = TaskType.get(2) // Unscheduled Breakin.
            result.taskInstance.taskBudgetStatus = TaskBudgetStatus.get(1) // Unplanned.

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                fail(code:"default.create.failure")

            if(!result.error) {
                def taskModification = new TaskModification(person: authService.currentUser,
                                                                taskModificationType: TaskModificationType.get(1), // Created.
                                                                task: result.taskInstance)

                if(taskModification.hasErrors() || !taskModification.save())
                    fail(field:"taskModifications", code:"task.modifications.failedToSave")
            }

            // Success.
            return result

        } //end withTransaction
    } // end saveUnscheduled()

    /**
    * Creates a new immediate callout task with the given params.
    * @param params The params to use when creating the new task.
    * @returns A map containing result.error (if any error) and result.taskInstance.
    */
    def saveImmediateCallout(params) {
        Task.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.taskInstance && m.field)
                    result.taskInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["Task", params.id] ]
                return result
            }

            // If not supplied.
            if(!params.taskStatus)
                params.taskStatus = TaskStatus.get(1) // Not Started.

            result.taskInstance = new Task(params)

            // Always for an immediate callout.
            result.taskInstance.taskType = TaskType.get(1) // Immediate Callout.
            result.taskInstance.taskBudgetStatus = TaskBudgetStatus.get(1) // Unplanned.
            result.taskInstance.taskPriority = TaskPriority.get(1) // Immediate.
            result.taskInstance.taskGroup = TaskGroup.get(1) // Engineering Activites.
            result.taskInstance.approved = true
            result.taskInstance.leadPerson = authService.currentUser
            result.taskInstance.targetCompletionDate = result.taskInstance.targetStartDate

            if(result.taskInstance.hasErrors() || !result.taskInstance.save())
                fail(code:"default.create.failure")

            if(!result.error) {
                def taskModification = new TaskModification(person: authService.currentUser,
                                                                taskModificationType: TaskModificationType.get(1), // Created.
                                                                task: result.taskInstance)

                if(taskModification.hasErrors() || !taskModification.save())
                    fail(field:"taskModifications", code:"task.modifications.failedToSave")
            }

            def productionReference
            if(params.entryFault.productionReference.id.isLong())
                productionReference = ProductionReference.get(params.entryFault.productionReference.id.toLong())

            def faultParams = [task: result.taskInstance,
                                            entryType: EntryType.get(1),
                                            comment: params.entryFault.comment,
                                            dateDone: result.taskInstance.targetStartDate,
                                            productionReference: productionReference,
                                            durationHour: params.entryFault.durationHour,
                                            durationMinute: params.entryFault.durationMinute]
            def faultResult = saveEntry(faultParams)
            result.entryFaultInstance = faultResult.entryInstance

            def causeParams = [task: result.taskInstance,
                                            entryType: EntryType.get(2),
                                            dateDone: result.taskInstance.targetStartDate,
                                            comment: params.entryCause.comment]
            def causeResult = saveEntry(causeParams)
            result.entryCauseInstance = causeResult.entryInstance

            def workDoneParams = [task: result.taskInstance,
                                                    entryType: EntryType.get(3),
                                                    comment: params.entryWorkDone.comment,
                                            dateDone: result.taskInstance.targetStartDate,
                                                    durationHour: params.entryWorkDone.durationHour,
                                                    durationMinute: params.entryWorkDone.durationMinute]
            def workDoneResult = saveEntry(workDoneParams)
            result.entryWorkDoneInstance = workDoneResult.entryInstance

            if(result.error)
                return result

            if(causeResult.error)
                return fail(code: "default.create.failure")

            if(faultResult.error)
                return fail(code: "default.create.failure")

            if(workDoneResult.error)
                return fail(code: "default.create.failure")

            // Success.
            return result

        } //end withTransaction
    } // end saveImmediateCallout()

} // end TaskService
