Index: /branches/features/taskProcedureRework/grails-app/controllers/TaskProcedureDetailedController.groovy
===================================================================
--- /branches/features/taskProcedureRework/grails-app/controllers/TaskProcedureDetailedController.groovy	(revision 774)
+++ /branches/features/taskProcedureRework/grails-app/controllers/TaskProcedureDetailedController.groovy	(revision 775)
@@ -5,5 +5,4 @@
 
     def filterService
-    def authService
     def taskProcedureService
 
@@ -69,5 +68,5 @@
             }
             catch(org.springframework.dao.DataIntegrityViolationException e) {
-                flash.message = "TaskProcedure ${params.id} could not be deleted"
+                flash.errorMessage = "TaskProcedure ${params.id} could not be deleted"
                 redirect(controller:'taskDetailed',
                                 action:'show',
@@ -77,5 +76,5 @@
         }
         else {
-            flash.message = "TaskProcedure not found with id ${params.id}"
+            flash.errorMessage = "TaskProcedure not found with id ${params.id}"
             redirect(action:list)
         }
@@ -91,5 +90,5 @@
 
         if(!taskProcedureInstance) {
-            flash.message = "TaskProcedure not found with id ${params.id}"
+            flash.errorMessage = "TaskProcedure not found with id ${params.id}"
             redirect(action:list)
         }
@@ -112,6 +111,15 @@
 
         if(result.error.code == "default.not.found") {
-            flash.message = g.message(code: result.error.code, args: result.error.args)
+            flash.errorMessage = g.message(code: result.error.code, args: result.error.args)
             redirect(action:list)
+            return
+        }
+
+        if(result.error.code == "default.optimistic.locking.failure") {
+            flash.errorMessage = g.message(code: result.error.code, args: result.error.args)
+            redirect(controller:'taskDetailed',
+                            action:'show',
+                            id:result.taskProcedureInstance.linkedTask.id,
+                            params:[showTab:"showProcedureTab"])
             return
         }
@@ -130,4 +138,15 @@
         params.linkedTask = Task.get(params.taskInstance.id)
 
+        // Task already has a taskProcedure.
+        if(params.linkedTask.taskProcedure) {
+            flash.errorMessage = g.message(code: 'default.optimistic.locking.failure')
+            redirect(controller:'taskDetailed',
+                            action:'show',
+                            id:params.linkedTask.id,
+                            params:[showTab:"showProcedureTab"])
+            return
+        }
+
+        // Task does not have a primaryAsset.
         if(!params.linkedTask?.primaryAsset) {
             flash.errorMessage = "Please set a Primary Asset first, then go to the Procedure tab."
@@ -142,30 +161,25 @@
 
     def save = {
-        def taskProcedureInstance = new TaskProcedure(params)
-        taskProcedureInstance.createdBy = authService.currentUser
-        taskProcedureInstance.lastUpdatedBy = authService.currentUser
-        def taskInstance = Task.get(params.linkedTask.id)
+        def result = taskProcedureService.save(params)
 
-        // Gaps in the html index's can be created by adding 2 items and removing the first one.
-        // This creates a gap at the missing index where LazyList will return a null.
-        def nullMaintenanceActions = taskProcedureInstance.maintenanceActions.findAll {!it}
-        if (nullMaintenanceActions) {
-            taskProcedureInstance.maintenanceActions.removeAll(nullMaintenanceActions)
+        if(!result.error) {
+            flash.message = g.message(code: "default.create.success", args: ["TaskProcedure", result.taskProcedureInstance.id])
+            redirect(controller:'taskDetailed',
+                            action:'show',
+                            id:result.taskProcedureInstance.linkedTask.id,
+                            params:[showTab:"showProcedureTab"])
+            return
         }
 
-        if(!taskProcedureInstance.hasErrors() && taskProcedureInstance.save(flush: true)) {
-             // Also sets: taskInstance.taskProcedure = taskProcedureInstance
-            taskProcedureInstance.addToTasks(taskInstance)
-            flash.message = "TaskProcedure ${taskProcedureInstance.id} created."
+        if(result.error.code == "default.optimistic.locking.failure") {
+            flash.errorMessage = g.message(code: result.error.code, args: result.error.args)
             redirect(controller:'taskDetailed',
                             action:'show',
-                            id:taskProcedureInstance.linkedTask.id,
+                            id:result.taskProcedureInstance.linkedTask.id,
                             params:[showTab:"showProcedureTab"])
+            return
         }
-        else {
-            // Populate maintenanceAction errors for display.
-            taskProcedureInstance.maintenanceActions.each { it.validate() }
-            render(view:'create',model:[taskProcedureInstance:taskProcedureInstance])
-        }
+
+        render(view:'create', model:[taskProcedureInstance: result.taskProcedureInstance])
     }
 
Index: /branches/features/taskProcedureRework/grails-app/services/TaskProcedureService.groovy
===================================================================
--- /branches/features/taskProcedureRework/grails-app/services/TaskProcedureService.groovy	(revision 774)
+++ /branches/features/taskProcedureRework/grails-app/services/TaskProcedureService.groovy	(revision 775)
@@ -22,4 +22,7 @@
                     result.taskProcedureInstance.errors.rejectValue(m.field, m.code)
                 result.error = [ code: m.code, args: ["TaskProcedure", params.id] ]
+                // Fetch to prevent lazy initialization error.
+                result.taskProcedureInstance?.linkedTask.primaryAsset
+                result.taskProcedureInstance?.createdBy
                 return result
             }
@@ -63,7 +66,4 @@
                 // Populate maintenanceAction errors for display.
                 result.taskProcedureInstance.maintenanceActions.each { it.validate() }
-                // Fetch to prevent lazy initialization error.
-                result.taskProcedureInstance.linkedTask.primaryAsset
-                result.taskProcedureInstance.createdBy
                 return fail(code:"default.update.failure")
             }
@@ -75,3 +75,54 @@
     }  // end update()
 
+    /**
+    * Creates a new taskProcedure with the given params.
+    * @param params The params to use when creating the new taskProcedure.
+    * @returns A map containing result.error (if any error) and result.taskProcedure.
+    */
+    def save(params) {
+        def result = [:]
+        TaskProcedure.withTransaction { status ->
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.taskProcedureInstance && m.field) 
+                    result.taskProcedureInstance.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["TaskProcedure", params.id] ]
+                // Fetch to prevent lazy initialization error.
+                result.taskProcedureInstance.linkedTask.primaryAsset
+                return result
+            }
+
+            result.taskProcedureInstance = new TaskProcedure(params)
+
+            // Optimistic locking check on linkedTask.
+            if(result.taskProcedureInstance.linkedTask.taskProcedure)
+                    return fail(field:"version", code:"default.optimistic.locking.failure")
+
+            result.taskProcedureInstance.createdBy = authService.currentUser
+            result.taskProcedureInstance.lastUpdatedBy = authService.currentUser
+
+            // Gaps in the html index's can be created by adding 2 items and removing the first one.
+            // This creates a gap at the missing index where LazyList will return a null.
+            def nullMaintenanceActions = result.taskProcedureInstance.maintenanceActions.findAll {!it}
+            if (nullMaintenanceActions) {
+                result.taskProcedureInstance.maintenanceActions.removeAll(nullMaintenanceActions)
+            }
+
+            // Also sets: taskInstance.taskProcedure = taskProcedureInstance
+            result.taskProcedureInstance.addToTasks(result.taskProcedureInstance.linkedTask)
+
+            if(result.taskProcedureInstance.hasErrors() || !result.taskProcedureInstance.save()) {
+                // Populate maintenanceAction errors for display.
+                result.taskProcedureInstance.maintenanceActions.each { it.validate() }
+                return fail(code:"default.create.failure")
+            }
+
+        } //end withTransaction
+
+        result.taskProcedureInstance.lastUpdated = new Date() // Required to trigger version increment to 1.
+
+        // success
+        return result
+    }  // end save()
+
 } // end class
