Index: trunk/grails-app/controllers/ReportController.groovy
===================================================================
--- trunk/grails-app/controllers/ReportController.groovy	(revision 651)
+++ trunk/grails-app/controllers/ReportController.groovy	(revision 652)
@@ -8,4 +8,5 @@
     def dateUtilService
     def taskReportService
+    def assetReportService
     def inventoryReportService
 
@@ -138,3 +139,37 @@
     } // stockTakeByLocation
 
+    def assetDetail = {
+
+        params.reportTitle = "Asset Detail"
+        params.logoUrl = grailsApplication.mainContext.getResource('images/logo.png').getURL()
+        params.currentUser = authService.currentUser
+        if(params.asset.id == 'all')
+            params.asset = "All"
+        else
+            params.asset = Asset.get(params.asset.id.toLong())
+
+        def dataModel = assetReportService.getAssetDetail(params, RCU.getLocale(request))
+
+        // Jasper plugin controller expects data to be a Collection.
+        chain(controller:'jasper', action:'index', model:[data: dataModel], params:params)
+
+    } // assetDetail
+
+    def assetRegister = {
+
+        params.reportTitle = "Asset Register"
+        params.logoUrl = grailsApplication.mainContext.getResource('images/logo.png').getURL()
+        params.currentUser = authService.currentUser
+        if(params.section.id == 'all')
+            params.section = "All"
+        else
+            params.section = Section.get(params.section.id.toLong())
+
+        def dataModel = assetReportService.getAssetRegister(params, RCU.getLocale(request))
+
+        // Jasper plugin controller expects data to be a Collection.
+        chain(controller:'jasper', action:'index', model:[data: [dataModel]], params:params)
+
+    } // assetRegister
+
 } // end of class.
Index: trunk/grails-app/i18n/messages.properties
===================================================================
--- trunk/grails-app/i18n/messages.properties	(revision 651)
+++ trunk/grails-app/i18n/messages.properties	(revision 652)
@@ -292,4 +292,5 @@
 default.please.select.text=--Please-Select--
 default.none.select.text=--None--
+default.all.select.text=--All--
 
 #
Index: trunk/grails-app/services/AssetReportService.groovy
===================================================================
--- trunk/grails-app/services/AssetReportService.groovy	(revision 652)
+++ trunk/grails-app/services/AssetReportService.groovy	(revision 652)
@@ -0,0 +1,119 @@
+import net.kromhouts.HqlBuilder
+
+/**
+* Service class that encapsulates the business logic for Asset Reports.
+*/
+class AssetReportService {
+
+    boolean transactional = false
+
+    def authService
+    def dateUtilService
+//     def messageSource
+
+    def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib()
+
+    def paramsMax = 100000
+
+    /**
+    * Selects and returns the assets 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 getAssetRegister(params, locale) {
+        def result = [:]
+
+        // Inner join used to return only attribTypes that are used by AssetExtendedAttributes.
+        // So the result is only asset extendedAttributeTypes.
+        def attribTypesQ = new HqlBuilder().query {
+            select 'distinct attribT.name'
+            from 'AssetExtendedAttribute attrib',
+                    'join attrib.extendedAttributeType as attribT'
+            order 'by attribT.name asc'
+        }
+        result.attribTypes = Asset.executeQuery(attribTypesQ.query, attribTypesQ.namedParams)
+
+        // A result is returned for every asset and for any extended attributes.
+        def q = new HqlBuilder().query {
+            select 'new map(asset.name as name',
+                        'asset.description as description',
+                        'asset.comment as comment',
+                        'attribT.name as attribType',
+                        'attrib.value as attribValue)'
+            from 'Asset asset',
+                    'left join asset.assetExtendedAttributes as attrib',
+                    'left join attrib.extendedAttributeType as attribT'
+            if(params.section instanceof Section) {
+                namedParams.section = params.section
+                where 'asset.section = :section'
+            }
+            order 'by asset.name asc, attribT.name asc'
+        }
+        def assetResults = Asset.executeQuery(q.query, q.namedParams)
+
+        // Build the report table row for each asset.
+        // Rows are keyed by asset.name and the value is a Map of the attributes.
+        def rows = [:]
+        assetResults.each { assetResult ->
+            // Create row if it does not exist yet.
+            if(!rows.containsKey(assetResult.name)) {
+                rows[assetResult.name] = ['name':assetResult.name,
+                                                            'description':assetResult.description,
+                                                            'comment':assetResult.comment]
+
+                // Add all attribType columns.
+                result.attribTypes.each { column ->
+                    rows[assetResult.name][column] = null
+                }
+            }
+
+            // Assign value to column.
+            rows[assetResult.name][assetResult.attribType] = assetResult.attribValue
+        }
+
+        // The value of each row is the dataList used by the report table.
+        result.dataList = rows.collect {it.value}
+
+        // Success.
+        return result
+
+    } // getAssetRegister
+
+    /**
+    * Selects and returns an asset (or all) and its details.
+    * @param params The request params, may contain params to specify the search.
+    * @param locale The locale to use when generating result.message.
+    */
+    def getAssetDetail(params, locale) {
+        //def result = [:]
+        def result
+
+        //result.summaryOfCalculationMethod = ''
+
+        // A result is returned for every asset and for any extended attributes.
+        // The report then groups by asset.name
+        def q = new HqlBuilder().query {
+            select 'new map(asset.name as name',
+                        'asset.description as description',
+                        'asset.comment as comment',
+                        'attribT.name as attribType',
+                        'attrib.value as attribValue)'
+            from 'Asset asset',
+                    'left join asset.assetExtendedAttributes as attrib',
+                    'left join attrib.extendedAttributeType as attribT'
+            if(params.asset instanceof Asset) {
+                namedParams.asset = params.asset
+                where 'asset = :asset'
+            }
+            order 'by asset.name asc, attribT.name asc'
+        }
+
+        // result.dataList = Asset.list()
+        result = Asset.executeQuery(q.query, q.namedParams)
+
+        // Success.
+        return result
+
+    } // getAssetDetail
+
+} // end class
Index: trunk/grails-app/views/appCore/start.gsp
===================================================================
--- trunk/grails-app/views/appCore/start.gsp	(revision 651)
+++ trunk/grails-app/views/appCore/start.gsp	(revision 652)
@@ -138,4 +138,27 @@
                                             <br />
                                             <g:jasperReport controller="report"
+                                                                            action="assetDetail"
+                                                                            jasper="assetDetail"
+                                                                            name="Asset Detail"
+                                                                            format="PDF, XLS">
+                                                <g:select optionKey="id"
+                                                                    from="${Asset.findAllByIsActive(true).sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }}"
+                                                                    name="asset.id"
+                                                                    noSelection="['all':/${g.message(code:'default.all.select.text')}/]">
+                                                </g:select>
+                                            </g:jasperReport>
+                                            <g:jasperReport controller="report"
+                                                                            action="assetRegister"
+                                                                            jasper="assetRegister"
+                                                                            name="Asset Register"
+                                                                            format="PDF, XLS">
+                                                <g:select optionKey="id"
+                                                                    from="${Section.findAllByIsActive(true).sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }}"
+                                                                    name="section.id"
+                                                                    noSelection="['all':/${g.message(code:'default.all.select.text')}/]">
+                                                </g:select>
+                                            </g:jasperReport>
+                                            <br />
+                                            <g:jasperReport controller="report"
                                                                             action="templatePortrait"
                                                                             jasper="templatePortrait"
