Graphical representation of warehouse usage in Dynamics AX

A often recurring requirement is a graphical inventory overview showing the usage of locations. There are many ways how to implement such a solution. One simple way is to use data shapes in Visio and link them to Dynamics Ax data.

graphical inventory usage


Since every warehouse is different, you have to create a separate Visio drawing for each one. Visio provides you with good standard shapes to draw a floor plan. In this example I am using a simple drawing of a warehouse with one door and 12 locations. In my example a square in Visio represents a WMSLocation in Dynamics AX. Create one Visio file per warehouse and save it on a file share.

Warehouse floor plan


Next create a view for each warehouse on the Dynamics AX database. Ask you DBA to secure access to the view for your users. Here is an example SQL code I am using to fetch data from Location 11 and 12 ( Contoso Demo Data)

(w.VOLUME > 0) AND

Link SQL Data to Visio shapes

In the Visio main menu go to the Data tab and Link Data with Shapes. Go through the wizard and connect to your SQL Server and the view you have just created. This will open the External Data window showing the results from the SQL query.

Load Dynamics AX data in Visio

In the Visio drawing panel select the first square that represents a location. Right click on a record in the external data grid and select Link to selected shape. A chain symbol will appear next to the record, showing you that this record is new linked to a shape in your drawing.

Right click on the shape that is now linked to a record in the external data. In the shapes context menu go to shape data and open edit data graphic. Here you can add and format the column values from the record to the shape. In my case I’ve formatted the InventLocationId as Header and the FreePercent as progress bar.


Once you have formatted one shape you can copy & paste it multiple times. You only need to selected the copy and then right click on the corresponding row in the external data and link to shape. This will update the shape data with the values from the new record.

View in Dynamics AX

Finally some work is needed in Dynamics AX to view the Visio drawing. At the InventLocation table add a new field using the FileName extended data type. Add the field in the InventLocation form, so you can provide a Visio file path for each InventLocation.

Visio floor plan for invent location

Create a new form and add an ActiveX control. Use the Microsoft Visio Document class.


Overwrite the init() method of the form to load the Visio document.

public void init()
    InventLocation inventLocation;

    if(element.args() &&
       element.args().record() &&
       element.args().record().TableId == tableNum(inventLocation))
        inventLocation = element.args().record();
        if(inventLocation.ERPFilenameOpen != "")

Create a display menu item for the form and add it to the InventLocation form. Make sure to set the InventLocation as data source for the menu item. Now if you have created a Visio document and provided the file path in the InventLocation record, by clicking on the menu item you can see a graphical representation of your warehouse.

Open Invent Location floor plan from Dynamics AX

PowerBI finance report grouped by main accounts and cost center

A customer recently asked to create a PowerBI report in order to group and compare ledger postings. The report had to meet the following requirements:

  • Compare two time periods
  • Group postings by account and type (e.g. Assets)
  • Postings on a certain cost center need to be shown separately
  • Grouping of accounts and cost centers are defined by a key user in Excel
    e.g. 1001 – 1104 and 5001 – 5002 are “Assets”
  • Cost center groups are defined by using wildcard style
    e.g. 601500 – 61500 and Costcenter ?6?? is Development/Compliance

Dynamics AX ledger postings grouped by account and cost center

Report Data from Dynamics AX

First, we loaded the report data from Dynamics AX into PowerBI. The data contained the LedgerJournalTrans joined with the LedgerAccountView. The Dimenions were renamed to Department, CostCenter and CostUnit.

from LedgerJournalTrans as T
where ACCOUNTTYPE <= 2

Report Definition Data

Next we defined and loaded the report definition from Excel. We used to sheets, one for the definition of the two time frames and one for the group definition

date range for ledger postings

grouping definition for ledger postings

Like initially described the Accounts sheet defines the grouping of postings regarding their account number. For example in line 2 and 3 postings on  accounts 1101 .. 1104 and 5001 .. 5002 shall be grouped as “Assets”. Postings on accounts 601500 .. 606300 with a cost center where the 2nd character is 6 shall be grouped separately as “Development/Compliance” regardless if they are also part of the group “Expenses”.

Both Excel worksheets were loaded into PowerBI. The Accounts was modifed to replace an empty value in the Costcenter column with the text “%”. This was done to use the Costcenter value in a SQL statement with a Like clause (see section “Calling function from the account definition”).

Query Parameter

We added 3 parameters to the PowerBI called FromAccountNum, ToAccountNum and CostCenter. The default values are the smallest account number for the FromAccountNum, the largest account number for the ToAccountNum and the text % for the CostCenter.

PowerBI parameters

Next we changed the query of LedgerJournalTrans and added the parameter to the query. This can be done used “Edit query” on the data set and opening “Advanced Editor”

Parameters in PowerBI query

The new query text in the advanced editor looked like this

Source = Sql.Database(„localhost“, „DynamicsAx“, [Query=“select T.TRANSDATE, T.AMOUNTCURCREDIT,T.AMOUNTCURDEBIT, T.ACCOUNTNUM,V.ACCOUNTNAME,DIMENSION as DEPARTMENT,DIMENSION2_ as COSTCENTER,DIMENSION3_ as COSTUNIT #(lf)from LedgerJournalTrans as T#(lf)join LEDGERACCOUNTVIEW as V on T.ACCOUNTNUM = V.ACCOUNTNUM #(lf)where ACCOUNTTYPE <= 2 AND T.ACCOUNTNUM >= „&FromAccountNum&“ AND T.ACCOUNTNUM <= „&ToAccountNum&“ AND T.DIMENSION2_ like ‚“&CostCenter&“‚ #(lf)order by T.ACCOUNTNUM,T.TRANSDATE#(lf)“, CreateNavigationProperties=false])

With the parameter in the query and the default values set to the parameters the dataset did not change. Next we added a new function to the LedgerJournalTrans. This can be done from the context menu of the query “Create Function”. PowerBI inspects the query statement and creates function parameter for each parameter in the query. In this case FromAccountNum, ToAccountNum and CostCenter.

Calling function from the Account Definitions

In PowerBI a function call can be used like a row wise join. A function can be added by using the used defined function button in the query editor. So we added the function call to the Accounts dataset, i.e. each account definition row fetches all postings from the LedgerJournalTrans with the corresponding accounts and costcenter.

PowerBI calling user defined function

The query parameter are mapped to the fields in the Accounts table.

Parameter values for used defined function

PowerBI will popup a warning that function calls can have a security impact. In the actual version (Mai 2018) PowerBI was quite annoying with security warnings and required a restart (close&open) to stop asking again and again. Finally, expand the Accounts table and the function call results by clicking on the Arrow Right-Left Button next to “GetPostings”. Per default PowerBI adds the fields from the function call result with the function name prefix e.g. GetPostings.Transdate, GetPostings.AmountCurCredit, etc.

As you can see below the Account definition 1101 – 1104 was expanded with all resulting rows from the LedgerJournalTrans that have an account between 1101 and 1104 and any Costcenter (%)

Expanding used defined function call results

Calculate Period Amounts

To get the amount values for each of the two periods, defined in the Daterange Excel sheet, we added 4 additional columns to the Accounts. A debit and credit column for period 1 and period 2.

Ledger postings in period

The code looks like this

CreditDate1 = IF(Accounts[GetPostings.TRANSDATE]>=FIRSTDATE(Daterange[FromDate1]);IF(Accounts[GetPostings.TRANSDATE]<=FIRSTDATE(Daterange[ToDate1]);Accounts[GetPostings.AMOUNTCURCREDIT];0);0)


DebitDate1 = IF(Accounts[GetPostings.TRANSDATE]>=FIRSTDATE(Daterange[FromDate1]);IF(Accounts[GetPostings.TRANSDATE]<=FIRSTDATE(Daterange[ToDate1]);Accounts[GetPostings.AMOUNTCURDEBIT];0);0)


CreditDate2 = IF(Accounts[GetPostings.TRANSDATE]>=FIRSTDATE(Daterange[FromDate2]);IF(Accounts[GetPostings.TRANSDATE]<=FIRSTDATE(Daterange[ToDate2]);Accounts[GetPostings.AMOUNTCURCREDIT];0);0)


DebitDate2 = IF(Accounts[GetPostings.TRANSDATE]>=FIRSTDATE(Daterange[FromDate2]);IF(Accounts[GetPostings.TRANSDATE]<=FIRSTDATE(Daterange[ToDate2]);Accounts[GetPostings.AMOUNTCURDEBIT];0);0)

Display results in matrix

Finally we used a matrix to display the values from the Accounts dataset and grouped it by Name and Accounts.

PowerBI matrix for ledger postings

Security considerations

Injecting range values in a query is not the best way to do this. A better way would be to refactor the LedgerJournalTrans query into a stored procedure and provide the FromAccountNum, ToAccountNum and Costcenter as parameter to the SP.

Integrate SharePoint Online with Dynamics AX Legacy Application

SharePoint Online is a wide spread solution for collaboration. Actual versions of Dynamics AX / 365 provide great integration capabilities and for all other purpose there exists the SharePoint Online Client API. However, legacy applications may not support the required libraries or .NET framework versions to use the SharePoint Online Client API. One way to overcome this issue is to utilize the REST API and communicate via HTTP. Here you can find a step by step guide how to provide an external application access to SharePoint Online via an App:

Register an App in SharePoint Online

The first thing you need to do is register an App in SharePoint Online. Open the registration site in a browser: . In my case I’m using a sub site, therefore my URL would look like this: .Use the form to generate a client ID and a secret code. Provide any title you like. Set the domain to localhost and redirection URL to https://localhost


Set Permissions for the App

Open the following URL in a Browser: . Use the Client ID to lookup the created App. In the free text form add the required permissions e.g. access a list. You can find the syntax here: If you need access to more than one list, you have to repeat this step. In my case I only need access to one list. The XML Permission Code looks like this:

<AppPermissionRequests AllowAppOnlyPolicy="true">
Right="Read" />

Click the Create button, this will forward you to a form where you can choose which list can be accessed by the app.


SharePoint Online Tenant ID

To find the Tenant ID open the following URL in your Browser  Since I’m using a sub site my URL looks like this . There you can find the Tenant ID


Create a Token

To interact with SharePoint Online you need to generate a token.This is done by sending an HTTP POST requiest with client ID, secret, and desired resource to SharePoint Online. Here is the Code in X++ written in Ax 2009.

str clientID = „<YOUR_CLIENT_APP_ID>“;

str secret = „<YOUR_SECRET>“;

str tenant = „<YOUR_TENANT>“;

str ctx = „00000003-0000-0ff1-ce00-000000000000“; //i.e. SharePoint

str sp = „<YOUR_SITE>“;

str listName = „<NAME_OF_YOUR_LIST>“; // e.g. Customers

str listUrl = “‚“+listName+“‚)/items; “

str url = „“


System.Net.WebClient cl = new System.Net.WebClient();

System.Net.WebHeaderCollection headers = new System.Net.WebHeaderCollection();

System.Collections.Specialized.NameValueCollection body = new System.Collections.Specialized.NameValueCollection();

System.Byte[] response;

System.Text.Encoding enc = System.Text.Encoding::get_UTF8();

System.String responseText;

str xppResponse;

int indexOf;

int start;

int totalLength;

int length;

str token;








response = cl.UploadValues(url,“POST“,body);

responseText = enc.GetString(response);

// cut token out of response text

indexOf = responseText.IndexOf(„access_token“);

start = indexOf + 15;

totalLength = responseText.get_Length();

length = totalLength – start – 2;

xppResponse = responseText;

token = responseText.Substring(start,length);


Query List Elements

The token has a valid timestamp measured in Unix Ticks. So it might be good idea to store the token in a parameter table and only request a new one if it is or will expire soon. However, in this example I go on and query the Customer List.

// .. Code from above

cl = new System.Net.WebClient();

headers = new System.Net.WebHeaderCollection();


headers.Add(„Authorization“,“Bearer „+token);



url = listUrl;

responseText = cl.DownloadString(listUrl);

xppResponse = responseText;



In this case the response type is an XML. You may parse the XML and only view e.g. the Title Tag or any other fields your are interested in. Here is the Infolog of the complete XML response from the SharePoint Online List.


Walkthrough: Configure XML/File Data Export via AIF in AX 2012

This is an example how to export the LedgerJournalTable + LedgerJournalTrans records as XML via AIF

Create a Query

  1. Create a new query and name it AxdLedgerJournal.
  2. Place the LedgerJournalTable as main Datasource
  3. Set the Fields Property Dynamic to Yes
  4. Add the LedgerJournalTrans as child data source
  5. Set the Fields property Dynamic of the LedgerJournalTrans data source to Yes
  6. Set the Relation property of the LedgerJournalTrans data source to Yes
  7. Set the Fetch Mode property of the LedgerJournalTrans data source to 1:n
  8. Set the Join Mode property of the LedgerJournalTrans data source to Outer Join

Document Query


Use the AIF Document Wizard to generate the service classes

  1. In the menu bar go to Tools > Application Integration Framework > Create document service
  2. Provide the AxdLedgerJournal query as source query for the document service
  3. Select  the options to generate the Read and Find methods
  4. Select the option to generate the AxBC classes
  5. Let the wizard generate the software artifacts for you

AIF Wizard

Fine tune the generated code

  1. Go to the private projects and open the newly generated project
  2. Compile the project
  3. If the cache() methods don’t compile delete them
  4. Review the open tasks and provide meaningful labels
    Fine tune AIF Code

Configure the Batch Jobs

  1. In AX go to System Administration > Inquires > Batch Jobs > Batch Jobs
  2. Create a new Batch Job and go to “View Task”
  3. Add four task and choose the following classes
  4. AifGatewayReceiveService
  5. AifGatewaySendService
  6. AifInboundProcessingService
  7. Close the Task window and set the Recurrence of the Batch Job to run e.g. all 5 minutes

AIF Batch Jobs


Configure the File System Adapter

  1. In AX go to System Administration > Setup > Services and Application Integration Famework > Outbound Ports
  2. Create a new Outbound Port
  3. Select the FileSystemAdapter as Adapter
  4. Provide a target location where to place the files e.g. a share with write permissions for the AOS account
  5. Open the Service Operation Dialog and add the method
  6. Activate the Port

AIF Outbound Port with File System Adapter

Add a send method

On the LedgerJournalTable add the following method

server void sendElectronically(XMLDocPurpose _xmlDocPurpose,
                               AifSendMode _aifSendMode = AifSendMode::Async)
    AxdSendContext      axdSendContext = AxdSendContext::construct();
    AifEntityKey        aifEntityKey   = AifEntityKey::construct();
    Map                 keyData;
    AifConstraint       aifConstraint = new AifConstraint() ;
    AifConstraintList   aifConstraintList = new AifConstraintList();
    keyData = SysDictTable::getKeyData(this);





Test the export

Create a job and use the following code to test the export

static void JobTestAifExport(Args _args)
    LedgerJournalTable journal;
    journal = ledgerJournalTable::find("00001");

In AX got to System Administration > Periodic > Services and Application Integration > Queue Manager. You will find a record for the LedgerJournal export. The Batch Job will first create an XML Document and in a second step perform the export to the file system.

AIF Queue Manager

Last Day Of Month and Year as View Computed Column in AX 2012

View Computed Columns in Dynamics Ax 2012 support the use of TSQL functions to calculate additional view columns. Here is  an example how to calculate the Last Day of Month and Last Day of Year based on the TransDate at the CustTransView. First, Extend the SysComputedColumnDatePart  Base Enum and add two additional values Month and Year. Extend the getDateDiffFormatSelector() method at the SysComputedColumn class.

private static server str
getDateDiffFormatSelector(SysComputedColumnDatePart _dateDiffFormat)
        case SysComputedColumnDatePart::Day :
            return ‚d‘;
        case SysComputedColumnDatePart::Second :
            return ’s‘;
        case SysComputedColumnDatePart::Month:
            return ‚m‘;
        case SysComputedColumnDatePart::Year :
            return ‚yy‘;

    return ‚d‘;


Last Day Of Month

Add a new static method to the view and call it lastDayOfMonth.

private static server str lastDayOfMonth()
    DictView    ctView = new DictView(tableNum(‚CustTransView‘));
    str sTransDate = ctView.computedColumnString(‚CustTrans‘,’TransDate‘);
    str sLastDay = "";

    sLastDay = SysComputedColumn::cast(
    ‚0‘,sTransDate,SysComputedColumnDatePart::Month)+ ‚+1′,’0‘,

    return sLastDay;

Add a new DateTime column to the view and set the lastDayofMonth() method as ViewMethod

Last Day of Year

Add a new static method to the view and call it lastDayOfYear

private static server str lastDayOfYear()
    DictView    ctView = new DictView(tableNum(‚CustTransView‘));
    str sTransDate = ctView.computedColumnString(‚CustTrans‘,’TransDate‘);
    str sLastDay = "";

    sLastDay = SysComputedColumn::cast(
    (‚0‘,sTransDate,SysComputedColumnDatePart::Year) + ‚+1‘,

    return sLastDay;

Add a new DateTime column to the view and set the lastDayOfYear() method as ViewMethod

The View

Dynamics AX 2012 translates both fields in TSQL Code. The view looks like this



The result in Dynamics AX looks like this

CustTransView with Computed Column

Visualize the firms contribution to total revenue in Power BI

We recently discussed at work how to display the contribution of multiple firms to the total revenue in Power BI, in a way that the firms can be filtered but the total revenue stays constant. We came up with a very simple no-code solution.

Data from Dynamics AX 2012 R3

All the required data can be fetched from the CustInvoiceJour table in Dynamics AX. There are three datasets in the Power BI data model. First, the invoices including the InvoiceAmountMST, InvoiceYear and DataAreaId. Second the sum of InvoiceAmountMST grouped by Year and DataAreaId. Finally, a table that contains the years. Here are the SQL statements to load the data.

InvoiceId, InvoiceAmountMST, InvoiceDate, DataAreaId,
year(InvoiceDate) as InvoiceYear
from CustInvoiceJour

Total Revenue:

sum(InvoiceAmountMST) as InvoiceAmountTotal,
year(InvoiceDate) as InvoiceYear, DATAAREAID
from CustInvoiceJour
group by year(InvoiceDate),DATAAREAID

In Power BI create a new dataset and enter the years manually, like 2010, 2011, 2012, etc.

Power BI Years Table

In Power BI go to the data model an connect the Invoices and Total Revenue with the dataset that contains the years


Line and stacked Column

Add a new Line and stacked Column chart to the Power BI Report. Set the field Year from the Year table as shared axis. From the CustInvoiceJour set the DataAreaId as Colum Series and the InvoiceAmountMST as Column  values. Set the InvoiceAmountTotal from the Totals table as line values. In the format tab disable the Y-Axis property Show Secondary.

Diagram settings 

Your diagram should look like this. Filtering the companies does not effect the total revenue line.


Integrate an USB Scale with Dynamics AX

I was recently playing with an Dymo USB scale and how to connect it to a Dynamics AX 2012 instance on a virtualized HyperV installation. It turned out that connecting these two is not so hard at all. The Dymo scale was immediately recognized by my PC.

Dymo scale connected to a Windows 10 PC

To access the USB scale, you need to know the Vendor ID and Product ID. This can be found in the Windows device manager. In my case the Vendor ID is 0x0922 and the Product ID is 0x8003

USB Vendor ID and Product ID in device manager

Access the USB Scale via C#

You need the Human Interface Device library, which is also available as source code. Download the Visual Studio project, open the .sln Solution from the SRC folder and build the library:

Some code is needed to access the scale. Fortunately, there is a code snipped available here: .

  • Create a Visual Studio C# DLL Project.
  • Copy the code for the USBScale class from the website.
  • Add the previous built HIDLibrary to your projects references.
  • Add two int properties to the class, for Vendor ID and Product ID.

    public class USBScale
        public int VendorId { get; set; }
        public int ProductId { get; set; }

  • Change the following line in the GetDevices() method at the USBScale class

public HidDevice[] GetDevices()
   //return HidDevices.Enumerate(0x0922, 0x8004).Cast().ToArray();
   return HidDevices.Enumerate(VendorId, ProductId).Cast<HidDevice>().ToArray();


  • For convenience, add a new method GetGramm()

    public decimal GetGramm()
        decimal? lb = -1;
        decimal? gr = -1;
        decimal? oz = -1;
        bool? stable = false;

        GetWeight(out lb, out gr, out oz, out stable);

        if (gr.HasValue)
            return gr.Value;
        return -1;


  • Build the DLL library

Forward the USB device  to HyperV

There are different ways to pass an USB device to a virtual machine. However, I was using a tool called USB Redirect which is available as trial version for testing. It has two components. The server which manages and shares the USB devices is installed on the physical machine.

USB Redirect

The client is installed on the VM and can access the USB device at the physical machine.

USB Redirect Client


Integrate with Dynamics AX

Finally, the last step to integrate with Dynamics AX is easy. Copy the HID Library DLL and the USBScale DLL to the client bin folder. Create a form with a Scale button. At the clicked() method create a new instance of the USBScale class, provide your product and vendor ID and call GetGramm().

<YOUR_NS>.UsbScale scale = new <YOUR_NS>.UsbScale();
real value;

value = scale.GetGramm();

info(strfmt(“%1 g”,value));

Dynamics AX with USB Scale