Index: branches/features/purchaseOrders/grails-app/services/CreateDataService.groovy
===================================================================
--- branches/features/purchaseOrders/grails-app/services/CreateDataService.groovy	(revision 897)
+++ branches/features/purchaseOrders/grails-app/services/CreateDataService.groovy	(revision 898)
@@ -17,4 +17,5 @@
     def appConfigService
     def searchableService
+    def purchaseOrderService
     def inventoryItemService
     def assignedGroupService
@@ -1740,7 +1741,5 @@
 
     def createDemoPurchaseOrderNumbers() {
-        for (int i=10000; i<10100; i++) {
-            saveAndTest(new PurchaseOrderNumber(value:"P${i}"))
-        }
+        def r = purchaseOrderService.savePurchaseOrderNumberRange("P", 10000, 10100)
     }
 
Index: branches/features/purchaseOrders/grails-app/services/PurchaseOrderService.groovy
===================================================================
--- branches/features/purchaseOrders/grails-app/services/PurchaseOrderService.groovy	(revision 897)
+++ branches/features/purchaseOrders/grails-app/services/PurchaseOrderService.groovy	(revision 898)
@@ -1,5 +1,44 @@
+/**
+ * Provides a service class for the PurchaseOrder and PurchaseOrderNumber domain classes.
+ */
 class PurchaseOrderService {
 
     boolean transactional = false
+
+    def savePurchaseOrderNumberRange(prefix, startOfRange, endOfRange, suffix="") {
+        PurchaseOrderNumber.withTransaction { status ->
+            def result = [:]
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.purchaseOrderNumber && m.field)
+                    result.purchaseOrderNumber.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["PurchaseOrderNumber"] ]
+                return result
+            }
+
+            // Sanity checks.
+            if(startOfRange < 0)
+                return fail(code:"default.create.failure")
+            if(endOfRange < 0)
+                return fail(code:"default.create.failure")
+            // Auto swap range.
+            if(endOfRange < startOfRange)
+                (startOfRange, endOfRange) = [endOfRange, startOfRange]
+
+            def r
+            def p = [:]
+            for(i in startOfRange..endOfRange) {
+                p.value= "${prefix}${i}${suffix}"
+                r = savePurchaseOrderNumber(p)
+                if(r.error)
+                    return fail(code: r.code)
+                    
+            }
+
+            // Success.
+            return result
+        }
+    }
 
     PurchaseOrderNumber findNextUnusedPurchaseOrderNumber() {
@@ -37,3 +76,26 @@
         return drafts
     }
+
+    def savePurchaseOrderNumber(params) {
+        PurchaseOrderNumber.withTransaction { status ->
+            def result = [:]
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.purchaseOrderNumber && m.field)
+                    result.purchaseOrderNumber.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["PurchaseOrderNumber", params.id] ]
+                return result
+            }
+
+            result.purchaseOrderNumber = new PurchaseOrderNumber(params)
+
+            if(result.purchaseOrderNumber.hasErrors() || !result.purchaseOrderNumber.save())
+                return fail(code:"default.create.failure")
+
+            // Success.
+            return result
+        }
+    }
+
 }
Index: branches/features/purchaseOrders/src/groovy/com/henyo/BaseUnitTestCase.groovy
===================================================================
--- branches/features/purchaseOrders/src/groovy/com/henyo/BaseUnitTestCase.groovy	(revision 898)
+++ branches/features/purchaseOrders/src/groovy/com/henyo/BaseUnitTestCase.groovy	(revision 898)
@@ -0,0 +1,39 @@
+package com.henyo
+
+import grails.test.GrailsUnitTestCase
+import org.springframework.transaction.TransactionStatus
+
+/**
+ * Base unit test class with mock extensions.
+ * http://blog.henyo.com/2009/04/mocking-transactions-for-unit-testing.html
+ */ 
+class BaseUnitTestCase extends GrailsUnitTestCase {
+
+    def statusControls
+
+    protected void setUp() {
+        super.setUp()
+        statusControls = []
+    }
+
+    protected void tearDown(){
+        statusControls.each{
+            it.verify()
+        }
+        statusControls.clear()
+        super.tearDown()
+    }
+
+    def mockForTransaction(Class clazz,boolean expectRollback = false){
+        registerMetaClass(clazz)
+        def statusControl = mockFor(TransactionStatus)
+        statusControls << statusControl
+        if(expectRollback)
+            statusControl.demand.setRollbackOnly(1..1) { println 'setRollbackOnly called'}
+        def status = statusControl.createMock()
+        clazz.metaClass.'static'.withTransaction = {
+            Closure callable ->  callable.call(status)
+        }
+        return statusControl
+    }
+}
Index: branches/features/purchaseOrders/test/unit/PurchaseOrderServiceTests.groovy
===================================================================
--- branches/features/purchaseOrders/test/unit/PurchaseOrderServiceTests.groovy	(revision 897)
+++ branches/features/purchaseOrders/test/unit/PurchaseOrderServiceTests.groovy	(revision 898)
@@ -1,3 +1,4 @@
 import grails.test.*
+import com.henyo.BaseUnitTestCase
 import static org.junit.Assert.assertThat
 import static org.hamcrest.CoreMatchers.equalTo
@@ -6,5 +7,5 @@
  * Unit tests for PurchaseOrderService class.
  */
-class PurchaseOrderServiceTests extends GrailsUnitTestCase {
+class PurchaseOrderServiceTests extends BaseUnitTestCase {
     def pos = new PurchaseOrderService()
 
@@ -21,7 +22,45 @@
     }
 
+    void testSavePurchaseOrderNumberExpectCommit() {
+        mockForTransaction(PurchaseOrderNumber)
+
+        def p = [value: "P100"]
+        def r = pos.savePurchaseOrderNumber(p)
+
+        assert ! r.error
+        assert PurchaseOrderNumber.count() == 1
+    }
+
+    void testSavePurchaseOrderNumberExpectRollback() {
+        mockForTransaction(PurchaseOrderNumber, true)
+
+        def p = [value: ""]
+        def r = pos.savePurchaseOrderNumber(p)
+
+        assert r.error
+        assert PurchaseOrderNumber.count() == 0
+    }
+
+    void testSavePurchaseOrderNumberRangeExpectCommit() {
+        mockForTransaction(PurchaseOrderNumber)
+
+        def r = pos.savePurchaseOrderNumberRange("P", 1000, 1009)
+
+        assert ! r.error
+        assert PurchaseOrderNumber.count() == 10
+    }
+
+    void testSavePurchaseOrderNumberRangeExpectRollback() {
+        mockForTransaction(PurchaseOrderNumber, true)
+
+        def r = pos.savePurchaseOrderNumberRange("P", -1000, 1009)
+
+        assert r.error
+        assert PurchaseOrderNumber.count() == 0
+    }
+
     void testFindsFirstUnusedPurchaseOrderWhenAllUnused() {
-        createTenPurchaseOrderNumbers()
-        assertThat PurchaseOrderNumber.list().size(), equalTo(10)
+        mockForTransaction(PurchaseOrderNumber)
+        pos.savePurchaseOrderNumberRange("P", 1000, 1009)
 
         def next = pos.findNextUnusedPurchaseOrderNumber()
@@ -31,5 +70,6 @@
 
     void testFindsFirstUnusuedPurchaseOrderWhenSomeUsed() {
-        createTenPurchaseOrderNumbers()
+        mockForTransaction(PurchaseOrderNumber)
+        pos.savePurchaseOrderNumberRange("P", 1000, 1009)
         createPurchaseOrders(4)
 
@@ -40,5 +80,6 @@
 
     void testFindsNullIfNoUnusedPurchaseOrderNumbers() {
-        createTenPurchaseOrderNumbers()
+        mockForTransaction(PurchaseOrderNumber)
+        pos.savePurchaseOrderNumberRange("P", 1000, 1009)
         createPurchaseOrders(10)
 
@@ -49,5 +90,6 @@
 
     void testGetOrCreatePurchaseOrderWithExistingOrder() {
-        createTenPurchaseOrderNumbers()
+        mockForTransaction(PurchaseOrderNumber)
+        pos.savePurchaseOrderNumberRange("P", 1000, 1009)
         createPurchaseOrders(3)
         def params=[purchaseOrderNumber:[id:2]]
@@ -60,5 +102,6 @@
 
     void testGetOrCreatePurchaseOrderWithNoExistingOrder() {
-        createTenPurchaseOrderNumbers()
+        mockForTransaction(PurchaseOrderNumber)
+        pos.savePurchaseOrderNumberRange("P", 1000, 1009)
         createPurchaseOrders(3)
         createSuppliers(1)
@@ -72,5 +115,6 @@
 
     void testFindsDraftPurchaseOrderNumbers() {
-        createTenPurchaseOrderNumbers()
+        mockForTransaction(PurchaseOrderNumber)
+        pos.savePurchaseOrderNumberRange("P", 1000, 1009)
         createPurchaseOrders(3)
         releaseOrder(PurchaseOrder.get(2))
@@ -103,10 +147,4 @@
     }
 
-    private createTenPurchaseOrderNumbers() {
-        for (int i = 1000; i < 1010; i++) {
-            new PurchaseOrderNumber(value: "P${i}").save(failOnError: true)
-        }
-    }
-
     def releaseOrder(PurchaseOrder po) {
         po.ordered = new Date()
