Index: trunk/grails-app/services/AssetService.groovy
===================================================================
--- trunk/grails-app/services/AssetService.groovy	(revision 751)
+++ trunk/grails-app/services/AssetService.groovy	(revision 798)
@@ -22,4 +22,26 @@
                 }
         }
+    }
+
+    /**
+    * Determines and returns a list of assemblies for an asset.
+    * This is purely a 'load from database' type method since a new hibernateSession is used.
+    * @params Asset to get the subItems for.
+    * @returns A list of the assemblies.
+    */
+    def getAssemblies(asset) {
+        def assemblies = []
+        if(!(asset instanceof Asset))
+            return assemblies
+        // Database efficiency:
+        // The asset is configured to batch fetch assetSubItems which
+        // in turn are configured to batch fetch subItems.
+        Asset.withNewSession {
+            Asset.get(asset.id).assetSubItems.each {
+                assemblies.addAll(it.subItems)
+            }
+        }
+        assemblies.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }
+        return assemblies
     }
 
Index: trunk/grails-app/services/CreateDataService.groovy
===================================================================
--- trunk/grails-app/services/CreateDataService.groovy	(revision 751)
+++ trunk/grails-app/services/CreateDataService.groovy	(revision 798)
@@ -3,7 +3,8 @@
 /**
 * Provides a data service to create base and demo data.
-* Beware that most, if not all, base data is referenced by "Id" throughout the program.
+* Beware that most, if not all, BASE data is referenced by "Id" throughout the program.
 * This allows changing the text of the 'name' property to something of the same meaning.
 * But be sure to maintain the correct Id during creation, indicated by #1, #2 etc.
+* Task.list()[0] is used to allow integration testing with DEMO data, where Id's may change due to create-delete.
 */
 class  CreateDataService {
@@ -846,5 +847,5 @@
         //Task #1
         p = [taskGroup:TaskGroup.findByName("Engineering Activites"),
-                taskPriority:TaskPriority.get(2),
+                taskPriority:TaskPriority.get(1),
                 taskType:TaskType.get(1),
                 leadPerson:Person.get(2),
@@ -1097,12 +1098,20 @@
         //TaskProcedure
         def taskProcedureInstance
-
-        taskProcedureInstance = new TaskProcedure(name: "Daily check")
+        def taskInstance
+        def person = Person.get(3)
+
+        taskInstance = Task.list()[6]
+        taskProcedureInstance = new TaskProcedure(linkedTask: taskInstance,
+                                                                                    createdBy: person,
+                                                                                    lastUpdatedBy: person)
         saveAndTest(taskProcedureInstance)
-        taskProcedureInstance.addToTasks(Task.list()[0])
-
-        taskProcedureInstance = new TaskProcedure(name: "100hr Service")
+        taskProcedureInstance.addToTasks(taskInstance)
+
+        taskInstance = Task.list()[4]
+        taskProcedureInstance = new TaskProcedure(linkedTask: taskInstance,
+                                                                                    createdBy: person,
+                                                                                    lastUpdatedBy: person)
         saveAndTest(taskProcedureInstance)
-        taskProcedureInstance.addToTasks(Task.list()[6])
+        taskProcedureInstance.addToTasks(taskInstance)
     }
 
@@ -1111,25 +1120,29 @@
         //MaintenanceAction
         def maintenanceActionInstance
+        def taskProcedure = TaskProcedure.get(1)
+        def assetSubItem = AssetSubItem.get(6)
 
         //MaintenanceAction #1
         maintenanceActionInstance = new MaintenanceAction(description: "Check all E-stops, activate E-stops S1-S12 and ensure machine cannot run",
                                                                                                         procedureStepNumber: 10,
-                                                                                                        maintenancePolicy: MaintenancePolicy.get(1),
-                                                                                                        taskProcedure: TaskProcedure.get(1))
-        saveAndTest(maintenanceActionInstance)
+                                                                                                        assetSubItem: assetSubItem,
+                                                                                                        taskProcedure: taskProcedure)
+        taskProcedure.addToMaintenanceActions(maintenanceActionInstance)
 
         //MaintenanceAction #2
         maintenanceActionInstance = new MaintenanceAction(description: "Do more pushups",
                                                                                                         procedureStepNumber: 20,
-                                                                                                        maintenancePolicy: MaintenancePolicy.get(1),
-                                                                                                        taskProcedure: TaskProcedure.get(1))
-        saveAndTest(maintenanceActionInstance)
+                                                                                                        assetSubItem: assetSubItem,
+                                                                                                        taskProcedure: taskProcedure)
+        taskProcedure.addToMaintenanceActions(maintenanceActionInstance)
 
         //MaintenanceAction #3
         maintenanceActionInstance = new MaintenanceAction(description: "Ok just one more pushup",
                                                                                                         procedureStepNumber: 30,
-                                                                                                        maintenancePolicy: MaintenancePolicy.get(1),
-                                                                                                        taskProcedure: TaskProcedure.get(1))
-        saveAndTest(maintenanceActionInstance)
+                                                                                                        assetSubItem: assetSubItem,
+                                                                                                        taskProcedure: taskProcedure)
+        taskProcedure.addToMaintenanceActions(maintenanceActionInstance)
+
+        saveAndTest(taskProcedure)
     }
 
@@ -1475,5 +1488,4 @@
                                                                 section: Section.get(1))
         saveAndTest(assetInstance1)
-//        assetInstance.addToMaintenanceActions(MaintenanceAction.get(1))
 
         //Asset #2
Index: trunk/grails-app/services/TaskProcedureService.groovy
===================================================================
--- trunk/grails-app/services/TaskProcedureService.groovy	(revision 798)
+++ trunk/grails-app/services/TaskProcedureService.groovy	(revision 798)
@@ -0,0 +1,144 @@
+/**
+* Provides a service class for the TaskProcedure domain class.
+*/
+class TaskProcedureService {
+
+    boolean transactional = false
+
+    def authService
+
+    /**
+    * Updates an existing taskProcedure.
+    * @param params The params to update for taskProcedure with id of params.id.
+    * @returns A map containing result.error (if any error) and result.taskProcedureInstance (if available).
+    */
+    def update(params) {
+        TaskProcedure.withTransaction { status ->
+            def result = [:]
+
+            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
+                result.taskProcedureInstance?.createdBy
+                return result
+            }
+
+            result.taskProcedureInstance = TaskProcedure.get(params.id)
+
+            if(!result.taskProcedureInstance)
+                return fail(code:"default.not.found")
+
+            // Optimistic locking check.
+            if(params.version) {
+                if(result.taskProcedureInstance.version > params.version.toLong())
+                    return fail(field:"version", code:"default.optimistic.locking.failure")
+            }
+
+            result.taskProcedureInstance.properties = params
+            result.taskProcedureInstance.lastUpdatedBy = authService.currentUser
+            result.taskProcedureInstance.lastUpdated = new Date() // Required to trigger version increment.
+
+            // 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)
+            }
+            def nullDocumentReferences = result.taskProcedureInstance.documentReferences.findAll {!it}
+            if (nullDocumentReferences) {
+                result.taskProcedureInstance.documentReferences.removeAll(nullDocumentReferences)
+            }
+
+            // Save for restoration if validation fails.
+            def savedMaintenanceActions = new ArrayList(result.taskProcedureInstance.maintenanceActions)
+            def savedDocumentReferences = new ArrayList(result.taskProcedureInstance.documentReferences)
+
+            // Remove toBeDeleted before validation.
+            def ma_toBeDeleted = result.taskProcedureInstance.maintenanceActions.findAll {it.toBeDeleted}
+            if (ma_toBeDeleted) {
+                result.taskProcedureInstance.maintenanceActions.removeAll(ma_toBeDeleted)
+            }
+            def docRef_toBeDeleted = result.taskProcedureInstance.documentReferences.findAll {it.toBeDeleted}
+            if (docRef_toBeDeleted) {
+                result.taskProcedureInstance.documentReferences.removeAll(docRef_toBeDeleted)
+            }
+
+            if(result.taskProcedureInstance.hasErrors() || !result.taskProcedureInstance.save()) {
+                // Restore the saved items, some of which contain toBeDeleted flags but
+                // have not been deleted yet since validation failed.
+                // The toBeDeleted items are hidden in the view.
+                result.taskProcedureInstance.maintenanceActions = savedMaintenanceActions
+                result.taskProcedureInstance.documentReferences = savedDocumentReferences
+                // Populate collection errors for display.
+                result.taskProcedureInstance.maintenanceActions.each { it.validate() }
+                result.taskProcedureInstance.documentReferences.each { it.validate() }
+                return fail(code:"default.update.failure")
+            }
+
+            // Success.
+            return result
+
+        } //end withTransaction
+    }  // 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)
+            }
+            def nullDocumentReferences = result.taskProcedureInstance.documentReferences.findAll {!it}
+            if (nullDocumentReferences) {
+                result.taskProcedureInstance.documentReferences.removeAll(nullDocumentReferences)
+            }
+
+            // Also sets: taskInstance.taskProcedure = taskProcedureInstance
+            result.taskProcedureInstance.addToTasks(result.taskProcedureInstance.linkedTask)
+
+            if(result.taskProcedureInstance.hasErrors() || !result.taskProcedureInstance.save()) {
+                // Populate collection errors for display.
+                result.taskProcedureInstance.maintenanceActions.each { it.validate() }
+                result.taskProcedureInstance.documentReferences.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
