Index: trunk/grails-app/services/CreateDataService.groovy
===================================================================
--- trunk/grails-app/services/CreateDataService.groovy	(revision 440)
+++ trunk/grails-app/services/CreateDataService.groovy	(revision 441)
@@ -62,4 +62,5 @@
         createBaseAddressTypes()
         createBaseContactTypes()
+        createBaseInventoryItemPurchaseTypes()
 
         // Tasks
@@ -113,4 +114,5 @@
         createDemoManufacturers()
         createDemoProductionReference()
+        createDemoCostCodes()
 
         // Tasks
@@ -612,4 +614,29 @@
     }
 
+    def createBaseInventoryItemPurchaseTypes() {
+
+        // InventoryItemPurchaseType
+        def inventoryItemPurchaseTypeInstance
+
+        // InventoryItemPurchaseType #1
+        inventoryItemPurchaseTypeInstance = new InventoryItemPurchaseType(name: "Order Placed",
+                                                                                description: "Order has been placed.")
+        saveAndTest(inventoryItemPurchaseTypeInstance)
+
+        // InventoryItemPurchaseType #2
+        inventoryItemPurchaseTypeInstance = new InventoryItemPurchaseType(name: "Received B/order To Come",
+                                                                                description: "Order has been partially received.")
+        saveAndTest(inventoryItemPurchaseTypeInstance)
+        // InventoryItemPurchaseType #3
+        inventoryItemPurchaseTypeInstance = new InventoryItemPurchaseType(name: "Received Complete",
+                                                                                description: "Order has been partially received.")
+        saveAndTest(inventoryItemPurchaseTypeInstance)
+
+        // InventoryItemPurchaseType #4
+        inventoryItemPurchaseTypeInstance = new InventoryItemPurchaseType(name: "Invoice Approved",
+                                                                                description: "Invoice approved for payment.")
+        saveAndTest(inventoryItemPurchaseTypeInstance)
+    }
+
     def createDemoSuppliers() {
 
@@ -656,4 +683,18 @@
         productionReferenceInstance = new ProductionReference(name: "Tuesday Production")
         saveAndTest(productionReferenceInstance)
+    }
+
+    def createDemoCostCodes() {
+
+        // CostCode
+        def costCodeInstance
+
+        // CostCode #1
+        costCodeInstance = new CostCode(name: "RM Reelstand")
+        saveAndTest(costCodeInstance)
+
+        // CostCode #2
+        costCodeInstance = new CostCode(name: "CAPEX Reelstand")
+        saveAndTest(costCodeInstance)
     }
 
Index: trunk/grails-app/services/InventoryCsvService.groovy
===================================================================
--- trunk/grails-app/services/InventoryCsvService.groovy	(revision 440)
+++ trunk/grails-app/services/InventoryCsvService.groovy	(revision 441)
@@ -11,4 +11,6 @@
 
     boolean transactional = false
+
+    def dateUtilService
 
     def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib()
@@ -47,9 +49,4 @@
             if (multiPartFile.getSize() > fileMaxSize)
                 return fail(code: "default.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"])
-
-            //TODO: delete
-            def columnValue = ''
-            def columnIndex = 0
-            def numberOfColumns = 0
 
             def line = []
@@ -105,25 +102,8 @@
             def tempAlternateItems = []
 
-            def column = ''
-
             def nextLine = {
                     line = reader.readNext()
                     lineNumber ++
                     log.info "Processing line: " + lineNumber
-            }
-
-            def nextColumn = {
-
-                if( columnIndex < numberOfColumns ) {
-                        column = line[columnIndex].trim()
-                }
-                else {
-                    log.info "No more columns on line: " + lineNumber
-                    return false
-                }
-
-                columnIndex++
-                // Success.
-                return column
             }
 
@@ -280,5 +260,5 @@
                         // Manufacturer Type.
                         if(tempPreferredManufacturerItemAndType.size == 2) {
-                            tempPreferredManufacturerType = WordUtils.capitalizeFully(tempPreferredManufacturerItemAndType[1])
+                            tempPreferredManufacturerType = WordUtils.capitalize(tempPreferredManufacturerItemAndType[1])
                             manufacturerTypeInstance = ManufacturerType.findByName(tempPreferredManufacturerType)
                         }
@@ -291,5 +271,5 @@
 
                         preferredManufacturerInstance = new Manufacturer(name: tempPreferredManufacturerItem,
-                                                                                                            supplierType: manufacturerTypeInstance)
+                                                                                                            manufacturerType: manufacturerTypeInstance)
                         if(!preferredManufacturerInstance.save()) {
                             log.error "Failed to create preferred manufacturer on line: " + lineNumber
@@ -302,6 +282,6 @@
 
                 // Alternate Manufacturers.
-                tempAlternateManufacturers = parseInputList(inventoryParams.manufacturers)
-                inventoryParams.manufacturers = []
+                tempAlternateManufacturers = parseInputList(inventoryParams.alternateManufacturers)
+                inventoryParams.alternateManufacturers = []
 
                 for(tempManufacturer in tempAlternateManufacturers) {
@@ -309,10 +289,10 @@
                     tempManufacturerItem = WordUtils.capitalizeFully(tempManufacturerItemAndType[0])
 
-                    manufacturerInstance = Manufacturer.findByName(tempManufacturerItem)
-                    if(!manufacturerInstance) {
+                    alternateManufacturerInstance = Manufacturer.findByName(tempManufacturerItem)
+                    if(!alternateManufacturerInstance) {
 
                         // ManufacturerType.
                         if(tempManufacturerItemAndType.size == 2) {
-                            tempManufacturerType = WordUtils.capitalizeFully(tempManufacturerItemAndType[1])
+                            tempManufacturerType = WordUtils.capitalize(tempManufacturerItemAndType[1])
                             manufacturerTypeInstance = ManufacturerType.findByName(tempManufacturerType)
                         }
@@ -324,7 +304,7 @@
                         }
 
-                        manufacturerInstance = new Manufacturer(name: tempManufacturerItem,
+                        alternateManufacturerInstance = new Manufacturer(name: tempManufacturerItem,
                                                                                                 manufacturerType: manufacturerTypeInstance)
-                        if(!manufacturerInstance.save()) {
+                        if(!alternateManufacturerInstance.save()) {
                             log.error "Failed to create manufacturers on line: " + lineNumber
                             return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
@@ -332,5 +312,5 @@
                     }
 
-                    inventoryParams.manufacturers.add(manufacturerInstance)
+                    inventoryParams.alternateManufacturers.add(alternateManufacturerInstance)
                 }
 
@@ -345,5 +325,5 @@
                         // SupplierType.
                         if(tempPreferredSupplierItemAndType.size == 2) {
-                            tempPreferredSupplierType = WordUtils.capitalizeFully(tempPreferredSupplierItemAndType[1])
+                            tempPreferredSupplierType = WordUtils.capitalize(tempPreferredSupplierItemAndType[1])
                             supplierTypeInstance = SupplierType.findByName(tempPreferredSupplierType)
                         }
@@ -379,5 +359,5 @@
                         // SupplierType.
                         if(tempSupplierItemAndType.size == 2) {
-                            tempSupplierType = WordUtils.capitalizeFully(tempSupplierItemAndType[1])
+                            tempSupplierType = WordUtils.capitalize(tempSupplierItemAndType[1])
                             supplierTypeInstance = SupplierType.findByName(tempSupplierType)
                         }
@@ -397,5 +377,5 @@
                     }
 
-                    inventoryParams.suppliers.add(alternateSupplierInstance)
+                    inventoryParams.alternateSuppliers.add(alternateSupplierInstance)
                 }
 
@@ -475,5 +455,5 @@
                 // Save inventoryItem.
                 if(inventoryItemInstance.hasErrors() || !inventoryItemInstance.save()) {
-                    log.error "Failed to create item on line: " + column + "(" + lineNumber + ")"
+                    log.error "Failed to create item on line: " + lineNumber
                     log.debug inventoryItemInstance.errors
                     return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
@@ -641,5 +621,206 @@
         writer.close()
         return sw.toString()
-    } // end buildInventory
+    } // end buildInventory()
+
+    /**
+    * Import inventoryItemPurchases creating items as required.
+    */
+    def importInventoryItemPurchases(request) {
+        InventoryItemPurchase.withTransaction { status ->
+            def result = [:]
+
+            def kByteMultiplier = 1000
+            def fileMaxSize = 800 * kByteMultiplier
+            def logFileLink = g.link(controller: "appCore", action: "appLog") {"log"}
+
+            def multiPartFile = request.getFile('file')
+
+            InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream)
+            CSVReader reader = new CSVReader(sr)
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                reader.close()
+                result.error = [ code: m.code, args: m.args ]
+                return result
+            }
+
+            if(!multiPartFile || multiPartFile.isEmpty())
+                return fail(code: "default.file.not.supplied")
+
+            if (multiPartFile.getSize() > fileMaxSize)
+                return fail(code: "default.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"])
+
+            def line = []
+            def lineNumber = 0
+            def maxNumberOfColumns = 10
+            def inventoryItemPurchaseParams = [:]
+            def inventoryItemPurchaseProperties = ["inventoryItem", "purchaseOrderNumber", "quantity",
+                                                                                "inventoryItemPurchaseType",
+                                                                                "costCode", "enteredBy", "dateEntered",
+                                                                                "orderValueAmount", "orderValueCurrency", "invoiceNumber"]
+
+            def personInstance
+            def costCodeInstance
+            def inventoryItemInstance
+            def inventoryItemPurchaseInstance
+            def inventoryItemPurchaseTypeInstance
+
+            def nextLine = {
+                    line = reader.readNext()
+                    lineNumber ++
+                    log.info "Processing line: " + lineNumber
+            }
+
+            def parseInputDate = {
+                if( (it == null) || (it.trim() == '') ) {
+                    log.error "Failed to find any date on line: " + lineNumber
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+
+                def d = it.split("/").collect{it.trim()}
+                if(d.size() != 3) {
+                    log.error "Failed to find full date on line: " + lineNumber
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+                dateUtilService.makeDate(d[0], d[1], d[2])
+            }
+
+            // Get first line.
+            nextLine()
+
+            // Check for header line 1.
+            if(line != purchasesTemplateHeaderLine1) {
+                log.error "Failed to find header line 1. "
+                log.error "Required: " + purchasesTemplateHeaderLine1.toString()
+                log.error "Supplied: " + line.toString()
+                return fail(code: "default.file.no.header")
+            }
+
+            log.info "Header line found."
+
+            // Prepare the first body line.
+            nextLine()
+
+            // Primary loop.
+            while(line) {
+
+                if(line.size() > maxNumberOfColumns) {
+                    log.error "Too many columns on line: " + lineNumber
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+
+                // Ignore comment lines.
+                if(line.toString().toLowerCase().contains("comment")) {
+                    log.info "Comment line found."
+                    nextLine()
+                    continue
+                }
+
+                // Ignore example lines.
+                if(line.toString().toLowerCase().contains("example")) {
+                    log.info "Example line found."
+                    nextLine()
+                    continue
+                }
+
+                // Parse the line into the params map.
+                inventoryItemPurchaseParams = [:]
+                line.eachWithIndex { it, j ->
+                    inventoryItemPurchaseParams."${inventoryItemPurchaseProperties[j]}" = it.trim()
+                }
+
+                // Debug
+                log.debug " Supplied params: "
+                log.debug inventoryItemPurchaseParams
+
+                // Ignore blank lines.
+                if(inventoryItemPurchaseParams.inventoryItem == '') {
+                    log.info "No inventory item name found."
+                    nextLine()
+                    continue
+                }
+
+                // Inventory Item.
+                inventoryItemPurchaseParams.inventoryItem = WordUtils.capitalize(inventoryItemPurchaseParams.inventoryItem)
+                inventoryItemInstance = InventoryItem.findByName(inventoryItemPurchaseParams.inventoryItem)
+                if(!inventoryItemInstance) {
+                    log.error "Inventory item not found on line: " + lineNumber
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+                inventoryItemPurchaseParams.inventoryItem = inventoryItemInstance
+
+                // Quantity.
+                if(inventoryItemPurchaseParams.quantity.isInteger())
+                    inventoryItemPurchaseParams.quantity = inventoryItemPurchaseParams.quantity.toInteger()
+                else {
+                    log.error "Quantity is not a valid number on line: " + lineNumber
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+
+                // InventoryItemPurchaseType.
+                inventoryItemPurchaseParams.inventoryItemPurchaseType = WordUtils.capitalizeFully(inventoryItemPurchaseParams.inventoryItemPurchaseType)
+                inventoryItemPurchaseTypeInstance = InventoryItemPurchaseType.findByName(inventoryItemPurchaseParams.inventoryItemPurchaseType)
+                if(!inventoryItemPurchaseTypeInstance) {
+                    log.error "Inventory item purchase type not found on line: " + lineNumber
+                    log.debug inventoryItemPurchaseParams.inventoryItemPurchaseType
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+                inventoryItemPurchaseParams.inventoryItemPurchaseType = inventoryItemPurchaseTypeInstance
+
+                // CostCode.
+                if(inventoryItemPurchaseParams.costCode != '') {
+                    inventoryItemPurchaseParams.costCode = WordUtils.capitalizeFully(inventoryItemPurchaseParams.costCode)
+                    costCodeInstance = CostCode.findByName(inventoryItemPurchaseParams.costCode)
+                    if(!costCodeInstance) {
+                        costCodeInstance = new CostCode(name: inventoryItemPurchaseParams.costCode)
+                        if(!costCodeInstance.save()) {
+                            log.error "Failed to create cost code on line: " + lineNumber
+                            return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                        }
+                    }
+                    inventoryItemPurchaseParams.costCode = costCodeInstance
+                }
+
+                // Entered By.
+                inventoryItemPurchaseParams.enteredBy = inventoryItemPurchaseParams.enteredBy.toLowerCase()
+                personInstance = Person.findByLoginName(inventoryItemPurchaseParams.enteredBy)
+                if(!personInstance) {
+                    log.error "Entered by person not found on line: " + lineNumber
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+                inventoryItemPurchaseParams.enteredBy = personInstance
+
+                // Date Entered.
+                inventoryItemPurchaseParams.dateEntered = parseInputDate(inventoryItemPurchaseParams.dateEntered)
+
+                // Debug
+                log.debug "InventoryItemPurchaseParams: "
+                log.debug inventoryItemPurchaseParams
+
+                // Save inventoryItem.
+                log.info "Creating new purchase."
+                inventoryItemPurchaseInstance = new InventoryItemPurchase(inventoryItemPurchaseParams)
+
+                if(inventoryItemPurchaseInstance.hasErrors() || !inventoryItemPurchaseInstance.save()) {
+                    log.error "Failed to create item on line: " + lineNumber
+                    log.debug inventoryItemPurchaseInstance.errors
+                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
+                }
+
+                if(lineNumber % 100 == 0)
+                    cleanUpGorm()
+
+                if(!result.error) nextLine()
+            } //while(line)
+
+            // Success.
+            log.info "End of file."
+            reader.close()
+            return result
+
+
+         } //end withTransaction
+    } // end importInventoryItemPurchases()
 
     private getTemplateHeaderLine1() {
@@ -648,4 +829,9 @@
             "Average Delivery Time", "Average Delivery Period", "Supplier's Part Number", "Preferred Supplier", "Alternate Suppliers",
             "Manufacturer's Part Number", "Preferred Manufacturer", "Alternate Manufacturers", "Alternate Item", "Spare For"]
+    }
+
+    private getPurchasesTemplateHeaderLine1() {
+            ["Inventory Item*", "Purchase Order Number", "Quantity*", "Purchase Type*", "Cost Code*", "Entered By*",
+            "Date Entered*", "Order Value", "Currency", "Invoice Number"]
     }
 
Index: trunk/grails-app/services/InventoryItemService.groovy
===================================================================
--- trunk/grails-app/services/InventoryItemService.groovy	(revision 440)
+++ trunk/grails-app/services/InventoryItemService.groovy	(revision 441)
@@ -28,4 +28,5 @@
     def show(params) {
         def result = [:]
+
         def fail = { Map m ->
             result.error = [ code: m.code, args: ["InventoryItem", params.id] ]
@@ -37,4 +38,38 @@
         if(!result.inventoryItemInstance)
             return fail(code:"default.not.found")
+
+        def p = [:]
+
+        if(params.paginate == "purchases") {
+            params.showTab = "showPurchasingTab"
+            p.max = Math.min(params.max?.toInteger() ?: 10, 100)
+            p.offset = params.offset?.toInteger() ?: 0
+            p.sort = params.sort ?: null
+            p.order = params.order ?: null
+        }
+        else {
+            p.max = 10
+            p.offset = 0
+        }
+
+        result.inventoryItemPurchasesTotal = InventoryItemPurchase.countByInventoryItem(result.inventoryItemInstance)
+
+        result.inventoryItemPurchases = InventoryItemPurchase.withCriteria {
+                eq("inventoryItem", result.inventoryItemInstance)
+                maxResults(p.max)
+                firstResult(p.offset)
+                // Sorting:
+                // Default is to sort by order number then id.
+                // When a sortable column is clicked then we sort by that.
+                // If the sortable column clicked is order number then we add id as the second sort.
+                if(p.sort && p.order) {
+                    order(p.sort, p.order)
+                    if(p.sort == "purchaseOrderNumber") order('id', 'asc')
+                }
+                else {
+                    order('purchaseOrderNumber', 'desc')
+                    order('id', 'asc')
+                }
+            }
 
         result.showTab = [:]
@@ -46,14 +81,18 @@
                 result.showTab.movement =  new String("true")
                 break
+            case "showPurchasingTab":
+                result.showTab.purchasing =  new String("true")
+                break
             default:
                 result.showTab.inventory = new String("true")
         }
 
-        def p = [:]
         p.max = result.inventoryMovementListMax = 10
+        p.offset = 0
         p.order = "desc"
         p.sort = "id"
         result.inventoryMovementList = InventoryMovement.findAllByInventoryItem(result.inventoryItemInstance, p)
         result.inventoryMovementListTotal = InventoryMovement.countByInventoryItem(result.inventoryItemInstance)
+
 
         // Success.
Index: trunk/grails-app/services/InventoryPurchaseService.groovy
===================================================================
--- trunk/grails-app/services/InventoryPurchaseService.groovy	(revision 441)
+++ trunk/grails-app/services/InventoryPurchaseService.groovy	(revision 441)
@@ -0,0 +1,346 @@
+class InventoryPurchaseService {
+
+    boolean transactional = false
+
+    def authService
+    def dateUtilService
+    def inventoryMovementService
+
+    /**
+    * Calulates the quantities for an inventoryItem and purchaseOrderNumber.
+    * @param order An inventory puchase that was the source of the order.
+    * @returns A result map contianing the totalOrdered, totalReceived, totalRemaining, thisOrderRemaining.
+    */
+    def calcQuantities(order) {
+
+        def result = [:]
+
+        result.totalOrdered = 0
+        InventoryItemPurchase.withCriteria {
+            eq("inventoryItem", order.inventoryItem)
+            eq("purchaseOrderNumber", order.purchaseOrderNumber)
+            inventoryItemPurchaseType {
+                    eq("id", 1L)
+            }
+        }.each() {
+            result.totalOrdered += it.quantity
+        }
+
+        result.totalReceived = 0
+        InventoryItemPurchase.withCriteria {
+            eq("inventoryItem", order.inventoryItem)
+            eq("purchaseOrderNumber", order.purchaseOrderNumber)
+            inventoryItemPurchaseType {
+                or {
+                    eq("id", 2L)
+                    eq("id", 3L)
+                }
+            }
+        }.each() {
+            result.totalReceived += it.quantity
+        }
+
+        result.totalRemaining
+        if(result.totalOrdered > result.totalReceived)
+            result.totalRemaining = result.totalOrdered - result.totalReceived
+        else
+            result.totalRemaining = 0
+
+        result.thisOrderRemaining
+        if(result.totalRemaining > order.quantity)
+            result.thisOrderRemaining = order.quantity
+        else
+            result.thisOrderRemaining = result.totalRemaining
+
+        return result
+    }
+
+    def delete(params) {
+        InventoryItemPurchase.withTransaction { status ->
+            def result = [:]
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.inventoryItemPurchase && m.field)
+                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
+                return result
+            }
+
+            result.inventoryItemPurchaseInstance = InventoryItemPurchase.get(params.id)
+
+            if(!result.inventoryItemPurchaseInstance)
+                return fail(code:"default.not.found")
+
+            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
+            def purchaseTypeId = result.inventoryItemPurchaseInstance.inventoryItemPurchaseType.id
+
+            // Handle Invoice Payment Approved.
+            // Find and mark all orders as invoicePaymentApproved = false.
+            if(purchaseTypeId == 4) {
+                InventoryItemPurchase.withCriteria {
+                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem)
+                    eq("purchaseOrderNumber", result.inventoryItemPurchaseInstance.purchaseOrderNumber)
+                    inventoryItemPurchaseType {
+                            eq("id", 1L) // Order Placed.
+                    }
+                }.each() {
+                    it.invoicePaymentApproved = false
+                }
+            }
+
+            // Handle Received.
+            // Refuse to delete if payment approved.
+            // Find and reverse the matching movement.
+            // Find and mark all orders as receivedComplete = false.
+            if(purchaseTypeId == 2 || purchaseTypeId == 3) {
+
+                def paymentAlreadyApproved = InventoryItemPurchase.withCriteria {
+                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem)
+                    eq("purchaseOrderNumber", result.inventoryItemPurchaseInstance.purchaseOrderNumber)
+                    inventoryItemPurchaseType {
+                            eq("id", 4L) // Invoice Payment Approved.
+                    }
+                }
+
+                if(paymentAlreadyApproved)
+                    return fail(code:"inventoryItemPurchase.delete.failure.payment.approved")
+
+                def startOfDay = dateUtilService.getMidnight(result.inventoryItemPurchaseInstance.dateEntered)
+                def inventoryMovements = InventoryMovement.withCriteria {
+                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem )
+                    eq("quantity", result.inventoryItemPurchaseInstance.quantity)
+                    between("date", startOfDay, startOfDay+1)
+                    inventoryMovementType {
+                        eq("id", 3L) //purchaseReceived.
+                    }
+                    order('id', 'desc') // The newest one will be first.
+                }
+
+                def movementResult = inventoryMovementService.reverseMove(inventoryMovements[0])
+                if(movementResult.error)
+                    return fail(code:"inventoryMovement.quantity.insufficientItemsInStock")
+
+                InventoryItemPurchase.withCriteria {
+                    eq("inventoryItem", result.inventoryItemPurchaseInstance.inventoryItem)
+                    eq("purchaseOrderNumber", result.inventoryItemPurchaseInstance.purchaseOrderNumber)
+                    inventoryItemPurchaseType {
+                            eq("id", 1L) // Order Placed
+                    }
+                }.each() {
+                    it.receivedComplete = false
+                }
+            } // Handle Received.
+
+            // Handle Order Placed.
+            // Refuse to delete if we have received items.
+            // Deletion of received already requires payment approved to be deleted.
+            if(purchaseTypeId == 1) {
+                def calcQuantities = calcQuantities(result.inventoryItemPurchaseInstance)
+                if(calcQuantities.totalReceived > 0)
+                    return fail(code:"inventoryItemPurchase.delete.failure.received.exists")
+            }
+
+            // Success.
+            // By this stage everything should be handled and the delete call is allowed and expected to pass.
+            result.inventoryItemPurchaseInstance.delete()
+            return result
+
+        } // end withTransaction
+    }
+
+    def edit(params) {
+        def result = [:]
+        def fail = { Map m ->
+            result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
+            return result
+        }
+
+        result.inventoryItemPurchaseInstance = InventoryItemPurchase.get(params.id)
+
+        if(!result.inventoryItemPurchaseInstance)
+            return fail(code:"default.not.found")
+
+        // Success.
+        return result
+    }
+
+    def update(params) {
+        InventoryItemPurchase.withTransaction { status ->
+            def result = [:]
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.inventoryItemPurchaseInstance && m.field)
+                    result.inventoryItemPurchaseInstance.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
+                return result
+            }
+
+            result.inventoryItemPurchaseInstance = InventoryItemPurchase.get(params.id)
+
+            if(!result.inventoryItemPurchaseInstance)
+                return fail(code:"default.not.found")
+
+            // Optimistic locking check.
+            if(params.version) {
+                if(result.inventoryItemPurchaseInstance.version > params.version.toLong())
+                    return fail(field:"version", code:"default.optimistic.locking.failure")
+            }
+
+            result.inventoryItemPurchaseInstance.properties = params
+
+            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
+                return fail(code:"default.update.failure")
+
+            // Success.
+            return result
+
+        } //end withTransaction
+    }  // end update()
+
+    def save(params) {
+        InventoryItemPurchase.withTransaction { status ->
+            def result = [:]
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.inventoryItemPurchase && m.field)
+                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
+                return result
+            }
+
+            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
+            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
+            result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(1) // Order
+
+            // Fetch to prevent lazy initialization error.
+            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
+
+            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
+                return fail(code:"default.create.failure")
+
+            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
+
+            // success
+            return result
+
+        } // end withTransaction
+    } // save()
+
+    def receiveSave(params) {
+        InventoryItemPurchase.withTransaction { status ->
+            def result = [:]
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.inventoryItemPurchase && m.field)
+                    result.inventoryItemPurchase.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
+                return result
+            }
+
+            def order = InventoryItemPurchase.get(params.orderId)
+            if(!order)
+                return fail(code:"default.not.found")
+            result.orderId = order.id
+
+            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
+            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
+            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
+            result.inventoryItemPurchaseInstance.costCode = order.costCode
+            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
+
+            def calcQuantities = calcQuantities(order)
+            if(result.inventoryItemPurchaseInstance.quantity)
+                calcQuantities.totalReceived += result.inventoryItemPurchaseInstance.quantity
+
+            if(calcQuantities.totalReceived >= calcQuantities.totalOrdered) {
+                order.receivedComplete = true
+                result.inventoryItemPurchaseInstance.receivedComplete = true
+                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(3) // Received Complete.
+            }
+            else {
+                order.receivedComplete = false
+                result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(2) // Received B/oder to Come.
+            }
+
+            // Fetch to prevent lazy initialization error.
+            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
+
+            if(order.hasErrors() || !order.save())
+                return fail(code:"default.create.failure")
+
+            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
+                return fail(code:"default.create.failure")
+
+            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
+
+            // Perform the inventory movement.
+            if(result.inventoryItemPurchaseInstance.quantity > 0) {
+                def p = [:]
+                p.inventoryItem = result.inventoryItemPurchaseInstance.inventoryItem
+                p.quantity = result.inventoryItemPurchaseInstance.quantity
+                p.inventoryMovementType = InventoryMovementType.read(3)
+                def movementResult = inventoryMovementService.move(p)
+                if(movementResult.error)
+                    return fail(code:"default.create.failure")
+            }
+
+            // success
+            return result
+
+        } // end withTransaction
+    } // save()
+
+    def approveInvoicePaymentSave(params) {
+        InventoryItemPurchase.withTransaction { status ->
+            def result = [:]
+
+            def fail = { Map m ->
+                status.setRollbackOnly()
+                if(result.inventoryItemPurchaseInstance && m.field)
+                    result.inventoryItemPurchaseInstance.errors.rejectValue(m.field, m.code)
+                result.error = [ code: m.code, args: ["InventoryItemPurchase", params.id] ]
+                return result
+            }
+
+            def order = InventoryItemPurchase.get(params.orderId)
+            if(!order)
+                return fail(code:"default.not.found")
+            result.orderId = order.id
+
+            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
+
+            result.inventoryItemPurchaseInstance = new InventoryItemPurchase(params)
+            result.inventoryItemPurchaseInstance.enteredBy = authService.currentUser
+            result.inventoryItemPurchaseInstance.purchaseOrderNumber = order.purchaseOrderNumber
+            result.inventoryItemPurchaseInstance.costCode = order.costCode
+            result.inventoryItemPurchaseInstance.orderValueCurrency = order.orderValueCurrency
+            result.inventoryItemPurchaseInstance.inventoryItemPurchaseType = InventoryItemPurchaseType.read(4) // Approve.
+
+            order.invoicePaymentApproved = true
+            result.inventoryItemPurchaseInstance.invoicePaymentApproved = true
+
+            // Fetch to prevent lazy initialization error.
+            result.inventoryItemPurchaseInstance.inventoryItem.unitOfMeasure
+
+            if(!result.inventoryItemPurchaseInstance.invoiceNumber)
+                return fail(field:"invoiceNumber", code:"inventoryItemPurchase.invoiceNumber.required")
+
+            order.invoicePaymentApproved = true
+
+            if(order.hasErrors() || !order.save())
+                return fail(code:"default.create.failure")
+
+            if(result.inventoryItemPurchaseInstance.hasErrors() || !result.inventoryItemPurchaseInstance.save())
+                return fail(code:"default.create.failure")
+
+            result.inventoryItemId = result.inventoryItemPurchaseInstance.inventoryItem.id
+
+            // Success..
+            return result
+
+        } // end withTransaction
+    } // approveInvoicePaymentSave()
+
+} // end class
