Log me off damn it!!!

One of the coolest new features in the Retail POS for AX 2012 R2 is the ability to host custom controls and entirely change it’s look and feel. These custom controls implement IPOSCustomControl and import a reference to IApplication using MEF. That way you have access to the transaction and can effectively run any POS operation you like. Shane Erstad has written a great introduction to Adding a Custom User Control to the POS if you require further information on the topic.

The problem arises when you first try to use the Log Off operation…


There are a substantial amount of operations you can perform using Application.RunOperation().  Let’s say you wanted a button that added an item to the current transaction. You would need to call the following code from the button’s click event to do this:

// Add an item to the transaction
// Application.RunOperation(PosisOperations.ItemSale, “<ItemId>”);

You can perform all sorts of different functions such as discounting the current transaction, locking the terminal or even closing the shift. The next step was to programmatically log off from the POS. The following code should achieve this desired result but the outcome isn’t quite what is expected.

// Logoff
// Application.RunOperation(PosisOperations.LogOff, null);

The issue is that it will in fact log you off and deactivate the shift but it will not actually close all of the active user controls and return to the login screen. The status bar normally looks something similar to this:

status.bar.normal

After executing the logoff operation the screen will look exactly the same but the status will be updated to show that the user is no longer associated with the POS like so:

status.bar.loggedoff

What I really expected was to be navigated to the logon screen so that I could now authenticate as a different user. When you use a button grid and configure the operation as a PosisOperations.LogOff  it works correctly so I figured something fishy was up. Enter Reflector.NET so we could decompile this bad boy. Sparing you the hours of investigation, the offending piece of code I discovered was on the main POS form in the event handler for the button grid:

private void buttonGrid1_FunctionEvent(object sender, ButtonGridEventArgs e)
{
    this.lastActiveDateTime = DateTime.Now;
    this.POSApp.RunOperation((PosisOperations) e.Action, e.ActionProperty);
    if (!ApplicationSettings.Terminal.TerminalOperator.OperatorLoggedOn())
    {
        this.OperatorLogOn();
        this.lastActiveDateTime = DateTime.Now;
    }
}

Whenever any operation is executed from the button grid the code checks immediately to see if there is a logged on operator. If not, the POS is navigated to the startup screen so the user can re-authenticate. This is why the log off operation works when called from a button grid but not from custom code.

So how can we go about executing the same logic? It’s a little funky unfortunately.The OperatorLogOn() method is private but upon further investigation I found it was called in several places including the logged off event handler:

private void posApp_POSUserLoggedOff()
{
    this.OperatorLogOn();
    this.lastActiveDateTime = DateTime.Now;
}

We need to be able to raise this POSUserLoggedOff event so that the main form will execute the code above and achieve the desired result of returning to the login screen. I added a reference to Microsoft.Dynamics.Retail.Pos.SystemCore and this afforded me the ability to access POSApp via a static accessor. I could then use reflection to raise this event to all of the clients handling this event like so:

AXApplication.RunOperation(PosisOperations.LogOff, true);

POSApp posApp = (POSApp)PosApplication.Instance.PosLegacyApp;

FieldInfo fieldInfo = posApp.GetType().GetField(“POSUserLoggedOff”, BindingFlags.Instance | BindingFlags.NonPublic);

if (fieldInfo != null)
{
    var eventDelegate = (MulticastDelegate) fieldInfo.GetValue(posApp);
    if (eventDelegate != null)
    {
        foreach (var handler in eventDelegate.GetInvocationList())
            handler.Method.Invoke(handler.Target, null);
    }
}

This will log the user off and then cause the main POS form to actually reload all of the user controls that are part of the screen layout. Using reflection to ninja-invoke delegates isn’t the most solid solution in the world but until I can find a more robust one it will certainly suffice. For now it’s a pretty insignificant code change and…

ohitson

 

Leave a Reply

Your email address will not be published. Required fields are marked *