Wednesday, April 4, 2018

Delete AUC for all users

Hey Guys!

Today I have got a small but interesting thing in Ax. We had a situation wherein space of C drive was reaching the alarming level of the Ax-client server (from where all users access Ax). I ran the tool WizTree which displays the analysis of a drive as per the space occupancy. It showed that the maximum amount of space was taken by the AppData folder and I knew what needs to be done next.

The problem was auc files had accumulated for all the users which were summed up to almost 30Gb. So, I decided to delete them but the challenge was how to delete it for all the users at once. So I took help from my master (yes, it's Google) and created a batch which will traverse through all the users AppData folder and delete the auc/kti files.

1. Open Notepad.
2. Paste the below commands there:

for /D %D in ("C:\USERS\*") do del "%~fD\AppData\Local\*.auc"
for /D %D in ("C:\USERS\*") do del "%~fD\AppData\Local\*.kti"
pause

3. Save the notepad as .bat file.


4. Run the batch file and it will delete all the auc/kti files for all the users.

Credits: http://blog.bhsolutions.com/index.php/2014/05/dynamics-ax-2012-spring-cleaning-app-data/
https://stackoverflow.com/questions/34019989/batch-file-copy-all-users-profile-app-data

Tuesday, January 9, 2018

Filter AOT objects using X++ in Ax2012

Hey guys,

Let's discuss a very interesting code for the simple purpose of filtering objects in a project. I hope we all know about the standard of Ax to filter and import AOT objects in a project. So now we will see how to do it using x++ code:

static void alle_aks_ImportObjectsToProjects(Args _args)
{
    sysprojectfilterrunbase varProject;
    utilelements            aotElements;

    varProject = new sysprojectfilterrunbase();
    varProject.parmProjectNode(systreenode::createProject('Alle_GST_TaxObejcts'));
    varProject.grouping(sysprojectgrouping::AOT);

    while select name, utilLevel, RecordType, ParentId from aotElements
        where aotElements.utilLevel == UtilEntryLevel::var
        && aotElements.name like 'Tax*'
    {
        try
        {
            aotElements.reread();
            varProject.doUtilElements(aotElements);
        }
        catch (exception::Error)
        {
            throw error('Error');
        }
    }
    varProject.write();
    info('Done');
}

Here you can modify the while select query to suit your need i.e. to change the layer or the name filter.


Source: https://mydynamicsax.wordpress.com/2015/03/19/how-to-filter-aot-objects-to-a-project/

Wednesday, June 7, 2017

"No elements are found in AOD files in the old directory" error in Ax2012

Hi guys,

Today while applying GST patch for one of our clients I faced an error in Code Upgrade. I had installed the hotfix and run the "Detect Code Upgrade Conflicts" and I received an error which said "No elements are found in AOD files in the old directory. The layer conflict project will not be created.". So of course after googling a bit I got a solution to this:

Modify the detectConflictsRun method on SysUpgradeDetectConflicts class like below:
Comment the code that checks for a record on utilElementsOld Table and sets layerDetectorRequirementsOk variable to True.

   // check if the Old directory exists
   /*select firstonly RecId from utilElementsOld;
   if (utilElementsOld.RecId)
   {
       layerDetectorRequirementsOk = true;
   }
   else
   {
       layerDetectorRequirementsOk = false;
       // Inform user that the layer project will not be created
       info("@SYS106623");
   }*/
   layerDetectorRequirementsOk = true;

And that works like a charm and you will get a project with code conflicts. :)

Credits: https://community.dynamics.com/ax/f/33/t/123783

Friday, May 26, 2017

The state of the source document or source document line could not be updated Ax2012

Hi Guys,

Today we faced this in our production environment. A particular PO had developed this issue wherein whenever a user clicks the Invoice button system would throw the error "The state of the source document or source document line could not be updated.".

While debugging, I came to know that this had something to do with Source Document Line records. So I tried looking for a solution on the web and got the below job which works perfectly in deleting the orphan records from SourceDocumentLine:

static void aks_fixOrphanedSourceDocumentsHeader(Args _args)
{
    SourceDocumentLine sline;
    SysDictTable table;
    PurchTable header;
    PurchLine purchline;
    PurchId purchId = "WPFO1617-0001218";
    boolean fix;
    Common rec;
    int fieldId, found, notfound;

    if (purchId)
    {
        while select purchLine where purchLine.PurchId == purchId
        {
            while select forUpdate sline where sline.ParentSourceDocumentLine == purchLine.SourceDocumentLine
            {
                table = new SysDictTable(sline.SourceRelationType);
                rec = table.makeRecord();
                fieldId = fieldName2id(sline.SourceRelationType, "SourceDocumentLine");
                select rec where rec.(fieldId) == sline.RecId;

                if (rec.RecId)
                {
                    info(strFmt("Record Match Found %1 %2", table.name(),rec.caption()));
                    found++;
                }
                else
                {
                    ttsBegin;
                    sline.doDelete();
                    ttsCommit;

                    info(strFmt("Orphan Found %1", table.name()));
                    notfound++;
                }
            }
            info(strFmt("Found %1", found));
            info(strFmt("Orphans found and deleted %1",notfound));

            found = 0;
            notfound = 0;
        }
    }
}


Credit: https://community.dynamics.com/ax/f/33/t/144316

Friday, March 24, 2017

Copy data from a table to another Ax2012

Hi guys,

Another short code for copying data from one table to another (backupTable). This is useful when you have to take backup of the data while you want to perform some operations of the production data (original table) or maybe to resolve some DB sync issues.

static void AKS_CopyTableData(Args _args)
{
    EcomInboundInventoryUpdate          origTable;
    Backup_EcomInboundInventoryUpdate   copyTable;

    ttsBegin;
    delete_from copyTable;

    while select origTable
    {
        buf2Buf(origTable, copyTable);
        copyTable.insert();
    }

    ttsCommit;
}

Delete duplicate records of a table AX2012

Hi Guys,

A code snippet to delete duplicate records of a table and keeping just one of the duplicate set.

static void AKS_DeleteDuplicates(Args _args)
{
    EcomInboundInventoryUpdate      table, tableSelect, tableDelete;

    while select table group by wmsreferencenum
        where table.EcomProcessStatus == EcomProcessStatus::Waiting
    {

        select firstOnly RecId from tableSelect
            where tableselect.WMSReferenceNum == table.WMSReferenceNum;


        delete_from tableDelete
            where tableDelete.WMSReferenceNum == table.WMSReferenceNum &&
                  tableDelete.RecId != tableSelect.RecId;
    }
}

Monday, March 13, 2017

Get dimension name and value in Ax2012

Hi Guys,

Here is another code snippet to get Dimension value and name by dimension:

static DimensionValue Ale_getDefaultDimensionValue(DimensionDefault   _dimensionDefault , Name  _defaultDimensionName)
{
    DimensionAttributeValueSet      dimAttrValueSet;
    DimensionAttributeValueSetItem  dimAttrValueSetItem;
    DimensionAttributeValue         dimAttrValue;
    DimensionAttribute              dimAttr;
    Common                          dimensionValueEntity;
    DimensionValue                  dimValue;
    Name                                   dimName;
    container                           dimNameValue;
    ;
    dimAttrValueSet = DimensionAttributeValueSet::find(_dimensionDefault);

    while select DimensionAttributeValue from dimAttrValueSetItem
        where   dimAttrValueSetItem.DimensionAttributeValueSet   == dimAttrValueSet.RecId
    {
        dimAttrValue        = DimensionAttributeValue::find(dimAttrValueSetItem.DimensionAttributeValue);

        dimAttr             = DimensionAttribute::find(dimAttrValue.DimensionAttribute);

        dimensionValueEntity = DimensionDefaultingControllerBase::findBackingEntityInstance(curext(),dimAttr,dimAttrValue.EntityInstance);

        if (dimAttr.Name == _defaultDimensionName)
        {
            dimNameValue = [dimAttrValue.getValue(), dimAttrValue.getName()];
        }
    }
    return dimNameValue;
}

Tuesday, March 7, 2017

Item CostPrice per dimension Ax2012

Hi guys,

Just a quick code snippet to fetch the CostPrice of an Item based on the dimensions:

public void ale_GetCostPrice()
{
    InventDim       inventDim;
    InventDimParm   inventDimParm;
    InventOnHand    inventOnHand;
    InventSum       inventSum;

    select firstOnly1 ItemId from inventSum
        where inventSum.ItemId == this.ItemId;

    inventDim.InventSiteId = this.inventDim().InventSiteId;
    inventDim.InventLocationId = this.inventDim().InventLocationId;
    inventDim.wMSLocationId = this.inventDim().wMSLocationId;
    inventDim.InventProfileId_RU = this.inventDim().InventProfileId_RU;
    inventDimParm.initFromInventDim(inventDim);

    inventOnHand = InventOnHand::newItemDim(inventSum.ItemId, inventDim, inventDimParm);

    ttsBegin;
    this.CostPrice = inventOnHand.costPricePcs();
    this.inventMovement().journalSetCostPrice();
    this.update();
    ttsCommit;
}

Sunday, January 29, 2017

Visual Studio cache clear

Hey guys,

Today, while playing around with Visual Studio templates, I did which had put me in a fix. I had changed some config files and after which I started getting weird errors on VS launch. (Sorry don't remember the exact errors now)

Then I revert the config files from the backup which I had taken before doing the changes still the errors didn't go away. Then it struck me that it's got something to do with the VS cache and on googling I found the below method to clear the cache:

1. Close Visual Studio (ensure devenv.exe is not present in the Task Manager)
2. Delete the %USERPROFILE%\AppData\Local\Microsoft\VisualStudio\14.0\ComponentModelCache directory
3. Restart Visual Studio.

You should change the VisualStudio/14.0 to the version of visual studio you are using. I am using VS2015.

Credits: https://github.com/Codealike/Codealike-KnowledgeBase/blob/master/clear-visual-studio-component-cache.md

Sunday, January 8, 2017

Sending barcode print to Label printers

Hi guys,

Last few days have been really tiring for me doing this particular task.

Requirement:
Barcode labels have to be printed from AX2012 for a particular work using a Thermal barcode printer.
Label size:2.48in X 0.91in

This may seem to be an easy solution but in reality, this may make your head spin.

Solution provided: SSRS report
Challenge faced: As the label size is landscape mode when you try to print the label from SSRS report, it prints the label tilting it to 90 degrees to the left. This is due to the label printer drivers as far as I could find out through google.

Also, there is a limitation in SSRS which automatically converts the mode to portrait or landscape based on the dimensions you provide for the report. So both these individual limitations make it impossible to print a rectangle label.

Solution provided 2: AX report
The challenge faced: This had seemed to be the best approach as the report renders itself as per the default printer settings. So when we were printing then it was coming out perfect and was scannable too. But the issue came when we were printing from different machines, it was not scannable!! This had me wondering that what could be the issue cause the labels from my machines (scannable) and other machines (not scannable) were exactly same.

The barcode font that we were using was IDAUTOMATIONHC39M which is of Code39 type and we got to know that Code39 expands the barcode horizontally and vertically. The universally best barcode is to use Code128.

Finally what had worked:
Solution provided 3: Direct printing using PRN file
Now this was really interesting as I had never done anything of this regard yet. So first I got a prn (TSC programmed) file. Then googled and got the solution to do so.

The challenge faced: PRN files hit directly to the printer by using printer name/IP address. In our case, the printer and AX were in different domains and we were sharing them through remote desktop. So when you share a printer through RDP then the name of the printer "TSC ME240" gets a suffix of  "TSC ME240 redirected 23" and hence the PRN command from AX was not able to find the printer "TSC ME240".

Solution: Put printer on Public IP

We had to put the printer on public IP and add it to the server with the same name by a fresh installation of printer drivers and avoid redirecting.

The Task:
1. Got the PRN from the Printer vendor.
2. Create a table for storing the PRN code similar to WHSDocumentRoutingLayout. Fields: LayoutId, Description, PrinterName, DefaultConfig, PRNCode
3. Write a find, findByDefault method.
4. Create a form for the same table.
5. Create a new layout, mark it default and define the TSC/ZPL code in the form.
Below code is in TSC programming langauge in this case for TSC label printers:

<xpml><page quantity='0' pitch='23.1 mm'></xpml>SIZE 61.7 mm, 23.1 mm
GAP 3 mm, 0 mm
DIRECTION 0,0
REFERENCE 0,0
OFFSET 0 mm
SET PEEL OFF
SET CUTTER OFF
SET PARTIAL_CUTTER OFF
<xpml></page></xpml><xpml><page quantity='1' pitch='23.1 mm'></xpml>SET TEAR ON
CLS
BARCODE 462,117,"128M",27,0,180,2,4,"OIDPIDBarcode"
CODEPAGE 1252
TEXT 440,83,"0",180,12,12,"EcomOrderProductId"
TEXT 449,158,"0",180,12,10,"ItemId"
TEXT 463,44,"0",180,8,10,"ItemName"
PRINT 1,1
<xpml></page></xpml><xpml><end/></xpml>

6. Now, create a class "WHSBarcodePrinting" with the following methods:

  • Class declaration

class WHSBarcodePrinting
{
     RecordSortedList    list;
}
  • initMenuFields - To read the fields to which needs to be printed.
RecordSortedList initMenuFields()
{
    TmpSysTableField    tmpField;
    DictField           dictField;
    DictTable           dictTable = new DictTable(tableNum(WHSWorkLine));
    int                 length = dictTable.fieldCnt();
    int                 i;

    for (i = 1; i <= length; ++i)
    {
        dictField = new DictField(tableNum(WHSWorkLine), dictTable.fieldCnt2Id(i));

        if (!dictField.isSystem() || !dictField.visible() && dictField.name() == 'ItemId' || dictField.name() == 'EcomOrderProductId')
        {
            tmpField.FieldId    = dictField.id();
            tmpField.FieldName  = dictField.name();
            tmpField.FieldLabel = dictField.label();
            list.ins(tmpField);
        }
    }

    return list;
}
  • new
public void new()
{
    list = new RecordSortedList(tableNum(TmpSysTableField));
    list.sortOrder(fieldNum(TmpSysTableField, FieldLabel));

    this.initMenuFields();
}
  • printDocument - To get the final string command to be sent to the printer.
public boolean printDocument(PrinterName _printerName, WHSLayoutId _layoutId, WHSWorkLine _label)
{
    str         finalStr;
    boolean     ret;

    finalStr = this.translate(WHSBarcodePrintingSetup::find(_layoutId).zpl, _label);

    Microsoft.Dynamics.AX.WHS.DeviceCom.Printer::SendStringToPrinter(_printerName, finalStr);

    return ret;
}
  • translate - Reading the PRN code and replacing the variable with Ax table values to be printed on label.
str translate(str _inputStr, WHSWorkLine _label)
{
    TmpSysTableField    tmpField;
    FieldLabel          label;
    str                 outputStr;

    outputStr = _inputStr;

    list.first(tmpField);

    while (tmpField.FieldLabel != '')
    {
        outputStr = strReplace(outputStr, strFmt('%1', tmpField.FieldName), _label.(tmpField.FieldId));
        outputStr = strReplace(outputStr, 'ItemName', _label.displayItemName());
        outputStr = strReplace(outputStr, 'OIDPIDBarcode', "!105" + subStr(_label.EcomOrderProductId, 0, 8) + "!100" +
        subStr(_label.EcomOrderProductId, 9, 3) + "!099" + subStr(_label.EcomOrderProductId, 12, strLen(_label.EcomOrderProductId)));
        label = tmpField.FieldLabel;

        tmpField.clear();

        list.next(tmpField);

        if (label == tmpField.FieldLabel)
        {
            break;
        }
    }

    return outputStr;
}
  • main - Filtering the records for which label has to be printed.
static void main(Args _args)
{
    WHSBarcodePrinting         barcodePrint = new WHSBarcodePrinting();
    PrinterName                     printerName;
    WHSWorkTable                    whsWorkTable;
    WHSWorkLine                     whsWorkLine;
    WHSBarcodePrintingSetup    barcodeSetup;
    WHSLayoutId                     layoutId;

    barcodeSetup    = WHSBarcodePrintingSetup::findByDefault(NoYes::Yes);
    printerName     = barcodeSetup.PrinterName;
    layoutId        = barcodeSetup.LayoutId;

    barcodePrint.initMenuFields();

    if (_args.record() && _args.record().TableId == tableNum(WHSWorkTable))
    {
        whsWorkTable    =   _args.record();

        if (whsWorkTable.WorkTransType == WHSWorkTransType::Sales && (whsWorkTable.WorkStatus != WHSWorkStatus::Cancelled
            || whsWorkTable.WorkStatus != WHSWorkStatus::Skipped))
        {
            while select ItemId, EcomOrderProductId from whsWorkLine
                order by whsWorkLine.WMSLocationId, whsWorkLine.ItemId
                where whsWorkLine.WorkId == whsWorkTable.WorkId
                && whsWorkLine.WorkType == WHSWorkType::Pick
            {
                barcodePrint.printDocument(printerName, layoutId, whsWorkLine);
            }
        }
    }
    else if (_args.record() && _args.record().TableId == tableNum(WHSWorkLine))
    {
        whsWorkLine     =   _args.record();
        barcodePrint.printDocument(printerName, layoutId, whsWorkLine);
    }
    else
    {
        throw error('This command must be run from a Work Id.');
    }
}

Pheww!! After all this you will be able to print the labels comfortably which are readable by any scanner. :)

Reference: http://jhodge65.blogspot.in/2015/03/zebra-printer-label-printing-with.html

Tuesday, December 27, 2016

Error in getting sid AX2012

Hi Guys,

I had recently setup a new environment and it was working fine. Then suddenly one of our team members reported that he is not able to post Purchase Packing Slip. The Packing Slip screen comes and when you press OK it just goes off and does nothing. He also got an error "Failed to create a session" but just once.

I was trying to set default company and I got "Error in getting SID" error. So after doing some hit and trial I reached a really silly solution. Whichever user is facing this issue just disable the UserId from SystemAdministrator->Common->Users->Users. Select the user, click "Edit", then uncheck the "Enabled" checkbox and save the record. and enable it. Now do the reverse and check the "Enabled" checkbox and save the record. Then ask him to re-login and check. And it works now. :)

Monday, December 19, 2016

Update SQLDictionary

When you have a TransactionDB from one environment and ModelDB from another, you will face DBSync issues. This is a very common problem where SQLDictionary in TransDB refers to the objects from older ModelDB hence it throws the error with TableId/FieldId mismatch or Typecasting issue.
The below job is used to update the SQLDictionary in some case when you have issues in DBSync:

static void fixTableAndFieldIds(Args _args)
{
    Dictionary dictionary = new Dictionary();
    SysDictTable dictTable;
    DictField dictField;
    TableId tableId;
    FieldId fieldId;
    SqlDictionary sqlDictionaryTable;
    SqlDictionary sqlDictionaryField;

    setPrefix("Update of data dictionary IDs");
    tableId = dictionary.tableNext(0);
    ttsbegin;

    while (tableId)
    {
        dictTable = new SysDictTable(tableId);

        setPrefix(dictTable.name());

        if (!dictTable.isSystemTable() && !dictTable.isView())
        {
            //Finds table in SqlDictionary by name in AOT, if ID was changed.
            //Empty field ID represents a table.
            select sqlDictionaryTable
                where sqlDictionaryTable.name == dictTable.name()
                && sqlDictionaryTable.fieldId == 0
                && sqlDictionaryTable.tabId != dictTable.id();

            if (sqlDictionaryTable)
            {
                info(dictTable.name());
                //Updates table ID in SqlDictionary
                if (ReleaseUpdateDB::changeTableId(
                    sqlDictionaryTable.tabId,
                    dictTable.id(),
                    dictTable.name()))
                {
                    info(strFmt("Table ID changed (%1 -> %2)", sqlDictionaryTable.tabId, dictTable.id()));
                }
            }

            fieldId = dictTable.fieldNext(0);

            //For all fields in table
            while (fieldId)
            {
                dictField = dictTable.fieldObject(fieldId);

                if (!dictField.isSystem())
                {
                    //Finds fields in SqlDictionary by name and compares IDs
                    select sqlDictionaryField
                        where sqlDictionaryField.tabId == dictTable.id()
                        && sqlDictionaryField.name == dictField.name()
                        && sqlDictionaryField.fieldId != 0
                        && sqlDictionaryField.fieldId != dictField.id();

                    if (sqlDictionaryField)
                    {
                        //Updates field ID in SqlDictionary
                        if (ReleaseUpdateDB::changeFieldId(
                            dictTable.id(),
                            sqlDictionaryField.fieldId,
                            -dictField.id(),
                            dictTable.name(),
                            dictField.name()))
                        {
                            info(strFmt("Pre-update: Field %1 - ID changed (%2 -> %3)",
                                dictField.name(),
                                sqlDictionaryField.fieldId,
                                -dictField.id()));
                        }
                    }
                }
                fieldId = dictTable.fieldNext(fieldId);
            }

            fieldId = dictTable.fieldNext(0);

            //For all fields in table
            while (fieldId)
            {
                dictField = dictTable.fieldObject(fieldId);

                if (!dictField.isSystem())
                {
                    select sqlDictionaryField
                        where sqlDictionaryField.tabId == dictTable.id()
                        && sqlDictionaryField.name == dictField.name()
                        && sqlDictionaryField.fieldId < 0;

                    if (sqlDictionaryField)
                    {
                        //Updates field ID in SqlDictionary
                        if (ReleaseUpdateDB::changeFieldId(
                            dictTable.id(),
                            sqlDictionaryField.fieldId,
                            -sqlDictionaryField.fieldId,
                            dictTable.name(),
                            dictField.name()))
                        {
                            info(strFmt("Final update: Field %1 - ID changed (%2 -> %3)",
                                dictField.name(),
                                sqlDictionaryField.fieldId,
                                -sqlDictionaryField.fieldId));
                        }
                    }
                }
                fieldId = dictTable.fieldNext(fieldId);
            }
        }
        tableId = dictionary.tableNext(tableId);
    }
    ttscommit;
}

Reference: http://sashanazarov.blogspot.in/2012/09/id-change-in-dynamics-ax-data-dictionary.html

Wednesday, December 14, 2016

Display method on InventOnHandItem form

Hi guys,

Today I faced a very typical issue while adding a new display field in InvnetOnHandItem. I had to show the sum of a quantity from a customized table based on ItemId and Warehouse. Now the problem here was the grouping between InventSum and InventDim due to which I was not getting the value of InventDimId in InventSum.

So after a lot of failed techniques, I found a solution to use the InventDim values and the code is below:

display public InventQtyReservOrdered ale_PendingVerificationQty(InventSum _inventSum)
{
    InboundWebOrderLine     inboundWebOrderLine;
    InventDim               joinDim, dimValues;
    InventDimParm           dimParm;

    dimValues.data(_inventSum.joinChild());
    dimParm.initFromInventDim(dimValues);

    select sum(QtyOrdered) from inboundWebOrderLine
        where inboundWebOrderLine.ItemId == _inventSum.ItemId
        && (inboundWebOrderLine.EcomMerchantLocation == dimValues.InventLocationId
        || inboundWebOrderLine.EcomMerchantLocation != '')
        && inboundWebOrderLine.EcomBridgeOrderItemStatus == EcomBridgeOrderItemStatus::Ordered;

    return inboundWebOrderLine.QtyOrdered;
}

This method worked like a charm and a had sigh of relief. :)

Sunday, October 9, 2016

buf2buf to buf2bufByName in Ax2012

Hi Guys,

I would like to share an interesting blog post regarding buf2buf wherein you can also copy table buffer to another table in which field names are same as the same i.e table structure should be similar of both the tables.

We can create below method in the Global class:

//Modified method, copy data from one table to another table with similar structure

static void buf2BufByName(Common  _from, Common  _to)
{
    DictTable   dictTableFrom   = new DictTable(_from.TableId);
    DictTable   dictTableTo     = new DictTable(_to.TableId);
    DictField   dictFieldFrom;
    FieldId     fieldIdFrom     = dictTableFrom.fieldNext(0);
    FieldId     fieldIdTo
    ;


    while (fieldIdFrom && ! isSysId(fieldIdFrom))
    {
        dictFieldFrom   = new DictField(_from.TableId, fieldIdFrom);


        if(dictFieldFrom)
        {
            fieldIdTo = dictTableTo.fieldName2Id(dictFieldFrom.name());


            if(fieldIdTo)
                _to.(fieldIdTo) = _from.(fieldIdFrom);
        }


        fieldIdFrom = dictTableFrom.fieldNext(fieldIdFrom);
    }
}

Source: http://mybhat.blogspot.in/2012/07/dynamics-ax-buf2buf-and-buf2bufbyname.html

Friday, September 23, 2016

Redirect WMS page to Login Screen AX2012

Hi Guys,

We had a requirement with a client wherein they wanted to see the WMS login screen rather than the WMS Homepage to save that 1 extra click ;)

So to achieve this I had to make a small change in the "Index.aspx" file located at "C:\Program Files (x86)\Microsoft Dynamics AX\60\Warehouse Mobile Devices Portal\00\Views\Home".

The change was a small script to redirect the page:

<script type="text/javascript">
setTimeout('Redirect()',10);
function Redirect()
{
  location.href = 'http://wmsServerName:port/Execute/Display';
}
</script>

So this script will take you directly from 








To this screen:


Thursday, August 18, 2016

Create User from another user using X++ AX2012

While searching some issue I have a very interesting blog post about a utility which creates a new user based on an existing user. It copies the Roles and User Options from the existing user and creates a new one.

This utiltiy not limited to copy user roles but can copy User Groups and user Options too.



Some important methods of this utility are;

Create User lookup for FROM USER

public void fromUserLookup(FormStringControl userLookupControl)
{
    Query                   qry = new Query();
    QueryBuildDataSource    qbd;
    QueryBuildRange         qbr;
    SysTableLookup          sysTableLookup;
    Userinfo                userInfo;


    systablelookup = SysTableLookup::newParameters(tableNum(UserInfo), userLookupControl);
    SysTableLookup.addLookupfield(fieldNum(UserInfo, ID));
    SysTableLookup.addLookupfield(fieldNum(UserInfo, NetworkAlias));
    SysTableLookup.addLookupfield(fieldNum(UserInfo, Name));

    qbd = qry.addDataSource(tableNum(userInfo));
    qbd.addRange(fieldNum(UserInfo,Company)).value(curext());
    sysTableLookup.parmQuery(qry);
    sysTableLookup.performFormLookup();
}

Create User lookup for TO USER

public void toUserLookup(FormStringControl userLookupControl)
{
    Query                   qry = new Query();
    QueryBuildDataSource    qbd;
    QueryBuildRange         qbr;
    SysTableLookup          sysTableLookup;
    Userinfo                userInfo;


    systablelookup = SysTableLookup::newParameters(tableNum(UserInfo), userLookupControl);
    SysTableLookup.addLookupfield(fieldNum(UserInfo, ID));
    SysTableLookup.addLookupfield(fieldNum(UserInfo, NetworkAlias));
    SysTableLookup.addLookupfield(fieldNum(UserInfo, Name));

    qbd = qry.addDataSource(tableNum(userInfo));
    qbd.addRange(fieldNum(UserInfo,Company)).value(curext());
    qbr = qbd.addRange(fieldNum(UserInfo, Enable));
    qbr.value('1');
    sysTableLookup.parmQuery(qry);
    sysTableLookup.performFormLookup();
}

Function to copy USER ROLES

/// <summary>
/// copy roles assigned to one user to another
/// </summary>
/// <returns>
/// true; roles are copied across. false; roles failed to copied
/// </returns>
/// <remarks>
/// this method is used to copy user roles assigned to one user to another user.
/// </remarks>
private boolean copyUserRoles()
{
    boolean                 ret = true;

    SecurityRole            securityRole;

    SecurityUserRole        securityUserRole;
    SecurityUserRole        securityUserRoleExist;
    SecurityUserRole        securityUserRoleInsert;
    OMUserRoleOrganization  userRoleOrganization, userRoleOrganization_Insert;

    List                    copiedUserRoles = new List(Types::String);

    ListEnumerator          lEnumerator;

    setPrefix(strFmt("Copy user", fromUser, toUser));

    try
    {
        select securityRole where securityRole.AotName == 'SystemUser';
        delete_from securityUserRole where securityUserRole.User == toUser && securityUserRole.SecurityRole == securityRole.RecId;
   
        while select securityUserRole
                where securityUserRole.User == fromUser
            notExists join * from securityUserRoleExist
                where securityUserRoleExist.SecurityRole    == securityUserRole.SecurityRole
                    && securityUserRoleExist.User           == toUser
        {
            select securityRole where securityRole.RecId == securityUserRole.SecurityRole;

            copiedUserRoles.addStart(securityRole.Name);

            securityUserRoleInsert.initValue();
            securityUserRoleInsert.SecurityRole = securityUserRole.SecurityRole;
            securityUserRoleInsert.User         = toUser;
            securityUserRoleInsert.insert();
            securityUserRoleInsert.clear();

            while select userRoleOrganization
                    where userRoleOrganization.User == fromUser
                        && userRoleOrganization.SecurityRole == securityUserRole.SecurityRole
            {
                userRoleOrganization_Insert.initValue();

                userRoleOrganization_Insert.OMHierarchyType             = userRoleOrganization.OMHierarchyType;
                userRoleOrganization_Insert.OMInternalOrganization      = userRoleOrganization.OMInternalOrganization;
                userRoleOrganization_Insert.SecurityRole                = userRoleOrganization.SecurityRole;
                userRoleOrganization_Insert.SecurityRoleAssignmentRule  = userRoleOrganization.SecurityRoleAssignmentRule;
                userRoleOrganization_Insert.User                        = toUser;

                userRoleOrganization_Insert.insert();
                userRoleOrganization_Insert.clear();
            }
        }
    }
    catch
    {
        ret = false;
    }

    if (ret)
    {
        lEnumerator = copiedUserRoles.getEnumerator();

        if (copiedUserRoles.empty())
            info(strFmt("User %1 and %2 have already the same user role",fromUser, toUser));

        while (lEnumerator.moveNext())
        {
            info(strFmt('%1',lEnumerator.current()));
        }
    }
    else
        error(strFmt("User Roles aborted please review list"));

    return ret;
}

Function to copy USER OPTIONS
/// <summary>
/// copy options assigned to one user to another
/// </summary>
/// <returns>
/// true; options are copied across. false; options failed to copied
/// </returns>
/// <remarks>
/// this method is used to copy user's options assigned to one user to another user.
/// </remarks>
private boolean copyUserOptions()
{
    boolean                 ret = true;

    UserInfo                userInfoSource;
    UserInfo                userInfoTarget;

    SysUserInfo             sysUserInfoSource;
    SysUserInfo             sysUserInfoTarget;

    setPrefix(strFmt("Copy user options", fromUser, toUser));

    try
    {
        select userInfoSource
            where userInfoSource.id == fromUser
        join sysUserInfoSource
            where sysUserInfoSource.Id == userInfoSource.id;

        ttsBegin;
       
            select forUpdate userInfoTarget where userInfoTarget.id == toUser;
            userInfoTarget.filterByGridOnByDefault = userInfoSource.filterByGridOnByDefault;
            userInfoTarget.statuslineInfo = userInfoSource.statuslineInfo;
            userInfoTarget.update();

       
            select forUpdate sysUserInfoTarget where sysUserInfoTarget.Id == toUser;
            sysUserInfoTarget.DefaultCountryRegion = sysUserInfoSource.DefaultCountryRegion;
            sysUserInfoTarget.update();
        ttsCommit;

    }
    catch
    {
        ret = false;
    }

    if (ret)
    {
        info(strFmt("User %1 and %2 have already the same user options ", fromUser, toUser));
    }
    else
        error(strFmt("User Options aborted please review list "));

    return ret;
}

Function to copy USER GROUPS
/// <summary>
/// copy groups assigned to one user to another
/// </summary>
/// <returns>
/// true; groups are copied across. false; groups failed to copied
/// </returns>
/// <remarks>
/// this method is used to copy user groups assigned to one user to another user.
/// </remarks>
private boolean copyUserGroups()
{
    boolean                 ret = true;

    UserGroupList           userGroupList;
    UserGroupList           userGroupListExist;
    UserGroupList           userGroupListInsert;

    List                    copiedGroups = new List(Types::String);

    ListEnumerator          lEnumerator;

    setPrefix(strFmt("Copy user groups", fromUser, toUser));

    try
    {
        while select userGroupList
                where userGroupList.userId == fromUser
            notExists join * from userGroupListExist
                where userGroupListExist.groupId == userGroupList.groupId
                    && userGroupListExist.userId == toUser
        {
            copiedGroups.addStart(userGroupList.groupId);

            userGroupListInsert.initValue();
            userGroupListInsert.groupId = userGroupList.groupId;
            userGroupListInsert.userId  = toUser;
            userGroupListInsert.insert();
            userGroupListInsert.clear();
        }
    }
    catch
    {
        ret = false;
    }

    if (ret)
    {
        lEnumerator = copiedGroups.getEnumerator();

        if (copiedGroups.empty())
            info(strFmt("User %1 and %2 have already the same user Groups ",fromUser, toUser));

        while (lEnumerator.moveNext())
        {
            info(strFmt('%1',lEnumerator.current()));
        }
    }
    else
        error(strFmt("User Groups aborted please review list "));

    return ret;
}

Credit: http://daxture.blogspot.in/2015/01/assigning-security-roles-to-new-ax-user.html

Tuesday, August 16, 2016

Refresh AOD, Data and Dictionary using X++ AX2012

Using the below code you can refresh AOD, Dicitonary and Data:


static void refreshAll()
{
    #Aif
    SysSQMSettings  _sqmSettings;

    ttsBegin;
    update_recordSet _sqmSettings setting GlobalGUID = str2guid(#EmptyGuidString);
    ttsCommit;

    xSession::removeAOC();
    SysTreeNode::refreshAll();
    SysFlushSystemSequence::doFlush(); SysEvent::fireEvent(SysEventType::FlushSystemSequence);

    SysFlushDictionary::doFlush(); SysEvent::fireEvent(SysEventType::FlushDictionary);

    SysFlushDatabaseLogSetup::doFlush(curext()); SysEvent::fireEvent(SysEventType::FlushDatabaseLogSetup,0,[curext()]);
    SysFlushData::doFlush(); SysEvent::fireEvent(SysEventType::FlushData);
    SysFlushAOD::doFlush();
    SysEvent::fireEvent(SysEventType::FlushAOD);
    xSession::updateAOC();
}

Change menuitem property through X++ AX2012

In a new requirement, I had to generate a URL based on the logged-in user using a new parameters table. The URL had to be opened from EP->EmployeeServices and hence it is a URLMenuItem I was not able to handle it through URLRedirection in C#.

What I decided to change the URL property of the menuitem through code. Below is the code:

private void leaveURL(Str60 encryptedUserName)
{
    Name        encryptedName;
    TreeNode    objTreeNode;
    objTreeNode = TreeNode::findNode(@"\Web\Web Menu Items\URLs\ALE_ESSLeave\");

    if (objTreeNode)
    {
        objTreeNode.AOTsetProperties("PROPERTIES\n URL  #" + 'http://' + HcmSharedParameters::find().ALE_ESS_IP + '/oasis/AX_ESS_Integration.aspx?U=' + encryptedUserName +'&C=' + HcmSharedParameters::find().ALE_LeaveEncryptedCode + "\n ENDPROPERTIES\n");
        objTreeNode.AOTsave();
    }
}

Add/Remove Role to User through X++ code AX2012

Hi guys. We had a requirement where I had to provide SysAdmin role to the current user for the task which he is performing and once it is done then the role had to be removed. As ever google came to help big time and finally my code is ready:

Method to add role:

private void grantAdminRole()
{
    SecurityRole        role;
    SecurityUserRole    userRole;
    boolean             added;
    UserInfo            userInfo;

    select role where role.Name == 'System Administrator';

    select * from userRole
        where userRole.SecurityRole == role.RecId &&
            userRole.User == curUserId();

    if (!userRole || (userRole.AssignmentStatus != RoleAssignmentStatus::Enabled))
    {
        userRole.User = curUserId();
        userRole.SecurityRole = role.RecId;
        userRole.AssignmentMode = RoleAssignmentMode::Manual;
        userRole.AssignmentStatus = RoleAssignmentStatus::Enabled;
        SecuritySegregationOfDuties::assignUserToRole(userRole, null);
    }
}


Method to remove the role from user:

private void revokeAdminRole()
{
    fieldName                           userId;
    SysSecTreeRoles                     roleTree;
    SecurityUserRole                    securityUserRole;
    OMUserRoleOrganization              org;
    SecurityUserRoleCondition           condition;
    SecuritySegregationOfDutiesConflict conflict;
    SecurityRole                        role;

    ttsbegin;

    select role where role.Name == 'System Administrator';

    delete_from condition
        exists join securityUserRole
        where condition.SecurityUserRole == securityUserRole.RecId && securityUserRole.User == curUserId() && securityUserRole.SecurityRole == role.RecId;

    select OMInternalOrganization, SecurityRole from org where org.User == curUserId() && org.SecurityRole == role.RecId;

    if (org.SecurityRole)
    {
        EePersonalDataAccessLogging::logUserRoleChange(org.SecurityRole, org.omInternalOrganization, curUserId(), AddRemove::Remove);
    }

    delete_from org where org.User == curUserId() && org.SecurityRole == role.RecId;

    delete_from conflict where conflict.User == curUserId() && ((conflict.ExistingRole == role.RecId) || (conflict.NewRole == role.RecId));

    //<GEEEE>
    EePersonalDataAccessLogging::logUserRoleChange(role.RecId, 0, curUserId(), AddRemove::Remove);
    //</GEEEE>

    delete_from securityUserRole where securityUserRole.User == curUserId() && securityUserRole.SecurityRole == role.RecId;

    ttscommit;

}

Friday, July 8, 2016

Update ProductDimension, StorageDimension & SearchName of Items/Products

We had a requirement to update ProductDimension and StorageDimension of all the items. I wrote 2 jobs for the same. Below are those:

Update ProductDimension:
static void UpdateProductDimension(Args _args)
{
    EcoResProduct                       ecoResProduct;
    EcoResProductDimensionGroupProduct  ecoResProductDimensionGroupProduct;
 
    while select ecoResProduct
        where ecoResProduct.RecId != 0
    {
        ecoResProductDimensionGroupProduct.initFromProduct(ecoResProduct);
        ecoResProductDimensionGroupProduct.ProductDimensionGroup = 'NameHere';
        ecoResProductDimensionGroupProduct.insert();
    }
 
    info("Done");
}

Update StorageDimension:
static void Job48(Args _args)
{
    EcoResProduct                       ecoResProduct;
    EcoResStorageDimensionGroup         ecoResStorageDimensionGroup;
    EcoResStorageDimensionGroupProduct  ecoResStorageDimensionGroupProduct;
    EcoResStorageDimensionGroupItem     ecoResStorageDimensionGroupItem;
    InventTable                         inventTable;
 
    select Name from ecoResStorageDimensionGroup
        where ecoResStorageDimensionGroup.Name == 'NameHere';
 
    while select ecoResProduct
        join inventTable
        where ecoResProduct.RecId == inventTable.Product
    {
        ecoResStorageDimensionGroupItem.initValue();
        ecoResStorageDimensionGroupItem.ItemDataAreaId = inventTable.dataAreaId;
        ecoResStorageDimensionGroupItem.ItemId = inventTable.ItemId;
        ecoResStorageDimensionGroupItem.StorageDimensionGroup = ecoResStorageDimensionGroup.RecId;
        ecoResStorageDimensionGroupItem.insert();
    }
 
    info("Done");
}

Update SearchName:
static void Job48(Args _args)
{
    EcoResProductTranslation    ecoResProductTranslation;
    InventTable                 inventTable;
    EcoResProduct               ecoResProduct;
 
    update_recordset inventTable
    setting NameAlias = ecoResProductTranslation.Name
    join ecoResProduct
    where ecoResProduct.RecId == inventTable.Product
    join ecoResProductTranslation
    where ecoResProductTranslation.Product == ecoResProduct.RecId
    && ecoResProductTranslation.Name == 'Bentinck One Seater Sofa in Provincial Teak Finish with Mudramark';
 
    info("Done");
}

ReferenceSource: http://fandyax.blogspot.in/2013/04/how-to-using-x-create-item-in.html