torsdag den 9. april 2015

Dynamics AX 2012 AOT add-in copying field list to clip board

Before Dynamics AX 2012 and the new editor which makes things a bit more Visual Studio like, we had some features in the AOT, that are gone now but I miss.

I *might* have rambled and raved about this before ( ;-).

E.g. I miss the ability to mark all fields on a table in the AOT and simply copy them to code.
In the olds days this could be done by simply marking the fields in AOT and dragging them to the editor window.

I needed this and got fed up with having to type the field names my self so I made a little class with the main method:

public static void main(Args _args)

    DictTable   dt;
    DictField   df;
    int f,start,end;
    Set s;
    SetEnumerator se;
    int tableno;
    TextBuffer txtb = new TextBuffer();
    str fieldNameList;
    str tableName;
    TreeNode treeNode;
    str searchFor = 'Path: '+#TablesPath+#AOTDelimiter;

    if (SysContextMenu::startedFrom(_args))   // started from SysContextMenu
        treeNode = _args.parmObject().first();
    else    // started with a button (or from a menu!)
    if (_args.dataset() == tablenum(UtilElements))
        treeNode = xUtilElements::getNodeInTree(_args.record());
    if (strScan(treeNode.toString(),#TablesPath,1,1024))
        start = strScan(treeNode.toString(),searchFor,1,1024)+strLen(searchFor);
        if (strScan(treeNode.toString(),'\\Fields',1,1024))
            end = strScan(treeNode.toString(),#FIELDS,1,1024);
            end = strScan(treeNode.toString(),' Layer: ',1,1024);
        tablename = subStr(treeNode.toString(),start,end-start);
        s = new Set(Types::String);

        if (tableName)
            tableno = tableName2id(tableName);
            dt = new DictTable(tableno);
            if (dt)
                    df = new DictField(tableno,dt.fieldCnt2Id(f));
                    if (!df.isSystem())
                se = s.getEnumerator();
                while (se.moveNext())
                    fieldNameList += (fieldNameList ? "\n" : "") + se.current();

                info("Field names copied to clipboard.");

And dragged the class to the Menu Items \ Action to create the action menu item:

I then added this menu item to the SysContextMenu:

Then I can go to a table in the AOT and right click and choose add-ins / Copy field names to clipboard.

and afterwards just paste into word or excel:

I think its useful for making documentation.

You can download a private project from:!80242&authkey=!AH8DxBaNZUc1LTg&ithint=file%2cxpo

onsdag den 25. marts 2015

Calculating easter sunday in different ways

Once a long time ago I found an interesting document on the inter web, describing different calendar systems.

From that I made a php-class for my website that can calculate easter sunday in the gregorian calendar. Then it is easy to calculate the rest of the danish holidays as they are offset according to easter sunday (except of course those that have a fixed date).

I translated that class in to x++.

You can find it here:!39710&authkey=!ABv0yYPULV3ReIE&ithint=file%2cxpo

Today I got talking with a colleague about calculating easter sunday so he mentioned that he had also made a version of the calculation:

So of course - being a bit nerdy - we just *had* to check if the routines arrived at the same result. So we came up with:

static void EasterTest(Args _args)
    Yr x;
    date easter(Yr yr) // Påskedag / påske søndag
        int g,c,h,i,j,l;
        int easterday, eastermonth;
        g = yr mod 19;
        // Gregorian Calendar
        c = real2int(rounddown(yr / 100,1));
        h = (c - roundDown(c/4,1)-rounddown(((8*c)+13)/25,1)+(19*g)+15) mod 30;
        i = h - rounddown(h/28,1)*(1- rounddown(29 / (h+1),1) * rounddown((21-g) / 11,1));
        j = (yr + (rounddown(yr/4,1))+i+2 - c + (rounddown(c/4,1))) mod 7;
        l = i - j;
        easterday = 3+rounddown((l+40)/44,1);
        return mkdate(l+28-31*rounddown(easterday / 4,1), easterday, yr);
    date dateOfEaster(Yr y)
        int a = y mod 19;
        int b = y div 100;
        int c = y mod 100;
        int d = b div 4;
        int e = b mod 4;
        int f = (b+8) div 25;
        int g = (b-f+1) div 3;
        int h = (19*a+b-d-g+15) mod 30;
        int i = c div 4;
        int k = c mod 4;
        int l = (32+2*e+2*i-h-k) mod 7;
        int m = (a+11*h+22*l) div 451;
        int n = (h+l-7*m+114) div 31;
        int p = (h+l-7*m+114) mod 31;
        return mkdate(p+1,n,y);
    for (x = 1900; x <= 2154; x++)
        if (dateOfEaster(x) != easter(x))
            info(strFmt('E1=%1 E2=%2', dateOfEaster(x), easter(x)));

And the routines calculated the same dates for easter sunday.

We stopped ourselves when discussing if we should implement a tick-couting-measurement to see if my colleagues routine was faster than mine. :-)

But I think mine is slower as it uses several function calls and not pure arithmetic.

torsdag den 5. marts 2015

Forcing the Name field of a salesline to be synchronized to the purchline when using Drop shipment

Using non-stock items in the daily business can be handled in Dynamics AX 2012 by using Direct delivery.

You can use the Button "Direct delivery" from a sales order you have created, to create a matching purchase order.

However if you use the Name field on the salesline to describe the specifications of the item you want to the vendor, the standard functionality does create the matching purchase order lines so that the name of the originating sales line is also used on the sales lines.

Direct deliveries are handled so that the inventory transactions of the salesline are marked against the purchline, so I wrote this small script to be able to get the hang of how to find the direct delivery purchaselines from the saleslines records of a sales order.

static void Job235(Args _args)
    SalesLine   salesLine;
    PurchLine   purchLine;
    InventTransOriginSalesLine itosl;
    InventTransOriginPurchLine itopl;
    InventTrans it,it1;
    InventTable iTbl;
    InventHandlingGroup ihg;
    while select salesLine
        where salesLine.SalesId == "1001135"
        join itosl
        where itosl.SalesLineDataAreaId == salesLine.dataAreaId
           && itosl.SalesLineInventTransId == salesLine.InventTransId
        join it
            where it.InventTransOrigin == itosl.InventTransOrigin
        join it1
            where it1.InventTransOrigin == it.MarkingRefInventTransOrigin
        join itopl
            where itopl.PurchLineDataAreaId == it1.dataAreaId
               && itopl.InventTransOrigin == it1.InventTransOrigin
        join forupdate purchLine
            where purchLine.dataAreaId == itopl.PurchLineDataAreaId
               && purchLine.InventTransId == itopl.PurchLineInventTransId
               && purchLine.Name !=
        join itbl
            where itbl.itemid == salesLine.ItemId
        info("***** FØR ********");
        info(strFmt("Salg: %1 %2 %3 %4",salesLine.SalesId,salesLine.ItemId,salesLine.LineNumber,;
        info(strFmt("Indkøb: %1 %2 %3 %4",purchLine.purchid,purchLine.ItemId,purchLine.LineNumber,;
        purchLine.Name = salesLine.Name;
        info("***** EFTER ********");
        info(strFmt("Salg: %1 %2 %3 %4",salesLine.SalesId,salesLine.ItemId,salesLine.LineNumber,;
        info(strFmt("Indkøb: %1 %2 %3 %4",purchLine.purchid,purchLine.ItemId,purchLine.LineNumber,;

With this code at hand I could continue to customize the PurchAutoCreate_Sales class to sync the names of the salesLines to the purchLines.