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
