Index: trunk/grails-app/controllers/AppCoreController.groovy
===================================================================
--- trunk/grails-app/controllers/AppCoreController.groovy	(revision 547)
+++ trunk/grails-app/controllers/AppCoreController.groovy	(revision 548)
@@ -213,12 +213,28 @@
     @Secured(['ROLE_AppAdmin'])
     def createBulkTestData = {
-        if(!createBulkDataService.create()) {
-            flash.message = "Bulk test data could not be created."
-            redirect(action: appAdmin)
-            return
-        }
-
-        // Success.
-        flash.message = "Bulk test data created."
+        def result = createBulkDataService.createAll()
+        if(!result.error) {
+            flash.message = g.message(code:"default.create.success", args:["Bulk test data", ''])
+            redirect(action: appAdmin)
+            return
+        }
+
+        flash.errorMessage = g.message(code: result.error.code, args: result.error.args)
+        redirect(action: appAdmin)
+    }
+
+    /**
+    * Allow admin to create bulk inventory test data.
+    */
+    @Secured(['ROLE_AppAdmin'])
+    def createBulkInventoryTestData = {
+        def result = createBulkDataService.createBulkInventoryTestData()
+        if(!result.error) {
+            flash.message = g.message(code:"default.create.success", args:["Bulk test data", ''])
+            redirect(action: appAdmin)
+            return
+        }
+
+        flash.errorMessage = g.message(code: result.error.code, args: result.error.args)
         redirect(action: appAdmin)
     }
Index: trunk/grails-app/i18n/messages.properties
===================================================================
--- trunk/grails-app/i18n/messages.properties	(revision 547)
+++ trunk/grails-app/i18n/messages.properties	(revision 548)
@@ -154,4 +154,7 @@
 task.associatedAssets.help=These assets are to be associated with this task, but costs will not be assigned.
 
+inventory.item.already.has.picture=Inventory item already has a picture, please delete the old picture first.
+inventory.item.picture.file.unrecognised=Image file [{0}]: type not recognised.
+
 inventoryMovement.quantity.insufficientItemsInStock=Could not complete operation, insufficient items in stock.
 inventoryMovement.inventoryItem.notFound=Inventory Item not found.
@@ -177,4 +180,5 @@
 default.file.not.supplied=No file supplied.
 default.file.no.header=The supplied file does not have the correct header lines, please see the template file.
+default.not.development.environment.failure=Could not complete operation, dev environment not detected.
 
 default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
@@ -314,6 +318,12 @@
 inventoryItem.search.text.recently.used.none.found=No items used in the last {0} days.
 
-# Reports
-report.too.many.results.warning=Warning over {0} results, please run report again.
+# Report error messages.
+report.error.too.many.results=Error: over {0} results, please run report again.
+report.error.no.locations.found=Error: no locations found, please run report again.
+report.error.too.many.locations=Error: over {0} locations, please run report again.
+report.error.no.inventory.items.found=Error: no inventory items found, please run report again.
+report.error.too.many.inventory.items=Error: over {0} inventory items, please run report again.
+
+# Report help balloon messages.
 report.stock.take.overview=Stock Take (Overview)
 report.stock.take.overview.help=Use this report to manage inventory stock take. Use in conjunction with the Stock Take (By Location) report.
Index: trunk/grails-app/services/CreateBulkDataService.groovy
===================================================================
--- trunk/grails-app/services/CreateBulkDataService.groovy	(revision 547)
+++ trunk/grails-app/services/CreateBulkDataService.groovy	(revision 548)
@@ -14,6 +14,8 @@
     def assignedGroupService
     def assignedPersonService
+    def inventoryItemService
 
     def sessionFactory
+    def grailsApplication
     def propertyInstanceMap = org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin.PROPERTY_INSTANCE_MAP
 
@@ -29,9 +31,14 @@
     * Make a run of data creation.
     */
-    def create() {
-        if(!GrailsUtil.environment == "development") {
-            log.error "Dev environment not detected, will NOT create bulk data."
-            return false
-        }
+    def createAll() {
+        def result = [:]
+
+        def fail = { Map m ->
+            result.error = [ code: m.code, args: m.args ]
+            return result
+        }
+
+        if(GrailsUtil.environment != "development")
+            return fail(code: 'default.not.development.environment.failure')
 
         log.info "Creating BULK data..."
@@ -40,24 +47,8 @@
         log.info "Creating persons..."
         createBulkTestPersons()
-
 //         createBulkTestSites()
 //         createBulkTestDepartments()
 //         createBulkTestSuppliers()
 //         createBulkTestManufacturers()
-
-        // Tasks
-        log.info "Creating tasks..."
-        createBulkTestTasks()
-
-//         createBulkTestEntries()
-//         createBulkTestAssignedGroups()
-//         createBulkTestAssignedPersons()
-//         createBulkTestTaskRecurringSchedules()
-
-        // Inventory
-//         createBulkTestInventoryStores()  /// @todo: Perhaps a 'createQuickStartData' method?
-//         createBulkTestInventoryLocations()
-//         createBulkTestInventoryGroups() /// @todo: Perhaps a 'createQuickStartData' method?
-//         createBulkTestInventoryItems()
 
         // Assets
@@ -71,8 +62,51 @@
 //         createBulkTestAssetSubItemExtenedAttributes()
 
+        // Inventory
+        log.info "Creating inventory..."
+//         createBulkTestInventoryStores()  /// @todo: Perhaps a 'createQuickStartData' method?
+        createBulkTestInventoryLocations()
+//         createBulkTestInventoryGroups() /// @todo: Perhaps a 'createQuickStartData' method?
+        createBulkTestInventoryItems()
+
+        // Tasks
+        log.info "Creating tasks..."
+        createBulkTestTasks()
+//         createBulkTestEntries()
+//         createBulkTestAssignedGroups()
+//         createBulkTestAssignedPersons()
+//         createBulkTestTaskRecurringSchedules()
+
         log.info "Creating BULK data...complete."
-        return true
-
-    }
+        return result
+
+    } // create()
+
+    /**
+    * Make a run of inventory data creation.
+    */
+    def createBulkInventoryTestData() {
+        def result = [:]
+
+        def fail = { Map m ->
+            result.error = [ code: m.code, args: m.args ]
+            return result
+        }
+
+        if(GrailsUtil.environment != "development")
+            return fail(code: 'default.not.development.environment.failure')
+
+        log.info "Creating BULK data..."
+
+        // Inventory
+        log.info "Creating inventory..."
+//         createBulkTestInventoryStores()  /// @todo: Perhaps a 'createQuickStartData' method?
+        createBulkTestInventoryLocations()
+//         createBulkTestInventoryGroups() /// @todo: Perhaps a 'createQuickStartData' method?
+        createBulkTestInventoryItems()
+
+        log.info "Creating BULK data...complete."
+        return result
+
+    } // createBulkInventoryTestData()
 
 /******************
@@ -177,5 +211,5 @@
         }
 
-    }
+    } // createBulkTestTasks()
 
     def createBulkTestEntries() {
@@ -202,4 +236,110 @@
 
     } // createBulkTestEntries()
+
+
+/**************************
+START OF INVENTORY
+**************************/
+
+    def createBulkTestInventoryLocations() {
+
+        def inventoryLocationResult
+        def p = [:]
+
+        def start = InventoryLocation.count() + 1
+        def end = start + 50
+
+        def range = start..end
+
+
+        def inventoryStore1 = InventoryStore.read(1)
+
+        def name = "Bulk test location "
+        def btName = ''
+
+        startTime = System.currentTimeMillis()
+        lastBatchStarted = startTime
+
+        range.each() {
+
+            if(it % 100 == 0) {
+                logStatus("Creating inventory location #" + it)
+                cleanUpGorm()
+            }
+
+            btName = name + it
+
+            p = [inventoryStore: inventoryStore1,
+                    name: btName]
+
+            inventoryLocationResult = new InventoryLocation(p).save()
+        } // each()
+
+    } // createBulkTestInventoryLocations()
+
+    def createBulkTestInventoryItems() {
+
+        def inventoryItemInstance
+        def p = [:]
+
+        def pictureResource = grailsApplication.mainContext.getResource('images/logo.png')
+
+        def start = InventoryItem.count() + 1
+        def end = start + 250
+
+        def range = start..end
+
+        def inventoryLocation
+        def inventoryLocationIndex = 0
+        def inventoryLocationList = InventoryLocation.findAll()
+        def unitOfMeasure2 = UnitOfMeasure.read(2)
+        def inventoryType1 = InventoryType.read(1)
+        def inventoryGroup1 = InventoryGroup.read(1)
+
+        def name = "Bulk test inventory item "
+        def btName = ''
+
+        startTime = System.currentTimeMillis()
+        lastBatchStarted = startTime
+
+        range.each() {
+
+            if(it % 100 == 0) {
+                logStatus("Creating inventory item #" + it)
+                cleanUpGorm()
+            }
+
+            // Spread the inventoryItems across all available locations.
+            if(inventoryLocationIndex < inventoryLocationList.size()) {
+                inventoryLocation = inventoryLocationList[inventoryLocationIndex]
+            }
+            else {
+                inventoryLocationIndex = 0
+                inventoryLocation = inventoryLocationList[inventoryLocationIndex]
+            }
+            inventoryLocationIndex++
+
+            // Change the name for each inventoryItem.
+            btName = name + it
+
+            p = [inventoryGroup: inventoryGroup1,
+                    inventoryType: inventoryType1,
+                    unitOfMeasure: unitOfMeasure2,
+                    inventoryLocation: inventoryLocation,
+                    name: btName,
+                    description: "Bulk test data",
+                    unitsInStock: 2,
+                    reorderPoint: 0]
+
+            inventoryItemInstance = new InventoryItem(p)
+            saveAndTest(inventoryItemInstance)
+
+            def pictureResult = inventoryItemService.savePicture(inventoryItemInstance, pictureResource)
+
+            if(pictureResult.error)
+                log.error pictureResult.error
+        } // each()
+
+    } // createBulkTestInventoryItems()
 
     /**
Index: trunk/grails-app/services/InventoryItemService.groovy
===================================================================
--- trunk/grails-app/services/InventoryItemService.groovy	(revision 547)
+++ trunk/grails-app/services/InventoryItemService.groovy	(revision 548)
@@ -226,3 +226,99 @@
     }
 
+    /**
+    * Save an inventory item picture.
+    * @param pictureSource A supported source to get the picture image from.
+    * Supported sources:
+    * HttpServletRequest e.g: 'request' var from controller to run getFile('file') against.
+    * ServletContextResource e.g: grailsApplication.mainContext.getResource('images/logo.png')
+    */
+    def savePicture(params, pictureSource) {
+        InventoryItem.withTransaction { status ->
+            def result = [:]
+
+            def kByteMultiplier = 1000
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.inventoryItemInstance && m.field)
+                    result.inventoryItemInstance.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: m.args ?: ["InventoryItem", params.id] ]
+                return result
+            }
+
+            result.inventoryItemInstance = InventoryItem.get(params.id)
+
+            if(!result.inventoryItemInstance)
+                return fail(code:"default.not.found")
+
+            // Optimistic locking check.
+            if(params.version) {
+                if(result.inventoryItemInstance.version > params.version.toLong())
+                    return fail(field:"version", code:"default.optimistic.locking.failure")
+            }
+
+            if(result.inventoryItemInstance.picture)
+                return fail(field:"picture", code:"inventory.item.already.has.picture")
+
+            def picture = new Picture(inventoryItem: result.inventoryItemInstance)
+            def imaging = new Imaging()
+            def images = null
+            def pictureFile
+            def pictureFileName = ''
+            def pictureInputStream
+
+            if(pictureSource instanceof javax.servlet.http.HttpServletRequest) {
+                def multiPartFile = pictureSource.getFile('file')
+                pictureFileName = multiPartFile.originalFilename
+
+                if(!multiPartFile || multiPartFile.isEmpty())
+                    return fail(code: "default.file.not.supplied")
+
+                if (multiPartFile.getSize() > Image.MAX_SIZE)
+                    return fail(code: "default.file.over.max.size", args: [Image.MAX_SIZE/kByteMultiplier, "kB"])
+
+                pictureInputStream = multiPartFile.inputStream
+            }
+            else if(pictureSource instanceof org.springframework.web.context.support.ServletContextResource) {
+                pictureFile = pictureSource.getFile()
+                pictureFileName = pictureFile.name
+
+                if ( !pictureFile.isFile() || (pictureFile.length() == 0) )
+                    return fail(code:"default.file.not.supplied")
+
+                if (pictureFile.length() > Image.MAX_SIZE)
+                    return fail(code:"default.file.over.max.size", args: [Image.MAX_SIZE/kByteMultiplier, "kB"])
+
+                pictureInputStream = pictureSource.inputStream
+            }
+            else {
+                    return fail(code:"default.file.not.supplied")
+            }
+
+            try {
+                images = imaging.createAll(result.inventoryItemInstance, picture, pictureInputStream)
+            }
+            catch(Exception ex) {
+                log.error("picture save", ex)
+                return fail(code:"inventory.item.picture.file.unrecognised", args: [pictureFileName])
+            }
+
+            images.each { image ->
+                picture.addToImages(image)
+            }
+
+            if(picture.hasErrors() || !picture.save())
+                return fail(code:"default.create.failure", args: ["Picture"])
+
+            result.inventoryItemInstance.picture = picture
+
+            if(result.inventoryItemInstance.hasErrors() || !result.inventoryItemInstance.save())
+                return fail(code:"default.create.failure")
+
+            // success
+            return result
+
+        } //end withTransaction
+    }
+
 } // end class
Index: trunk/grails-app/views/appCore/appAdmin.gsp
===================================================================
--- trunk/grails-app/views/appCore/appAdmin.gsp	(revision 547)
+++ trunk/grails-app/views/appCore/appAdmin.gsp	(revision 548)
@@ -11,7 +11,5 @@
         </div>
         <div class="body">
-            <g:if test="${flash.message}">
-                <div class="message">${flash.message}</div>
-            </g:if>
+            <g:render template="/shared/messages" />
             <div class="dialog">
                 <table>
@@ -66,8 +64,10 @@
                             <tr class="prop">
                                 <td valign="top" class="name">
-                                    <label>Test:</label>
+                                    <label>Bulk Test:</label>
                                 </td>
                                 <td valign="top" class="value">
-                                    <g:link action="createBulkTestData">Bulk</g:link> - Create a large volume of test data.
+                                    <g:link action="createBulkTestData">All Types</g:link> - Create a large volume of test data.
+                                    <br />
+                                    <g:link action="createBulkInventoryTestData">Inventory</g:link> - Create a large volume of inventory test data.
                                 </td>
                             </tr>
Index: trunk/grails-app/views/appCore/start.gsp
===================================================================
--- trunk/grails-app/views/appCore/start.gsp	(revision 547)
+++ trunk/grails-app/views/appCore/start.gsp	(revision 548)
@@ -13,7 +13,5 @@
         </div>
         <div class="body">
-            <g:if test="${flash.message}">
-                <div class="message">${flash.message}</div>
-            </g:if>
+            <g:render template="/shared/messages" />
             <g:hasErrors bean="${appCore}">
                 <div class="errors">
