Configuration of the Device Hub

Fernando Amadoz | March 25, 2019

As it turns out, the cliché , “the more the merrier” is not always the case – nor appropriate for that matter. For instance, with respect to traffic, mortgages, IG “influencers”, and… the number of mouse clicks needed to complete an activity in a software system – more is definitely not better.

Where systems are deployed in large, busy warehouses, often the preference for users is going directly from the Acumatica screen to the printer in one click. In Acumatica, by default, this process requires at least a couple of additional clicks and a few seconds wait time while the preview window is loaded before the document is actually printed.

Configuration of the Device Hub

Starting from version 2018 R1, Acumatica included the Device Hub feature which is a process constantly running in the background polling for new documents to be printed, so that they can go from Acumatica to the printer queue in one click.

In this blog post we will review the process needed to make this happen by reviewing the installation, configuration and some good old code examples.

Printer Device Hub Installation

When the Acumatica ERP configuration wizard is run during the installation process, one of the configuration features available is the Install DeviceHub. Make sure this checkbox is selected and then carry on with the rest of the installation as it would normally be done.

Install DeviceHub

Printer Configuration in the Server

Given that all this configuration is being done in a fresh server, we need to make sure that at least one printer is correctly configured and operational. In this case, we will use the Microsoft Printer to PDF (redirected 2) printer

Printer Configuration in the Server

We verify that a test document is correctly added to the printer queue:

Verify that a test document is correctly added to the printer queue

Environment Configuration

In the Enable/Disable Features page (CS100000), make sure that the DeviceHub option is selected:

Environment Configuration

The DeviceHub application itself has 2 tabs to be configured: the site and the printers.

DeviceHub configuration
DeviceHub configuration

Please note that because the DeviceHub is a process that is run continuously in the background inquiring the system whether there is new information to be added to the printer queue, it is convenient to have a specific user created for this purpose.

After pressing on OK, the DeviceHub will start running and polling for new documents to be printed.

DeviceHub is running and polling for new documents to be printed

Also note that if the DeviceHub window is closed, the process will continue running in the background. To stop it, the task manager should be used.

DeviceHub status

Acumatica Configuration

The first thing that should be done is making the printer available in Acumatica. This is accomplished from the Printers page (SM206510).

In this page, the Update Printer List button is used to populate the grid with the printers previously defined in the DeviceHub.

Acumatica Configuration

Again, notice that that if no results are visible in the grid after pressing in the Update Printer List button, the Cancel button should be used to refresh the grid.

Completing this will update the list of available printers in the DeviceHub.

List of available printers in the DeviceHub

Having the printers already available in Acumatica, the next step is mapping the different users to different printers. For instance, this will allow the administrative team to use one printer, and the warehouse’s picker team utilizing another separate printer in a different location. This is done in the Printer Access page (SM106000).

This page is comprised of two tabs: the users and the associated printers.

The users and the associated printers

Notice that the selected users in the group do not require the inclusion of the DeviceHub user previously created as they serve different purposes: the former are used to identify the printer to be used and the latter is used for polling for jobs in the queue.

Also notice that printers and users may belong to multiple groups. The effect of this configuration is visible in the GroupMask field of the RelationGroup table where a combination of binary values allow the system to properly identify which printer to use.

With all the settings and configurations completed, next we will be extending the Inventory page and a new button to print a report will be added:

Step 1 – A graph is created to manage the printing logic:


using System;
using System.Collections.Generic;
using PX.SM;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.CR;
using PX.Objects.AR;

namespace AcumaticaDeviceHub
{
    public class PEPrintSLMaint : PXGraph<PEPrintSLMaint>
    {
        #region DAC PrintParameters
        [System.SerializableAttribute]
        public partial class PrintParameters : IBqlTable, PX.SM.IPrintable
        {
            #region PrintWithDeviceHub
            public abstract class printWithDeviceHub : IBqlField
            {
            }
            [PXDBBool]
            [PXDefault(typeof(FeatureInstalled<FeaturesSet.deviceHub>))]
            [PXUIField(DisplayName = "Print with DeviceHub")]
            public virtual bool? PrintWithDeviceHub { get; set; }
            #endregion
            #region DefinePrinterManually
            public abstract class definePrinterManually : IBqlField
            {
            }
            [PXDBBool]
            [PXDefault(true)]
            [PXUIField(DisplayName = "Define Printer Manually")]
            public virtual bool? DefinePrinterManually { get; set; }
            #endregion
            #region Printer
            public abstract class printerName : PX.Data.IBqlField { }
            [PX.SM.PXPrinterSelector]
            public virtual string PrinterName { get; set; }
            #endregion
        }
        #endregion

        public static class Parameters
        {
            public const string Inventory_ID = "Inventory_ID";
        }

        public static class Fields
        {
            public static readonly string Inventory_ID = string.Format("{0}.{1}", nameof(InventoryItem), nameof(Parameters.Inventory_ID));
        }

        #region Methods
        public void PrintReportInDeviceHub(string reportID, string actualReportID, List<string> nameGroupsList, Dictionary<string, string> parametersDictionary, string printerName, int numberPrint)
        {
            Guid loggedUser = this.Accessinfo.UserID;

            Users usersRow = PXSelect<Users,
                                    Where<Users.pKID, Equal<Required<Users.pKID>>>>
                                    .Select(this, loggedUser);
            string BinaryGroupMask = Convert.ToString(usersRow.GroupMask[0], 2);

            List<string> ListGroupsUser = new List<string>();
            for (int i = 0; i < BinaryGroupMask.Length; i++)
            {
                if (BinaryGroupMask.Substring(i, 1) != "0")
                {
                    String BinaryGroup = (BinaryGroupMask.Substring(i, 1).PadLeft(i + 1, '0')).PadRight(BinaryGroupMask.Length, '0');
                    ListGroupsUser.Add(Convert.ToString(Convert.ToDecimal(Convert.ToByte(BinaryGroup, 2))));
                }

            }

            Dictionary<string, string> groupsUserDictionary = new Dictionary<string, string>();
            foreach (String GroupUser in ListGroupsUser)
            {
                foreach (RelationGroup relationGroupRow in PXSelect<RelationGroup, Where<RelationGroup.active, Equal<Required<RelationGroup.active>>>>.Select(this, 1))
                {
                    if (Convert.ToString(relationGroupRow.GroupMask[0]) == GroupUser)
                    {
                        groupsUserDictionary[relationGroupRow.GroupName] = Convert.ToString(relationGroupRow.GroupMask[0]);
                        break;
                    }
                }
            }

            foreach (SMPrinter printersRow in PXSelect<SMPrinter, Where<SMPrinter.isActive, Equal<Required<SMPrinter.isActive>>>>.Select(this, 1))
            {
                foreach (string nameGroup in nameGroupsList)
                {
                    if (groupsUserDictionary.ContainsKey(nameGroup))
                    {
                        if (groupsUserDictionary[nameGroup] == Convert.ToString(printersRow.GroupMask[0]))
                        {
                            printerName = printersRow.PrinterName;
                            break;
                        }
                    }
                }
            }

            if (String.IsNullOrEmpty(printerName))
            {
                throw new PXException(Convert.ToString("The user is not active in an impression group"));
            }

            Dictionary<string, PXReportRequiredException> reportsToPrint = new Dictionary<string, PXReportRequiredException>();
            PrintParameters filter = new PrintParameters();
            filter.PrintWithDeviceHub = true;
            filter.DefinePrinterManually = true;
            filter.PrinterName = printerName;

            for (int i = 0; i < numberPrint; i++)
            {
                reportsToPrint = PX.SM.SMPrintJobMaint.AssignPrintJobToPrinter(reportsToPrint, parametersDictionary, filter,
                new NotificationUtility(this).SearchPrinter, ARNotificationSource.Customer, reportID, actualReportID, 16);
            }

            if (reportsToPrint != null)
            {
                PX.SM.SMPrintJobMaint.CreatePrintJobGroups(reportsToPrint);
            }
        }
        #endregion
    }
}

Step 2 – Extended logic to add the print button


using System.Collections;
using System.Collections.Generic;
using PX.Data;
using PX.Objects.IN;

namespace AcumaticaDeviceHub
{
    public class InventoryItemMaintSKExt : PXGraphExtension<InventoryItemMaint>
    {
        public PXAction<InventoryItem> printLabel;
        [PXUIField(DisplayName = "Print Item Balance", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
        [PXButton(CommitChanges = true)]
        public virtual IEnumerable PrintLabel(PXAdapter adapter)
        {
            PEPrintSLMaint tGPrintGraph = PXGraph.CreateInstance<PEPrintSLMaint>();

            Dictionary<string, string> parametersDictionary = new Dictionary<string, string>();
            parametersDictionary[PEPrintSLMaint.Fields.Inventory_ID] = this.Base.Item.Current.InventoryCD.Trim();
            List<string> ListGroupsFilter = new List<string> { "Group1", "Group2", "Group3" };
            tGPrintGraph.PrintReportInDeviceHub("IN615000", "IN615000", ListGroupsFilter, parametersDictionary, null, 1);

            return adapter.Get();
        }
    }
}

Example Result

Print Item Balance

When the user presses on the new Print item balance button, the report is added directly in the printer queue:

Printer queue

And the DeviceHub indicates the job being printed:

DeviceHub indicates the job being printed

The list of pending and processed print requests can be found in the Print Jobs page:

Print Jobs page

Conclusion

Take advantage of Device Hub feature in Acumatica R1 and later versions. Printing a few documents a day with this feature does not have a significant effect in many cases. However, for bustling warehouses where new pallets, boxes, and packages are continuously being assembled, a few seconds saved, per print request, add up at the end of the day.  This translates directly to increased efficiency and performance.  Because you know what they say, “time is money”.


*Click on the link to download the C# sample code in the article.

Fernando Amadoz

Founder and CEO of SkyKnack, an ERP solution provider that assists Acumatica partners in developing customizations, integrations and new products, as well as implementation and training services.

Categories: Developers

Subscribe to our bi-weekly newsletter

Compare 10 Leading Cloud Financial Solutions

Using Gartner’s new Magic Quadrant report for cloud core financial solutions, you can easily see what each has to offer—and why Acumatica was named a Visionary.