Tuesday, August 20, 2019

dynamically create a Query inquiry in x++ code on Dynamics ax

if you try to run this job

static void JobEF_QueryInquiry(Args _args)

{

    QueryBuildDataSource queryBuildDataSource;

    QueryBuildRange queryBuildRange;

    QueryRun queryRun;

    Query    query;

    Qty total;

    InventTrans inventTrans;

    inventDim inventDim;

    query = new Query();

 

    queryBuildDataSource = query.addDataSource(tableNum(InventTrans));

    queryBuildDataSource.addSelectionField(fieldNum(InventTrans,Qty),SelectionField::Sum);

    queryBuildRange      = queryBuildDataSource.addRange(fieldNum(InventTrans,ItemId));

    queryBuildDataSource = queryBuildDataSource.addDataSource(tableNum(InventDim));

    queryBuildDataSource.addGroupByField(fieldNum(InventDim,InventBatchId));

    queryBuildDataSource.relations(true);

    queryRun = new QueryRun(query);

 

    if (queryRun.prompt())

    {

        while (queryRun.next())

        {

            inventTrans = queryRun.get(tableNum(InventTrans));

            inventDim   = queryRun.get(tableNum(inventDim));

            info(strFmt("Batch %1, qty %2",inventDim.inventBatchId, inventTrans.qty));

        }

    }

}


it will prompt you with a query inquiry form that you can fill up with the appropriate values to set the filters


image

container to numbered list

an example how to convert a container to a numbered list that we will show into a dynamically built form:

class MYD_Functions

{

}

public static Notes conToBulletText(container _con)

{

    TextBuffer txtb = new TextBuffer();

    int i;

 

    if (!_con)

        return '';

 

    try

    {

        for( i = 1; i <= conLen(_con) ; i++)

        {

            if (conPeek(_con, i))

                txtb.appendText(strFmt('%1%2) %3', i==1 ? '' : '\n', i, conPeek(_con, i)));

        }

 

    }

    catch (Exception::Error)

    {

        txtb.appendText('Error DFFMLIKQXM: could not translate the container to Notes');

    }

    return txtb.getText();

}

public static void conToBulletTextShowInForm(container _con)

{

    Args args;

    Form form;

    FormRun formRun;

    FormBuildDesign formBuildDesign;

    FormBuildStringControl formBuildStringControl;

 

    Notes notes = MYD_Functions::conToBulletText(_con);

 

    //I need to show it in a form text, in an info box would not show the new lines

    form = new Form();

 

    formBuildDesign = form.addDesign("Design");

    form.design().height(300);

    form.design().caption('Notes');

 

    formBuildStringControl =

        formBuildDesign.addControl(FormControlType::StaticText, "txt");

    formBuildStringControl.text(notes);

    formBuildStringControl.height(200);

 

    args = new Args();

    args.object(form);

 

    // Create the run-time form.

    formRun = new FormRun(Args);

    formRun.run();

    formRun.detach();

}

 

To try we can use a job:

static void MYD_Functions_conToNotes1(Args _args)

{

    container con = ['test1', 'test2'];

    container con1 = ['test3', 'test4'];

   

    MYD_Functions::conToBulletTextShowInForm(Global::conUnion(con, con1));

}



clip_image001

use c# Regular Expressions match to validate an email or a phone number

I share here a helper method to use c# Regular Expressions.

class MYD_Functions
{
}
public static boolean matchRegularExpression(str pattern, str value)
{
    System.Text.RegularExpressions.Match myMatch;

    myMatch = System.Text.RegularExpressions.Regex::Match(value, pattern);

    return myMatch.get_Success();
}
public static boolean validateEmail(EmailBase email)
{
    Str emailPattern = @'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$';

    return MYD_Functions::matchRegularExpression(emailPattern, email);
}
public static boolean validatePhone(Phone phone)
{
    Str phonePattern = @'^([0-9-+ ]{4,20}|)$'; //only num +- space; empty or min lenght of 4 to 20 char
    return MYD_Functions::matchRegularExpression(phonePattern, phone);
}

but I suggest you always use built in methods rather then creating yours. So here the same example using the Global::isMatch method (which behind the scene is actually doing the same thing

public static boolean validateEmail(EmailBase email)
{
    Str emailPattern = @'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$';

    return Global::isMatch(email, emailPattern);
}
public static boolean validatePhone(Phone phone)
{
    Str phonePattern = @'^([0-9-+ ]{4,20}|)$'; //only num +- space; empty or min lenght of 4 to 20 char
    return Global::isMatch(phone, phonePattern);
}

get the postal address in x++ Dynamics Ax

in order to get the Logistics postal address in Dynamics Ax you have different options. Here some examples.

For this example I choose to search for the postal address of an operating unit (department), but the same concept is valid for any other entity (clients, suppliers, workers…)

static void JobEF_AddressDepartment(Args _args)

{

    OMOperatingUnit department =  OMOperatingUnit::find(

                    MYD_HcmPositionDepartmentUtil::getOMDepartmentRecIdFromUnitNumber('123'),

                    OMOperatingUnitType::OMDepartment);

    DirParty dirParty = DirParty::constructFromCommon(department);

    RecId dirPartyRecId = dirParty.getPartyRecId();

 

    LogisticsLocationEntity logisticsLocationEntity = dirParty.getPrimaryPostalAddressLocation();

    LogisticsLocationRecId logisticsLocationRecId = logisticsLocationEntity.parmLocationRecId();

 

    //I have different options

 

    //1) I can instatiate the view DirPartyPostalAddressView

    DirPartyPostalAddressView dirPartyPostalAddressView = dirParty.getPostalAddress(logisticsLocationRecId);

 

    //2) I can instantiate LogisticsPostalAddress

    LogisticsPostalAddress logisticsPostalAddress = LogisticsPostalAddress::findByLocation(logisticsLocationRecId);

 

    //3) I can instantiate LogisticsPostalAddress in just one method:

    LogisticsPostalAddress logisticsPostalAddress1 = DirParty::primaryPostalAddress(dirPartyRecId);

 

    info(DirParty::getAddress(dirPartyRecId));

    info(dirPartyPostalAddressView.City);

    info(logisticsPostalAddress.City);

    info(logisticsPostalAddress1.City);

    info(dirPartyPostalAddressView.Street);

    info(logisticsPostalAddress.Street);

    info(logisticsPostalAddress1.Street);

 

    info(logisticsPostalAddress1.StreetNumber);

    info(logisticsPostalAddress1.Street);

    info(logisticsPostalAddress1.City);

    info(logisticsPostalAddress1.ZipCode);

    info(logisticsPostalAddress1.State);

    info(logisticsPostalAddress1.County);

    info(logisticsPostalAddress1.CountryRegionId);

    info(logisticsPostalAddress1.PostBox);

    info(logisticsPostalAddress1.DistrictName);

}

 

Get Active Directory user name details from x++ in Dynamics Ax

here an example how you can query your active directory and get user details like email, sid, name

static void JobEF_ActiveDirectoryUserDetails(Args _args)
{
    xAxaptaUserManager                  axUsrMgr;
    xAxaptaUserDetails                  axUsrDet;

    #define.NewDomain("yourDomain.com")

    axUsrMgr = new xAxaptaUserManager();

    try
    {
        axUsrDet = axUsrMgr.getDomainUser(#NewDomain, 'activeDirectoryUserName');

        if (axUsrDet)
        {
            info(strFmt('%1', axUsrDet.getUserMail(0)));
            info(strFmt('%1', axUsrDet.getUserSid(0)));
            info(strFmt('%1', axUsrDet.getUserName(0)));
        }

    }
    catch
    {
        info('Could not find');
    }
}

but please note AxaptaUserManager runs on the Client.
This cannot be used with the new batch framework. Since new batch
ramework runs on the server and we cannot call code on the client.

Convert a List to Array in Dynamics Ax

here a simple helper method to convert a List collection to an Array.

static Array arrayFromList(List _list)
{
     Array array;
     ListIterator listIterator;
     int i;

    array = new Array(_list.typeid());
     listIterator = new ListIterator(_list);

    for (i = 1; listIterator.more(); ++i)
     {
         array.value(i, listIterator.value());
         listIterator.next();
     }

    return array;
}

Chart of account get ordered list from x++ in Dynamics Ax

If you want to get the list here in the Chart of accounts in code is quite simple, here below an example.

image

static void FCH_chartOfAccountsList(Args _args)

{

    LedgerChartOfAccountContract chartOfAccounts = new LedgerChartOfAccountContract();

    DimensionServiceProvider serviceProvider = new DimensionServiceProvider();

    List mainAccountsList;

    MainAccountContract contract;

    ListEnumerator mainAccountsListEnumerator;

 

    chartOfAccounts.parmName("COABasic");

 

    mainAccountsList = serviceProvider.getMainAccountsForLedgerChartOfAccount(chartOfAccounts);

    mainAccountsListEnumerator = mainAccountsList.getEnumerator();

 

    while (mainAccountsListEnumerator.moveNext())

    {

        contract = mainAccountsListEnumerator.current();

        info(strFmt('%1 - %2 - %3 - %4',

                contract.parmMainAccountId(),

                contract.parmName(),

                contract.parmType(),

                MainAccountCategory::findAccountCategoryRef(contract.parmAccountCategoryRef()).AccountCategory));

    }

}


image

There is just a little issue you might want to address. As you noticed the list is not in the ordered.

If you need an ordered list, since you can’t order the collection List, we can convert it to a Map, which by default orders by its key value.

static void FCH_chartOfAccountsListOrderd(Args _args)

{

    LedgerChartOfAccountContract chartOfAccounts = new LedgerChartOfAccountContract();

    DimensionServiceProvider serviceProvider = new DimensionServiceProvider();

    List mainAccountsList;

    MainAccountContract contract;

    ListEnumerator mainAccountsListEnumerator;

    Map mainAccountsMap = new Map(Types::String, Types::Class);

    MapEnumerator mainAccountsMapEnumerator;

 

    chartOfAccounts.parmName("COABasic");

 

    mainAccountsList = serviceProvider.getMainAccountsForLedgerChartOfAccount(chartOfAccounts);

    mainAccountsListEnumerator = mainAccountsList.getEnumerator();

 

    while (mainAccountsListEnumerator.moveNext())

    {

        mainAccountsMap.insert(

                    mainAccountsListEnumerator.current().parmMainAccountId(),

                    mainAccountsListEnumerator.current());

    }

   

    mainAccountsMapEnumerator = mainAccountsMap.getEnumerator();

   

    while (mainAccountsMapEnumerator.moveNext())

    {

        contract = mainAccountsMapEnumerator.currentValue();

       

        info(strFmt('%1 - %2 - %3 - %4',

        contract.parmMainAccountId(),

        contract.parmName(),

        contract.parmType(),

        MainAccountCategory::findAccountCategoryRef(contract.parmAccountCategoryRef()).AccountCategory));

 

    }

}

 

now it orders by account number.

image