Tuesday, June 9, 2015

Set Default Client Configuration

The following is how to set the default AX client configuration.  Usually the reason that I want to do this is so that Visual Studio will open using the cus layer.

The standard instructions on using the AX 2012 Client Configuration tool are here:
https://technet.microsoft.com/en-us/library/aa569651.aspx

But they don't clearly say how to set the default configuration.  There are a few ways to do this, but this is what I do:

  1. Open the AX 2012 Client Configuration tool.
  2. Click Manage, Create Configuration.
  3. Edit the connection, developer, etc information as needed on your new configuration.
  4. Click Manage, Save configuration as file and choose a file name.  Now you have an axc file that you can share with someone else.
  5. Click Manage, Import configuration from file and import the axc file that you just saved.  This is the key step that actually sets up a new default configuration that is saved in the registry.

When this is done correctly, you will open up the AX 2012 Configuration utility and the Configuration dropdown will show your desired default configuration.  Double check that the settings are correct.  If not, edit them and click Apply to save them to the registry.


View/edit in the registry
As an aside, assuming the active configuration store is "Registry" (noted in the image above), the configuration setting values can be seen using RegEdit at the following key:

HKEY_CURRENT_USER\Software\Microsoft\Dynamics\5.0\Configuration.  (or 6.0, etc)

It's possible to make some changes to values there, but it's not normally necessary.

Edit the Shortcut
As an alternative, you can create a shortcut to Visual Studio that references a specific axc file as described in the following link.  The downside of this method is that it only works when launching VS from your shortcut, not from within the AX AOT.
https://msdn.microsoft.com/en-us/library/gg889291.aspx


Friday, June 27, 2014

AX 2009 Open Transaction Editing Mark All

A while back I posted some "Mark All" code for AX 4.0 here: http://natepaine.blogspot.com/2010/10/mark-all-for-open-cust-trans-and-open.html

Today I had a need to write the same code in AX 2009.  It's much simpler in 2009.  Just create a form method "customMarkAll" and call it from a button on the custOpenTrans form.

void customMarkAll()
{
    CustTransOpen   custTransOpenBuffer;
    int             linesProcessed;
    ;

    for (custTransOpenBuffer = custTransOpen_ds.getFirst(false) ? custTransOpen_ds.getFirst(false) : custTransOpen_ds.cursor();
        custTransOpenBuffer; custTransOpenBuffer = custTransOpen_ds.getnext())
    {
        if (manager.getTransMarked(custTransOpenBuffer) == NoYes::No)
        {
            manager.updateTransMarked(custTransOpenBuffer, NoYes::Yes);
            linesProcessed++;
        }
    }

    element.updateDesignDynamic();

    //refresh the screen
    element.redraw();

    box::info(strfmt("%1 Vouchers Marked",linesProcessed));
}


Friday, April 18, 2014

Custom dialog form buttons

Sometimes I need to make a dialog form from scratch.  Of course a dialog has the OK and Cancel buttons in the lower right of the screen.  Like this:

Every time I have to do this I try about 27 combinations before I find/remember the correct control settings.  My first instinct is to use a ButtonGroup, set ArrangeMethod, and maybe AlignChild, AlignChildren, AlignControl, Width, Left, bah!  What the heck!

What works is to create a Group (not ButtonGroup), with Left = Auto(right), FrameType = None, and Columns = 2 (or however many buttons you have).  Then add your buttons into the group with all default properties.

Passing multiple parameters between forms
While I'm at it, another typical issue that comes up with custom dialog forms is how to pass multiple values between forms.  There are plenty of blog posts on the standard args.parmXYZ options, and I often think there should be a better provision for this...but in the end it seems like using con2str and str2con and args.parm is the most reasonable way to go.

Maybe this will help you save a few minutes...or help me save a few minutes next time I do it.

Wednesday, February 5, 2014

2014 Dynamics AX Technical Conference Summary

It's day 3 at the AX Tech conference and, as I'm organizing my own thoughts on the conference, I thought I'd make a little extra effort to do it as a blog post.

First off, it is always a pleasure to be surrounded by so many talented individuals.  In my day to day life as a freelance consultant, I'm often the sole AX resource in the room.  It's refreshing to get to talk shop with people who do what I do every day.  Not to mention getting to interact with Microsoft personnel who are architecting and coding the next generation technology.

There are about 1300 attendees this year - customers, partners, and Microsoft employees.  As usual the Europeans seem to have the headcount advantage.  The content of the conference is focused on new functionality to be released in R3 of AX 2012.  The estimated RTM date is May 2014.

"Rainier" or AX 2015

The next version of AX is code named Rainier...and will probably be named AX 2015.  The message being communicated here is that AX 2013 R3 will define the functionality going forward and that Rainier will primarily represent technological changes, that is the new HTML5 client and a shift towards the cloud in general.  It's nice to know this so that we can dig in to 2012 R3 learning with the confidence that, functionality wise, we'll have a few years of not-to-much change.

HTML 5 Client

It has been announced that the big change coming to AX is a shift from the current rich client to a web client (HTML 5) as the primary desktop interface.  It's not clear if this change will significantly change the look and feel from 2012.  As a developer, I am very interested to see how the current code base will be transformed to be performant web-based code.  That seems like a monumental task.

Azure Infrastructure as a service

As a first step towards a cloud-hosted solution, Microsoft has made it easy to use Azure to host an instance of AX.  To be clear, this is infrastructure as a service, nothing more.  Azure is simply providing hardware that you can install your standard license of on-premises AX.

Warehouse Management /Transportation Management

The area that I was most interested in was the expansion of warehouse and transportation management capabilities.  This is the result of Microsoft's purchase of Blue Horseshoe's WAX and TRAX products.

Some new capabilities on the warehouse management side include an out-of-the-box web-based mobile inventory control client, new reservation logic allowing reservation at the warehouse level rather than the item level, license plating to track in-transit inventory, and a pick & put directive engine to prescribe inventory movement work in detail - even  based on the products properties (i.e. heavy item on bottom shelf).  Developers note: two new inventory dimensions are coming: inventory status and license plate.

The mobile client supports these functions: purchase order receipt, purchase order put-away, transfer order receipt, transfer order picking, and sales order picking.  From a developer's perspective it has a nice extensible class structure inside of AX that drives the mobile app functionality.

On the transportation management side some notable capabilities are advanced planning features for multi-destination shipment (multiple delivery addresses on one order), shipment consolidation (multiple orders for same customer), split order planning (one order, multiple truck loads).  Another key offering is the rate shopping workbench which provides a framework for calculating (or querying vendors for) shipping rates and then making a decision on which carrier to use for a particular shipment.  Multi-modal, multi-segment, multi-stop routes, accessorial/fuel charges, and routing constraints are supported by the framework.  Developers note: a good portion of the new transportation management code is in C# dlls and not able to be modified.  This is how the code was originally written before Microsoft purchased it.

Mobile device client

There are several mobile device clients that have appeared with AX 2012 R3.  First, there is the HTML-based WAX/TRAX mobile client discussed above. Then there are several of what Microsoft is calling 'companion apps' which are a set of native apps written for Windows Phone, iPhone, and Android.  These cover the functions of entering expenses, entering time sheets, and approval of expenses/timesheets.  The approach and architecture of these offerings are entirely different.  The companion apps look nice but are completely closed code.  The warehouse mobile client looks as plain as you can imagine, but is fully customizable. I was hoping to get insight into the new HTML5 client architecture at some point, but none of the R3 offerings touch on that.  Developers note: the companion apps use the Azure Service Bus to access AX.

Well, that covers just a small portion of the notes I took at the conference...but hopefully I've been able to pass along the highlights.

Thursday, October 3, 2013

Copy User Groups from one User to another

I'm doing more AX administration work than usual lately.  When a new employee arrives, the typical statement is "give them the same security as _____" (some existing user).  The code below will copy user groups from one user to another.

I select the New user row and click Copy User Groups (the new button I created)




That opens a dialog with the To User populated.

And a infolog displays the results:

So, here's the code.

In your new button:
//NDP 10/3/13
void clicked()
{
    ;
    if (securityHelper::copyUserGroups("",UserInfo.id))
    {
        //refresh the list panel on the user group tab
        listPanel.fill(true);
    }
}

Create a new class called "securityHelper" with the static method below.  You could also put this code in the SysUserInfo form if you like.
//NDP 10/3/13
static boolean copyUserGroups(UserId _fromUserId = '', UserId _toUserId = '')
{
    UserGroupList       userGroupList, userGroupDupeCheck, userGroupListInsert;
    userInfo            userInfo;
    UserId              fromUserId, toUserId;
    int                 i;
    boolean             okToRun, deleteGroupAssignmentFirst;
    dialog              dialog;
    dialogField         fromUser, toUser, deleteFirst;
    FormStringControl   formStringControl;
    FormGroupControl    formGroupControl;
    ;

    dialog = new Dialog("Copy User Groups");
    fromUser = dialog.addField(TypeId(UserId),"Copy Groups From User");
    toUser = dialog.addField(TypeId(UserId),"Copy Groups To User");
    deleteFirst = dialog.addField(TypeId(NoYesId),"Delete existing To User groups before copy?");

    if (_fromUserId)
    {
        fromUser.value(_fromUserId);
    }
    if (_toUserId)
    {
        toUser.value(_toUserId);
    }

    formStringControl = fromUser.fieldControl();
    formStringControl.mandatory(true);
    formStringControl = toUser.fieldControl();
    formStringControl.mandatory(true);

    if (dialog.run())
    {
        fromUserId = fromUser.value();
        toUserId = toUser.value();

        if (fromUserId == '' || toUserId == '')
        {
            error("User Group Copy Cancelled: Please enter From and To User Ids");
            return false;
        }

        select firstonly recid from userInfo
            where userInfo.Id == fromUserId;

        if (userInfo.RecId == 0)
        {
            error(strfmt("User %1 not found",fromUserId));
            return false;
        }

        select firstonly recid from userInfo
            where userInfo.Id == toUserId;

        if (userInfo.RecId == 0)
        {
            error(strfmt("User %1 not found",toUserId));
            return false;
        }

        if (deleteFirst.value() == NoYes::Yes)
        {
            delete_from userGroupList
                where userGroupList.userId == toUserId;

            info(strfmt("Existing groups were deleted for %1",toUserId));
        }

        while select userGroupList
            where userGroupList.UserId == fromUserId
        {
            select firstonly recid from userGroupDupeCheck
                where userGroupDupeCheck.userId == toUserId
                && userGroupDupeCheck.groupId == userGroupList.groupId;

            if (userGroupDupeCheck.RecId == 0)
            {
                userGroupListInsert.clear();
                userGroupListInsert.initValue();
                userGroupListInsert.groupId = userGroupList.groupId;
                userGroupListInsert.userId = toUserId;
                userGroupListInsert.insert();
                i++;
                info(userGroupListInsert.groupId);
            }
        }
        info(strfmt("%1 groups copied from %2 to %3",i,fromUserId, toUserId));
        return true;
    }
    else
    {
        info("User Group Copy Cancelled");
        return false;
    }
}

The code validates a number of things...but it does allow you to take yourself out of the admin group.  So be careful of that!

Enjoy.

Thursday, September 12, 2013

Is that a Credit or Debit?

Are you a developer who sometimes talks with accountants?  I am.  Over the years I've gotten a pretty good handle on typical accounting stuff...but for some reason I'll still forget how to tell if a transaction is a debit or a credit.

Here's a screen shot of the a transactions screen to help you (me) remember.  Make sure the Amount column is wide enough, and then you will see some numbers on the left and some on the right.  Debits are on the left, credits are on the right.


Friday, September 6, 2013

Bank Reconciliation Fix

Here's a quick job to 'unreconcile' a bank account statement in Dynamics AX 2009.

'Cleared' check boxes will remain checked for the statement, so you can easily re-reconcile.  Set the Bank Account id and the Bank Account Statement Date in the code to suite your situation.

//Use this job to un-reconcile a bank account statement.
static void UnRenconcileBankAccount(Args _args)
{
    bankAccountStatement    bankAccountStatement;
    BankAccountTrans        bankAccountTrans;
    int                     i;
    ;

    select firstonly forupdate bankAccountStatement
        where bankAccountStatement.AccountId == '1050'                          //set this
        && bankAccountStatement.AccountStatementDate == mkDate(31,7,2013);      //set this (D,M,Y)

    if (bankAccountStatement)
    {
        ttsbegin;

        bankAccountStatement.ReconcileDate = datenull();
        bankAccountStatement.update();

        while select forupdate bankAccountTrans
            where bankAccountTrans.AccountId == bankAccountStatement.AccountId
            && bankAccountTrans.AccountStatementDate == bankAccountStatement.AccountStatementDate
            && bankAccountTrans.Reconciled == NoYes::Yes
        {
            bankAccountTrans.Reconciled = NoYes::No;
            bankAccountTrans.doUpdate();
        }
        ttscommit;
    }
}