class InventoryMovementService {

    boolean transactional = false

    def authService
    def authenticateService

    def reverseMove(params) {
        InventoryMovement.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.inventoryMovementInstance && m.field)
                    result.inventoryMovementInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["InventoryMovement", params.id] ]
                return result
            }

            result.inventoryMovementInstance = InventoryMovement.lock(params.id)

             if(!result.inventoryMovementInstance)
                fail(code:"default.not.found")

            // Fetch to prevent lazy initialization error.
            result.inventoryMovementInstance.inventoryItem.unitOfMeasure

            // Used type must have a task that is not complete or in the trash
            if(result.inventoryMovementInstance.inventoryMovementType.id == 1)  {

                // Unlike move, if the task does not exist at all then deletion is still allowed.
                if(result.inventoryMovementInstance.task?.trash)
                    return fail(field:"task", code:"task.operationNotPermittedOnTaskInTrash")

                if(result.inventoryMovementInstance.task?.taskStatus?.id == 3)
                    return fail(field:"task", code:"task.operationNotPermittedOnCompleteTask")

                // Check for authorisation on recurring tasks.
                if(result.inventoryMovementInstance.task?.taskRecurringSchedule) {
                    if(!authenticateService.ifAnyGranted('ROLE_AppAdmin,ROLE_Manager,ROLE_TaskManager,ROLE_InventoryManager'))
                        return fail(field:"task", code:"task.operationNotPermittedOnRecurringTaskWithoutAuth")
                }

            }

            result.inventoryItemInstance = InventoryItem.lock(result.inventoryMovementInstance.inventoryItem.id)
            result.taskId = result.inventoryMovementInstance.task?.id

            if(!result.inventoryItemInstance)
                return fail(field:"inventoryItem", code:"inventoryMovement.inventoryItem.notFound")

            // Reverse the movement of inventory.
            if(!result.inventoryMovementInstance.inventoryMovementType.incrementsInventory) {
                result.inventoryItemInstance.unitsInStock += result.inventoryMovementInstance.quantity
            }
            else {
                if(result.inventoryItemInstance.unitsInStock >= result.inventoryMovementInstance.quantity) {
                    result.inventoryItemInstance.unitsInStock -= result.inventoryMovementInstance.quantity
                }
                else {
                    return fail(field:"quantity", code:"inventoryMovement.quantity.insufficientItemsInStock")
                }
            }

            if(!result.inventoryItemInstance.save())
                return fail(code: "default.delete.failure")

            // Success..
            result.inventoryMovementInstance.delete()
            return result

        } // end withTransaction
    } //end reverseMove()

    def move(params) {
        InventoryMovement.withTransaction { status ->
            def result = [:]

            def fail = { Map m ->
                status.setRollbackOnly()
                if(result.inventoryMovementInstance && m.field)
                    result.inventoryMovementInstance.errors.rejectValue(m.field, m.code)
                result.error = [ code: m.code, args: ["InventoryMovement", params.id] ]
                return result
            }

            result.inventoryMovementInstance = new InventoryMovement(params)

            result.inventoryMovementInstance.person = authService.currentUser

            // Fetch to prevent lazy initialization error.
            result.inventoryMovementInstance.inventoryItem.unitOfMeasure

            // Used type must have a task that is not complete or in the trash
            if(result.inventoryMovementInstance.inventoryMovementType.id == 1)  {

                if(!result.inventoryMovementInstance.task)
                    return fail(field:"task", code:"task.notFound")

                if(result.inventoryMovementInstance.task.trash)
                    return fail(field:"task", code:"task.operationNotPermittedOnTaskInTrash")

                if(result.inventoryMovementInstance.task.taskStatus.id == 3)
                    return fail(field:"task", code:"task.operationNotPermittedOnCompleteTask")

                // Check for authorisation on recurring tasks.
                if(result.inventoryMovementInstance.task.taskRecurringSchedule) {
                    if(!authenticateService.ifAnyGranted('ROLE_AppAdmin,ROLE_Manager,ROLE_TaskManager,ROLE_InventoryManager'))
                        return fail(field:"task", code:"task.operationNotPermittedOnRecurringTaskWithoutAuth")
                }
            }

            // Bail early if validation fails.
            if(!result.inventoryMovementInstance.validate())
                return fail(code:"default.create.failure")

            def inventoryItem = InventoryItem.lock(result.inventoryMovementInstance.inventoryItem.id)
            result.taskId = result.inventoryMovementInstance.task?.id

            if(!inventoryItem)
                return fail(field:"inventoryItem", code:"inventoryMovement.inventoryItem.notFound")

            // Perform the movement of inventory.
            if(result.inventoryMovementInstance.inventoryMovementType.incrementsInventory) {
                inventoryItem.unitsInStock += result.inventoryMovementInstance.quantity
            }
            else {
                if(inventoryItem.unitsInStock >= result.inventoryMovementInstance.quantity) {
                    inventoryItem.unitsInStock -= result.inventoryMovementInstance.quantity
                }
                else {
                    return fail(field:"quantity", code:"inventoryMovement.quantity.insufficientItemsInStock")
                }
            }

            if(!inventoryItem.save() || !result.inventoryMovementInstance.save())
                return fail(code:"default.create.failure")

            // Success..
            return result

        } // end withTransaction
    } // end move()

} // end class
