WHS Mobile Device Development – Process Guide Framework

Process Guide Framework - new WHS Mobile Device Process Development.

Extend standard functionality

The following enums should be extended: WHSWorkExecuteMode and  WHSWorkCreationProcess for work-related processes or  WHSWorkActivity for indirect.

To automatically enable process guide functionality for a new process:
/// <summary>
/// Class extension for <c>WHSRFMenuItemTable</c> table.
/// </summary>
[ExtensionOf(tableStr(WHSRFMenuItemTable))]
final class VKWHSRFMenuItemTableTbl_Extension
{
    /// <summary>
    /// Checks if the work activity is required to use process guide framework.
    /// </summary>
    /// <returns>true if process guide framework must be used; otherwise, false</returns>
    protected boolean workActivityMustUseProcessGuideFramework()
    {
        boolean ret = next workActivityMustUseProcessGuideFramework();
        
        // or this.WorkCreationProcess == WHSWorkCreationProcess::NewValue
        if (!ret && this.WorkActivity == WHSWorkActivity::VKPrintShippingLabel)
        {
            ret = true;
        }

        return ret;
    }
}

Controller

The main entry point for the process. Controller class linked with the process by WHSWorkExecuteMode attribute and the value it provides. The controller class indicates the initial step class (initialStepNamemethod) and defines the step sequence.
In this case, the process is circular, after successful step execution we again ask for a license place to print another label. In the usual multi-step (multiple screens) guide process case, the second parameter of navigationRoute.addFollowingStep method should identify the next step and there should be several navigationRoute.addFollowingStep calls for each separate step. The last step (last navigationRoute.addFollowingStep call) should reference the first step. For the process to begin from scratch.

/// <summary>
/// Pring shipping label process guide controller
/// </summary>
[WHSWorkExecuteMode(WHSWorkExecuteMode::VKPrintShippingLabel)]
public class VKProcessGuidePrintShippingLabelController extends ProcessGuideController
{
    protected ProcessGuideStepName initialStepName()
    {
        return classStr(VKProcessGuidePrintShippingLabelLPStep);
    }

    protected ProcessGuideNavigationRoute initializeNavigationRoute()
    {
        ProcessGuideNavigationRoute navigationRoute = new ProcessGuideNavigationRoute();

        // In this case, the process is circular, after successful step execution we again ask for a license place to print another label
        navigationRoute.addFollowingStep(classStr(VKProcessGuidePrintShippingLabelLPStep), classStr(VKProcessGuidePrintShippingLabelLPStep));

        return navigationRoute;
    }
}
In case of complex navigation instead of the initializeNavigationRoute method, you can use the navigationAgentFactory method. Please see WHSProcessGuideSortPutawayController as an example:
    protected ProcessGuideNavigationAgentAbstractFactory navigationAgentFactory()
    {
        return new WHSProcessGuideSortPutawayNavigationAgentFactory();
    }

If you experience issues with controller class initialization you can flush the cache using the browser and the following URL (add in front the host):
/?cmp=dat&mi=SysClassRunner&cls=SysFlushAOD
Or from the main menu: System administration / Setup / Refresh elements

Step creation: general information

Each step consists of the PageBuilder class, which defines available controls and the Step class, which is responsible for the logic. Also, the NavigationAgentFactory and NavigationAgent classes might be engaged to indicate the next step (if your step navigation is complex and for example from the current step, you can jump to several different steps depending on the data entered by the user)
  • PageBuilder extends ProcessGuidePageBuilder
    See as an example:
    WHSProcessGuidePopulateLPAndSortPositionPageBuilder
    WHSProcessGuideSortPutawayPromptItemPageBuilder
  • Step extends ProcessGuideStep
    See as an example:
    WHSProcessGuidePopulateLPAndSortPositionStep
    WHSProcessGuideSortPutawayPromptItemStep
  • NavigationAgent extends ProcessGuideNavigationAgent
    See as an example:
    WHSProcessGuideSortPutawayPopulateLPAndSortPositionNavigationAgent
    WHSProcessGuideSortPutawayPromptSortPositionIdNavigationAgent
  • NavigationAgentFactory extends ProcessGuideNavigationAgentAbstractFactory
    See as an example:
    WHSProcessGuideSortPutawayNavigationAgentFactory

Step creation: PageBuilder class

PageBuilder has two main methods:
  • addDataControls() to add fields 
  • addActionControls() to add buttons
You can overwrite base addControls methods (which calls those two) and structure the page more thoroughly like for the cases where the lists are used. In the standard example for the WHSProcessGuidePromptWaveLabelSeriesPageBuilder class where buildHeader / buildTableContents / buildPagingControls are used:
    protected void addControls(ProcessGuidePage _page)
    {
        super(_page);
        WhsrfPassthrough pass = controller.parmSessionState().parmPass();
        WHSRFMenuItemTable menuItemTable = WHSRFMenuItemTable::find(pass.lookup(ProcessGuideDataTypeNames::MenuItem));

        this.buildHeader(_page, menuItemTable, this.defaultFilter(pass));
        this.buildTableContents(_page, menuItemTable, pass);
        this.buildPagingControls(_page, menuItemTable);

    }
WHSProcessGuidePromptWaveLabelSeriesStep step class where you can find what item the user is clicked on controller.parmClickedData().
Metadata search: "type:class,method name:buildTableContents"

Our example PageBuilder class content:
/// <summary>
/// Builder page class for Print shippint label / Container ID step
/// </summary>
[ProcessGuidePageBuilderName(classStr(VKProcessGuidePrintShippingLabelContainerIdPageBuilder))]
public class VKProcessGuidePrintShippingLabelContainerIdPageBuilder extends ProcessGuidePageBuilder
{
    protected WhsrfPassthrough pass;
    
    protected void addDataControls(ProcessGuidePage _page)
    {
        // to access information in the pass if you need to display something
        pass = controller.parmSessionState().parmPass();
        
        str successMessage = pass.lookupStr(VKProcessGuideDataTypeNamesPrintShippingLabel::VKSuccessMessage);
        if (successMessage)
        {
            _page.addLabel(VKProcessGuideDataTypeNamesPrintShippingLabel::VKSuccessMessage, successMessage, #WHSRFUndefinedDataType);
            pass.remove(VKProcessGuideDataTypeNamesPrintShippingLabel::VKSuccessMessage);
        }

        _page.addTextBox(ProcessGuideDataTypeNames::ContainerId, "@WAX1422", extendedTypeNum(WHSContainerId));

        // Use to identify that process is in detour session
        if (this.isInDetourSession())
        {
        }
    }

    protected void addActionControls(ProcessGuidePage _page)
    {
        _page.addButton(step.createAction(ProcessGuideActionNames::ActionOk), true);
        _page.addButton(step.createAction(ProcessGuideActionNames::ActionCancelExitProcess));
    }
}

Step creation: Step class

Step class has following main methods:
  • validateControls() - validate input from the user. Here you need to use processingResult.fieldValues as pass (controller.parmSessionState().parmPass()) is not yet updated.
  • isComplete() - idientifies if the step process sucessfully completed. It true - will mark the step as completed (same as this.markAsCompleted() in the step class)
  • doExecute() - main logic execution method. Not each step need to have it. If the step is used only to collect data from the user - you can ommit this method implementation.
  • pageBuilderName() - links step class with page builder
Execution sequence (in case of clicking standard OK button):
  • validateControls()
  • isComplete()
  • doExecute()
However sequence depends where you call super() in your doExecute() method. The sequence above is valid only if the super() call is called first, which you should normally do.
  • ProcessGuideStep.doExecute().processData().validateControls()
  • ProcessGuideStep.doExecute().saveProcessedDataToSessionState() - saves data from processingResult.fieldValues to pass (WhsrfPassthrough: controller.parmSessionState().parmPass())
  • ProcessGuideStep.doExecute().isComplete()
  • ProcessGuideStep.doExecute().markAsCompleted() - if isComplete() = true
Our example Step class content:
/// <summary>
/// The <c>VKProcessGuidePrintShippingLabelContainerIdStep</c> represents a step that prompts the user for container id information
/// </summary>
[ProcessGuideStepName(classStr(VKProcessGuidePrintShippingLabelContainerIdStep))]
public class VKProcessGuidePrintShippingLabelContainerIdStep extends ProcessGuideStep
{
    WhsrfPassthrough            pass;
    WHSContainerTable           containerTable;
    TMSRouteSegmentContainer    routeSegmentContainer;
    WHSPrinterName              printerName;

    protected boolean isComplete()
    {
        pass = controller.parmSessionState().parmPass();

        boolean                 complete        = true;

        if (complete && !containerTable.ShipCarrierTrackingNum)
        {
            this.addError("@TRX:NoShippingTrackingNumberFound");
            complete = false;
        }
        else
        {
            routeSegmentContainer = containerTable.findRouteSegmentContainerVK();
        }

        if (complete && !routeSegmentContainer.ContainerShippingLabel)
        {
            this.addError("@TRX:NoContainerShippingLabelFound");
            complete = false;
        }

        if (complete)
        {
            printerName = this.identifyPrinterName();

            if (!printerName)
            {
                this.addError("Unable to identify printer name.");
                complete = false;
            }
        }

        return complete;
    }

    protected void validateControls()
    {
        if (controller.parmClickedAction() == ProcessGuideActionNames::ActionOK)
        {
            this.validateContainerId();
        }
    }

    private void validateContainerId()
    {
        WHSContainerId containerId = processingResult.fieldValues.lookupStr(ProcessGuideDataTypeNames::ContainerId);
        containerTable = WHSContainerTable::findByContainerId(containerId);

        if (!containerTable)
        {
            throw error(strFmt("@WAX:WHSProcessGuideScanParametersToPrintContainerLabelStep_ContNotExistError", containerId));
        }
    }

    protected void doExecute()
    {
        super();

        if (!processingResult.isErrorState && containerTable && routeSegmentContainer.ContainerShippingLabel)
        {
            TMSPrintContainerShippingLabel printShippingContainerLabel = TMSPrintContainerShippingLabel::createInstanceVK(routeSegmentContainer.ContainerShippingLabelType);
            printShippingContainerLabel.printLabel(printerName, routeSegmentContainer.ContainerShippingLabel);

            pass.insert(VKProcessGuideDataTypeNamesPrintShippingLabel::VKSuccessMessage, strFmt("Shipping label for container ID %1 sent to %2 printer.", containerTable.ContainerId, printerName));

            pass.remove(ProcessGuideDataTypeNames::ContainerId);

            printerName = '';
            containerTable.clear();
            routeSegmentContainer.clear();
        }
    }

    protected ProcessGuidePageBuilderName pageBuilderName()
    {
        return classStr(VKProcessGuidePrintShippingLabelContainerIdPageBuilder);
    }

    /// <summary>
    /// Identifies printer name by mobile device user and warehouse
    /// </summary>
    /// <returns>Printer name</returns>
    protected WHSPrinterName identifyPrinterName()
    {
        WHSWorkUserDefaultLabelPrinterTable workUserDefaultLabelPrinterTable;
        InventLocationId                    inventLocationId = InventDim::find(containerTable.InventDimId).InventLocationId;
            
        if (inventLocationId)
        {
            select firstonly RecId, PrinterName from workUserDefaultLabelPrinterTable
                    where workUserDefaultLabelPrinterTable.UserId               == pass.parmUserId()
                        &&workUserDefaultLabelPrinterTable.InventLocationId     == InventLocationId;
        }

        if (!workUserDefaultLabelPrinterTable)
        {
            select firstonly RecId, PrinterName from workUserDefaultLabelPrinterTable
                    where workUserDefaultLabelPrinterTable.UserId               == pass.parmUserId();
        }

        return workUserDefaultLabelPrinterTable.PrinterName;
    }

}

Helper classes

For convenience, you can create parm methods as an extension to WhsrfPassthrough:
/// <summary>
/// CoC extension for WhsrfPassthrough class
/// WhsrfPassthrough "parm" extensions
/// WHSRFPassthrough_Extension - standard
/// WhsrfPassthroughProcessGuide_Extension - internal (why?)
/// </summary>
[ExtensionOf(classStr(WhsrfPassthrough))]
final class VKWhsrfPassthrough_Extension
{
    public WHSContainerId parmContainerIdVK(WHSContainerId _containerId = '')
    {
        if (!prmIsDefault(_containerId))
        {
            this.insert(ProcessGuideDataTypeNames::ContainerId, _containerId);
        }

        return this.lookupStr(ProcessGuideDataTypeNames::ContainerId);
    }

    /// <summary>
    /// Cleans product inventory dimensions from the pass
    /// Work simular to:
    /// - initProductInventDimFromPass()
    /// - initInventDimFromPass()
    /// - initTrackingInventDimFromPass()
    /// - createProductInventDimFromPass()
    /// 
    /// However they are internal/private
    /// </summary>
    public void cleanProductInventDimFromPassVK()
    {
        Enumerator      fieldEnumerator = InventDim::dimProductDimFieldList().getEnumerator();

        while (fieldEnumerator.moveNext())
        {
            FieldId     inventDimFieldId    = fieldEnumerator.current();
            FieldName   fieldName           = fieldId2name(tableNum(InventDim), inventDimFieldId);

            if (this.hasValue(fieldName))
            {
                this.remove(fieldName);
            }
        }
    }

}

Helper methods as an extension to ProcessGuideStep to conveniently add different types of messages:
/// <summary>
/// CoC for ProcessGuideStep class
/// </summary>
[ExtensionOf(classstr(ProcessGuideStep))]
final class VKProcessGuideStepCls_Extension
{
    public void addError(str _error)
    {
        ProcessGuideMessageData processGuideMessageData = ProcessGuideMessageData::construct();

        processGuideMessageData.message = _error;
        processGuideMessageData.level   = WHSRFColorText::Error;

        processingResult.messageData    = processGuideMessageData;
        processingResult.isErrorState   = true;
    }

    public void addWarning(str _warning)
    {
        ProcessGuideMessageData processGuideMessageData = ProcessGuideMessageData::construct();

        processGuideMessageData.message = _warning;
        processGuideMessageData.level   = WHSRFColorText::Warning;

        processingResult.messageData    = processGuideMessageData;
    }

    public void addMessage(str _message)
    {
        ProcessGuideMessageData processGuideMessageData = ProcessGuideMessageData::construct();

        processGuideMessageData.message = _message;
        processGuideMessageData.level   = WHSRFColorText::Success;

        processingResult.messageData    = processGuideMessageData;
    }

}

Objects

  • ProcessGuideDataTypeNames - contains constants representing logical data type names used for controls (pass names)
  • WhsrfPassthrough - used to store form state during processing RF controls

Additional links




 

Search

About

DaxOnline.org is free platform that allows you to quickly store and reuse snippets, notes, articles related to Dynamics AX.

Authors are allowed to set their own AdSense units and "buy me a coffee" link.
Join us.

Blog Tags