Index: trunk/grails-app/i18n/messages.properties
===================================================================
--- trunk/grails-app/i18n/messages.properties	(revision 509)
+++ trunk/grails-app/i18n/messages.properties	(revision 510)
@@ -84,4 +84,6 @@
 like to assign this person to the task.
 
+task.delete.failure.production=Tasks may not be deleted in production mode, \
+    set the trash flag instead.
 task.notFound=Could not complete operation, task not found.
 task.operationNotPermittedOnCompleteTask=This operation is not permitted on a complete task.
Index: trunk/grails-app/services/TaskService.groovy
===================================================================
--- trunk/grails-app/services/TaskService.groovy	(revision 509)
+++ trunk/grails-app/services/TaskService.groovy	(revision 510)
@@ -1,2 +1,4 @@
+import grails.util.Environment
+
 /**
 * Provides a service class for the Task domain class.
@@ -221,4 +223,50 @@
 
     /**
+    * 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()
+            }
+
+            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.
@@ -241,5 +289,5 @@
 
             def taskInstance
-            if(result.entryInstance.task.id) {
+            if(result.entryInstance.task?.id) {
                 result.taskId = result.entryInstance.task.id
                 taskInstance = Task.lock(result.entryInstance.task.id)
@@ -255,10 +303,12 @@
                 return fail(field:"task", code:"task.operationNotPermittedOnCompleteTask")
 
-            // If task status is "Not Started" and entry type is "Work Done" then we create the started modification and set the status.
-            if(taskInstance.taskStatus.id == 1 && result.entryInstance.entryType.id == 3) {
+            // 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.get(2),
+                                                        taskModificationType: TaskModificationType.read(2),
                                                         task: taskInstance)
 
@@ -267,5 +317,5 @@
 
                 // Set task status to "In Progress".
-                taskInstance.taskStatus = TaskStatus.get(2)
+                taskInstance.taskStatus = TaskStatus.read(2)
 
                 if(taskInstance.hasErrors() || !taskInstance.save())
@@ -494,5 +544,14 @@
             }
 
-            result.taskInstance.taskStatus = TaskStatus.get(2)
+            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())
Index: trunk/test/integration/TaskServiceTests.groovy
===================================================================
--- trunk/test/integration/TaskServiceTests.groovy	(revision 510)
+++ trunk/test/integration/TaskServiceTests.groovy	(revision 510)
@@ -0,0 +1,156 @@
+import grails.test.*
+
+/**
+* Integration tests for TaskService.
+*/
+class TaskServiceTests extends GroovyTestCase {
+
+    // Data will be saved, not rolled back.
+    // Be sure to clean up in tearDown().
+    boolean transactional = false
+
+    def taskService
+    def dateUtilService
+
+    def taskA
+    def taskB
+    def taskCount = 0
+
+    // Setup is called before each test.
+    protected void setUp() {
+        super.setUp()
+
+        // Check environment state.
+        assert Task.count() == 0
+        assert Entry.count() == 0
+        assert TaskModification.count() == 0
+
+        def p = [:]
+        def result
+
+        p = [taskGroup:TaskGroup.findByName("Engineering Activites"),
+                taskPriority:TaskPriority.get(2),
+                taskType:TaskType.get(1),
+                leadPerson:Person.get(1),
+                description:"TestA",
+                comment:"Service test task.",
+                targetStartDate: dateUtilService.today,
+                targetCompletionDate: dateUtilService.today]
+
+        result = taskService.save(p)
+        assert result.error == null
+        taskCount++
+        taskA = result.taskInstance.refresh()
+
+        p.description = "TestB"
+        result = taskService.save(p)
+        assert result.error == null
+        taskCount++
+        taskB = result.taskInstance.refresh()
+    }
+
+    // Tear down is called after each test.
+    protected void tearDown() {
+        super.tearDown()
+
+        taskService.delete(taskA)
+        taskService.delete(taskB)
+
+        // Ensure that we leave environment clean.
+        assert Task.count() == 0
+        assert TaskModification.count() == 0
+        assert Entry.count() == 0
+    }
+
+    def testSave() {
+
+        // Task are created by setUp().
+        assert Task.count() == taskCount
+
+        taskA.refresh()
+        assert taskA.taskModifications.size() == 1
+        assert taskA.taskModifications.each() {
+            it.taskModificationType.id == 1 // Created.
+        }
+
+        taskB.refresh()
+        assert taskB.taskModifications.size() == 1
+        assert taskB.taskModifications.each() {
+            it.taskModificationType.id == 1 // Created.
+        }
+
+    } // testSave()
+
+    void testComplete() {
+
+        def modificationCount = 0
+
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(1) // Not Started.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        taskService.complete(taskA)
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(3) // Complete.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+    } // testComplete()
+
+    void testReopen() {
+
+        def entryParams = [:]
+        def modificationCount = 0
+
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(1) // Not Started.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        taskService.complete(taskA)
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(3) // Complete.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        taskService.reopen(taskA)
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(1) // Not Started.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        // Work Done Entry, with zero time booked.
+        entryParams = [task: taskA,
+                                        entryType: EntryType.read(3),
+                                        comment: "Test entry.",
+                                        durationHour: 0,
+                                        durationMinute: 0]
+
+        assert taskService.saveEntry(entryParams).error == null
+
+        taskService.complete(taskA)
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(3) // Complete.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        taskService.reopen(taskA)
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(1) // Not Started.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        // Work Done Entry, with time booked.
+        entryParams.durationMinute = 1
+        assert taskService.saveEntry(entryParams).error == null
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(2) // In Progress.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        taskService.complete(taskA)
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(3) // Complete.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+        taskService.reopen(taskA)
+        taskA.refresh()
+        assert taskA.taskStatus ==  TaskStatus.read(2) // In Progress.
+        assert taskA.taskModifications.size() == ++modificationCount
+
+    } // testReopen()
+
+} // end class
