Index: trunk/grails-app/services/AssetReportService.groovy
===================================================================
--- trunk/grails-app/services/AssetReportService.groovy	(revision 652)
+++ trunk/grails-app/services/AssetReportService.groovy	(revision 654)
@@ -117,3 +117,86 @@
     } // getAssetDetail
 
+    /**
+    * Selects and returns level 1 sub items (aka machines or equipment) and their details.
+    * @param params The request params, may contain params to specify the search.
+    * @param locale The locale to use when generating result.message.
+    */
+    def getEquipmentRegister(params, locale) {
+        def result = [:]
+
+        // Inner join used to return only attribTypes that are used by AssetSubItemExtendedAttributes.
+        // So the result is only assetSubItem extendedAttributeTypes.
+        def attribTypesQ = new HqlBuilder().query {
+            select 'distinct attribT.name'
+            from 'AssetSubItemExtendedAttribute attrib',
+                    'join attrib.extendedAttributeType as attribT'
+            order 'by attribT.name asc'
+        }
+        result.attribTypes = Asset.executeQuery(attribTypesQ.query, attribTypesQ.namedParams)
+
+        // Since there will be nothing to show in the report table for assets without level 1 assetSubItems,
+        // a list needs to be given to the user.
+        def assetsWithoutEquipmentQ = new HqlBuilder().query {
+            select 'distinct asset'
+            from 'Asset asset',
+                    'left join asset.assetSubItems as assetSubItem'
+            where 'assetSubItem = null'
+        }
+        result.assetsWithoutEquipment = AssetSubItem.executeQuery(assetsWithoutEquipmentQ.query, assetsWithoutEquipmentQ.namedParams)
+
+        // A result is returned for every level 1 assetSubItem and for any extended attributes.
+        def q = new HqlBuilder().query {
+            select 'new map(asset.name as assetName',
+                        'assetSubItem.name as name',
+                        'assetSubItem.description as description',
+                        'assetSubItem.comment as comment',
+                        'attribT.name as attribType',
+                        'attrib.value as attribValue)'
+            from 'AssetSubItem assetSubItem',
+                    'inner join assetSubItem.assets as asset',
+                    'left join assetSubItem.assetSubItemExtendedAttributes as attrib',
+                    'left join attrib.extendedAttributeType as attribT'
+            where 'asset != null' // ensure that only level 1 assetSubItems are returned.
+            if(params.section instanceof Section) {
+                namedParams.section = params.section
+                and 'asset.section = :section'
+            }
+            order 'by asset.name asc, assetSubItem.name asc, attribT.name asc'
+        }
+        def equipmentResults = AssetSubItem.executeQuery(q.query, q.namedParams)
+
+        // Build the report table rows.
+        // Rows are keyed by equipmentResult.assetName+equipmentResult.name` while the value is a Map of the attributes.
+        // The report table then groups by assetName.
+        def rows = [:]
+        equipmentResults.each { equipmentResult ->
+            // Create row if it does not exist yet.
+            def rowKey = equipmentResult.assetName+equipmentResult.name
+            if(!rows.containsKey(rowKey)) {
+                rows[rowKey] = ['assetName': equipmentResult.assetName,
+                                                            'name':equipmentResult.name,
+                                                            'description':equipmentResult.description,
+                                                            'comment':equipmentResult.comment]
+
+                // Add all attribType columns.
+                result.attribTypes.each { column ->
+                    rows[rowKey][column] = null
+                }
+            }
+
+            // Assign value to column.
+            rows[rowKey][equipmentResult.attribType] = equipmentResult.attribValue
+        }
+
+        // The value of each row is the dataList used by the report table.
+        result.dataList = rows.collect {it.value}
+        // Print formatting, since we are done with these as objects.
+        result.attribTypes = result.attribTypes.join(', ')
+        result.assetsWithoutEquipment = result.assetsWithoutEquipment.collect {it.name}.join(', ')
+
+        // Success.
+        return result
+
+    } // getEquipmentRegister
+
 } // end class
