Index: trunk/grails-app/controllers/AssetDetailedController.groovy
===================================================================
--- trunk/grails-app/controllers/AssetDetailedController.groovy	(revision 178)
+++ trunk/grails-app/controllers/AssetDetailedController.groovy	(revision 271)
@@ -1,12 +1,51 @@
 import org.codehaus.groovy.grails.plugins.springsecurity.Secured
+import org.codehaus.groovy.grails.commons.ConfigurationHolder
 
 class AssetDetailedController extends BaseController {
 
+    def csvService
     def filterService
+    def exportService
 
     def index = { redirect(action:search,params:params) }
 
     // the delete, save and update actions only accept POST requests
-    static allowedMethods = [delete:'POST', save:'POST', update:'POST']
+    static allowedMethods = [delete:'POST', save:'POST', update:'POST', saveCopy:'POST']
+
+    def importAssetTree = {
+    }
+
+    def importAssetTreeSave = {
+        def result = csvService.importAssetTree(request)
+
+        if(!result.error)
+            flash.message = g.message(code: "asset.tree.import.success")
+        else
+            flash.warning = g.message(code: result.error.code, args: result.error.args)
+
+        redirect(action: importAssetTree)
+    }
+
+    /**
+    * Export a csv template.
+    * NOTE: IE has a 'validating' bug in dev mode that causes the export to take a long time!
+    * This does not appear to be a problem once deployed to Tomcat.
+    */
+    def exportAssetTreeTemplate = {
+        response.contentType = ConfigurationHolder.config.grails.mime.types["csv"]
+        response.setHeader("Content-disposition", "attachment; filename=AssetTreeTemplate.csv")
+        def s = csvService.buildAssetTreeTemplate()
+        render s
+    }
+
+    def exportAssetTree = {
+
+        def assetList = Asset.list()
+
+        response.contentType = ConfigurationHolder.config.grails.mime.types["csv"]
+        response.setHeader("Content-disposition", "attachment; filename=AssetTree.csv")
+        def s = csvService.buildAssetTree(assetList)
+        render s
+    }
 
     def list = {
@@ -16,16 +55,100 @@
 
     def search = {
-        params.max = Math.min( params.max ? params.max.toInteger() : 10,  100)
+
+        if(session.taskSearchParamsMax)
+            params.max = session.taskSearchParamsMax
+
+        params.max = Math.min( params.max ? params.max.toInteger() : 10,  1000)
+
+        def assetInstanceList = []
+        def assetInstanceTotal
+        def filterParams = [:]
 
         // Quick Search:
         if(!params.filter) {
-           return [ assetInstanceList: Asset.list( params ), assetInstanceTotal: Asset.count() ]
-        }
+            assetInstanceList = Asset.list( params )
+            assetInstanceTotal = Asset.count()
+            filterParams.quickSearch = params.quickSearch
+        }
+        else {
         // filterPane:
-        return[ assetInstanceList: filterService.filter( params, Asset ),
-                assetInstanceTotal: filterService.count( params, Asset ),
-                filterParams: com.zeddware.grails.plugins.filterpane.FilterUtils.extractFilterParams(params),
-                params:params ]
-    }
+            assetInstanceList = filterService.filter( params, Asset )
+            assetInstanceTotal = filterService.count( params, Asset )
+            filterParams = com.zeddware.grails.plugins.filterpane.FilterUtils.extractFilterParams(params)
+        }
+
+        // export plugin:
+        if(params?.format && params.format != "html") {
+
+            def dateFmt = { date ->
+                formatDate(format: "EEE, dd-MMM-yyyy", date: date)
+            }
+
+//             def fmtAsset = { m ->
+//                     def r = ''
+//                     def assetInstance = Asset.findByName(m)
+// 
+//                     r +=  assetInstance
+//                     r += ", "
+// 
+//                     def  lastSubAsset = assetInstance.subAssets.size() - 1
+//                     assetInstance.subAssets.eachWithIndex() { obj, i ->
+//                         r += "\"" + obj + "\""
+//                         if( i < lastSubAsset )
+//                             r += ", "
+//                     }
+//                     return r
+//             }
+
+//             def fmtSubAsset = { m ->
+//                     def r = ''
+//                     m.each() {
+//                         def machine = Machine.findByName(it)
+//                         def assemblies = machine.assemblies
+//                         r += machine.name
+//                         r += " "
+//                         r += assemblies
+//                         r += " "
+//                     }
+//                     return r
+//             }
+
+            String title = "Asset List."
+
+            response.contentType = ConfigurationHolder.config.grails.mime.types[params.format]
+            response.setHeader("Content-disposition", "attachment; filename=tasks.${params.extension}")
+            List fields = ["section.site",
+                                "section",
+                                "name",
+                                "description"]
+            Map labels = ["section.site": "Site",
+                                "section": "Section",
+                                "name": "Asset",
+                                "description": "Description"]
+//             Map labels
+//             Map formatters = ["subAsset.name": fmtSubAsset]
+            Map formatters = [:]
+            Map parameters = [title: title, separator: ","]
+
+            exportService.export(params.format,
+                                                response.outputStream,
+                                                assetInstanceList.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) },
+                                                fields,
+                                                labels,
+                                                formatters,
+                                                parameters)
+        }
+
+        // Add some basic params to filterParams.
+        filterParams.max = params.max
+        filterParams.offset = params.offset?.toInteger() ?: 0
+        filterParams.sort = params.sort ?: "id"
+        filterParams.order = params.order ?: "desc"
+
+        return[ assetInstanceList: assetInstanceList,
+                assetInstanceTotal: assetInstanceTotal,
+                filterParams: filterParams ]
+
+    } // end search()
 
     def show = {
@@ -113,4 +236,18 @@
     }
 
+    def copy = {
+        def assetToCopy = Asset.get(params.asset.id)
+
+        if(!assetToCopy) {
+            flash.message = "Please select an asset to copy."
+            redirect(action: list)
+            return
+        }
+
+        def assetInstance = new Asset(name: assetToCopy.name, section: assetToCopy.section)
+        assetInstance.properties = params
+        return ['assetInstance':assetInstance, assetToCopy: assetToCopy]
+    }
+
     def save = {
         def assetInstance = new Asset(params)
@@ -123,3 +260,107 @@
         }
     }
-}
+
+    def saveCopy = {
+        def result = saveCopySrvce(params)
+
+        if(!result.error) {
+            flash.message = g.message(code: "default.create.success", args: ["Asset", result.assetInstance.id])
+            redirect(action:show, id: result.assetInstance.id)
+            return
+        }
+
+        if(result.error.code == "default.not.found") {
+            flash.message = g.message(code: result.error.code, args: ["Asset", params.assetToCopy?.id])
+            redirect(action: list)
+            return
+        }
+
+        render(view:'copy', model:[assetInstance: result.assetInstance, assetToCopy: result.assetToCopy])
+    }
+
+    def saveCopySrvce(params) {
+        Asset.withTransaction { status ->
+            def result = [:]
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.assetInstance && m.field)
+                    result.assetInstance.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["Asset", params.id] ]
+                return result
+            }
+
+            result.assetToCopy = Asset.get(params.assetToCopy?.id)
+            if(!result.assetToCopy)
+                return fail(code:"default.not.found")
+
+            result.assetInstance =  new Asset(params)
+
+            if(result.assetInstance.hasErrors() || !result.assetInstance.save())
+                return fail(code:"default.create.failure")
+
+            def assetSubItemInstance1
+
+            // Copy subItems from level 2 and bellow.
+            def copyAssetSubItem = { assetSubItemToCopy, parentItem ->
+                def assetSubItemInstance = new AssetSubItem(name: assetSubItemToCopy.name,
+                                                                                            description: assetSubItemToCopy.description,
+                                                                                            parentItem: parentItem)
+
+                if(assetSubItemInstance.hasErrors() || !assetSubItemInstance.save())
+                    return fail(field:"subItems", code:"default.create.failure")
+
+                def i = 0
+                for(assetSubItem in assetSubItemToCopy.subItems) {
+                    call(assetSubItem, assetSubItemInstance)
+                    // Protect against endless recurrsion.
+                    i++
+                    if(i > 100)
+                        fail(code:"default.create.failure")
+                    // Stop if an error is flagged.
+                    if(result.error)
+                        break
+                }
+            } //copyAssetSubItem
+
+            // Copy the 1st level of subItems.
+            def copyAssetSubItem1 = { assetSubItemToCopy ->
+                assetSubItemInstance1 = new AssetSubItem(name: assetSubItemToCopy.name,
+                                                                                        description: assetSubItemToCopy.description,
+                                                                                        asset: result.assetInstance)
+
+                if(assetSubItemInstance1.hasErrors() || !assetSubItemInstance1.save())
+                    return fail(field:"assetSubItems", code:"default.create.failure")
+
+                def i = 0
+                for(assetSubItem in assetSubItemToCopy.subItems) {
+                    copyAssetSubItem(assetSubItem, assetSubItemInstance1)
+                    // Protect against endless recurrsion.
+                    i++
+                    if(i > 100)
+                        fail(code:"default.create.failure")
+                    // Stop if an error is flagged.
+                    if(result.error)
+                        break
+                }
+            } //copyAssetSubItem1
+
+            def i = 0
+            for(assetSubItem in result.assetToCopy.assetSubItems) {
+                copyAssetSubItem1(assetSubItem)
+                // Protect against endless recurrsion.
+                i++
+                if(i > 100)
+                    fail(code:"default.create.failure")
+                // Stop if an error is flagged.
+                if(result.error)
+                    break
+            }
+
+            // Success or not.
+            return result
+
+        } //end withTransaction
+    } // end saveCopySrvce
+
+} // end class
