Wednesday, June 14, 2023

D365 FinOps - How call a runnable class with URL parameters

We are already familiar with calling D365FO URL menu items or forms or Runnable class.

To recap, I am giving few samples below

To call Display menu item from URL,

https://D35FO/?cmp=EC&mi=SalesQuotationListPage
https://D365FO/?cmp=EC&mi=SalesTableListPage

To call a form from URL,

https://D365FO/?cmp=EC&f=VendTransOpen
https://D365FO/?cmp=EC&f=CustParameters

To browse the data from URL,

https://D365FO/?cmp=EC&mi=SysTableBrowser&TableName=Currency

To call the Runnable class,

https://D365FO/?cmp=EC&mi=SysClassRunner&cls=RunnableClassName

We use these methods often in our day to day FO work.

If the runnable class requires a parameter, it is also possible to pass the value of the parameter.

Given below my runnable class, which tries to open SalesCreateQuotation passing the customer value as it dynamic parameter

class AE_SalesQuotationCreate
{
    public static void main(Args _args)
    {
        AE_SalesQuotationCreate salesQuotationCreate = new AE_SalesQuotationCreate();
        MenuFunction             menuFunction;        
        CustTable                custTable          = CustTable::find('1001');        
        Object                   formRun;

        Args args = new Args();        
        args.record(custTable);        
        menuFunction = new MenuFunction(menuitemActionStr(SalesCreateQuotation),  MenuItemType::Action);
        menuFunction.run(args);     
        
    }

}

In order to call the runnable class with customer value in FO URL,

https://D365FO/?cmp=USMF&mi=SysClassRunner&cls=AE_SalesQuotationCreate&custAccount='1001'

You can use any name in the parameter for passing the customer value ,here I used custAccount as the parameter name.

https://D365FO/?cmp=USMF&mi=SysClassRunner&cls=AE_SalesQuotationCreate&p1='1001'
https://D365FO/?cmp=USMF&mi=SysClassRunner&cls=AE_SalesQuotationCreate&cc='1001'
(From AnithaEswaran)

Friday, June 9, 2023

D365 F&O: Using display method of table on form

So simple is as below screenshot






D365 F&O: Event handler on Table

For event ValidatedField 

[DataEventHandler(tableStr(TableName), DataEventType::ValidatedField)]

    public static void TableName_onValidatedField(Common sender, DataEventArgs e)

    {

        ValidateFieldEventArgs event = e as ValidateFieldEventArgs ;

        TableName buffTable = sender as TableName;

        boolean result = event.parmValidateResult();

    }

For method validatedField

    [PostHandlerFor(tableStr(TableName), tableMethodStr(TableName, validateField))]

    public static void TableName_Post_validateField(XppPrePostArgs args)

    {

        TableName buffTable = args.getThis();

        FieldId fieldId = args.getArg(“_fieldId”);

        boolean ret = args.getReturnValue();


        args.setReturnValue(ret);


    }

Thursday, June 8, 2023

D365 FinOps - How compare sources x++ using the extension import project

How to compare an AOT object in D365FO. If you received a project export (.axpp file) and you want to see what's the changes between this file and your current code. While importing this project (from Dynamic 365 menu in VS), right-click on the object and you will get the option to compare this object from your current metadata.
It will open a compare windows 






Where 
1 is a model store or your current metadata. 
2 is Target file or file from import project 
3 is a menu bar with 'Next', 'Previous', 'Expand all' and 'compare code' options. 

When you click on 'compare code' option it will open this compare file in the visual studio itself, similar to other compare option. 


Finally, always remember that all checked objects in screen Import project will be run to import source code, otherwise nothing. 
And also do a backup before importing anything



Friday, June 2, 2023

D365 FinOps: Accessing private or protected class methods and members from extension code

 All class member variables are protected by default in AX 7, so it is impossible to access them from extensions. It becomes a real problem when you try to extend classes like SalesLineType.

For example, we want to add custom logic on sales line insert event. Call to super() is commented out so we cannot create pre or post event handlers. We may try to create event handlers for SalesLineType.insert() mehtod, but we won’t get access to salesLine buffer because this variable is protected.

There are two options: use overlaying or use reflection.

Overlaying is preferable approach, but today we will talk about reflection to explain this option.

Reflection is usually used for unit testing in case you need to cover protected or private code and it is hard to call this code using public API.

It has bunch of disadvantages:

  • It breaches entire basis of OO programming.
  • Slow performance.
  • Possible issues with future updates. Private methods could be changed at any time.

However, once you may get into situation where it could be the only option so it’s better to know about this possibility.

Let’s try to change some fields on sales line insert using reflection.

Create new event handler class for SalesLineType and subscribe to post insert:

using System.Reflection;

/// 
/// Handles events raised by SalesLineTypeEventHandler class.
/// 
public class SalesLineTypeEventHandler
{
    [PostHandlerFor(classStr(SalesLineType), methodStr(SalesLineType, insert))]
    public static void SalesLineType_Post_insert(XppPrePostArgs _args)
    {
        SalesLineType salesLineType = _args.getThis();

        var bindFlags = BindingFlags::Instance | BindingFlags::NonPublic;

        var field = salesLineType.GetType().GetField("salesLine", bindFlags);

        SalesLine salesLine = field.GetValue(salesLineType);

        if (salesLine)
        {
            salesLine.MyNewField = 42;
            salesLine.doUpdate();
        }
    }
}

Also we can call private or protected method:

var bindFlags = BindingFlags::Instance | BindingFlags::NonPublic;

var methodInfo = salesLineType.GetType().GetMethod(methodStr(SalesLineType, checkQuantityUpdate), bindFlags);

if (methodInfo)
{
    methodInfo.Invoke(salesLineType,  new System.Object[0]());
}

You can read more about reflection on msdn

A dialog on form with the customer lookup for a specific financial dimension in D365 FinOps

 Recently I had a request to create a custom form in Dynamics 365 Finance and Operations. One requirement was that prior to the form opening, it would prompt the user for a date (defaulted in with the current date) and a value from a specific financial dimension. The working prompt form would look like this (with the custom lookup code on the financial dimension)

So, here's how I would create a custom dialog on a form with a customer lookup for a specific financial dimension in D365 FinOps. The dialog code itself is not hard to do. It simply is placed in the init() method of the main form

    public void init()
    {
        super();

        // build the dialog for prompt
        Dialog dialog = new Dialog("Label goes here",element);
        DialogField dfDate = dialog.addField('Date1980','Date');
        dfDate.value(DateTimeUtil::getToday(DateTimeUtil::getUserPreferredTimeZone()));

        DialogField dfDimValue = dialog.addField('DimensionValue','LocationSite');
        FormBuildStringControl control;
        control = dfDimValue.control();

        // use the custom lookup
        control.registerOverrideMethod('LookUp','dimLookup',this);

        // if we click OK, we run, otherwise, close the form
        if (dialog.run())
        {
            dlDate = dfDate.value();
            dlDimValue = dfDimValue.value();

            boolean dlClosed = dialog.closedOk();

            if (dlClosed)
            {

		   // code does in here
            }
        }
        else
        {
            element.close();
        }

    }

This key line in the code above is the following:

control.registerOverrideMethod('LookUp','dimLookup',this);


This line allows us to override the lookup method of the control in the dialog, and replace it with our custom code.  This method exists on the form as well.

    private void dimLookup(FormStringControl _control)
    {
        
        Query query = new Query();
        QueryBuildDataSource qbdsDimensionFinancialTag = query.addDataSource(tableNum(DimensionFinancialTag));
        QueryBuildRange qbrFinancialTagCategory = qbdsDimensionFinancialTag.addRange(fieldNum(DimensionFinancialTag, FinancialTagCategory));
        qbrFinancialTagCategory.value(strFmt('%1', DimensionAttribute::findByName(dimName, false).financialTagCategory()));

        SysTableLookup sysTableLookup = sysTableLookup::newParameters(tableNum(DimensionFinancialTag), _control,true);
        sysTableLookup.addLookupfield(fieldNum(DimensionFinancialTag, Value), true);
        sysTableLookup.addLookupfield(fieldNum(DimensionFinancialTag, Description));
        sysTableLookup.addSelectionField(fieldNum(DimensionFinancialTag, FinancialTagCategory));
        sysTableLookup.parmQuery(query);

        sysTableLookup.performFormLookup();
    }

This is pretty standard lookup code using the SysTableLookup class.  That is all that is needed to accomplish my requirements (other than some specific requirement code that is not relevant here).

You can use this method to create any custom dialog form opening and have a customer lookup for any specific financial dimension in D365 Finance and Operations. If you have any more questions on how to do this yourself, please contact us.

Wednesday, May 31, 2023

D365 F&O// X++: Adding zero numbers into Left/Right text values

 

Sometimes we need to add padding numbers in the files in AX. Ex: Postive pay files
I found the below post that helped to add the padding zero numbers in the positive pay file.

Original post: http://www.atomicax.com/content/zero-padding-numbers

X++ provides us with an easy way to pad strings with extra characters. A common situation where this is required is use a "fake" number sequence inside AX.
If you wish to increment a counter, but store or display it as a string such as "ABC-00001", "ABC-00002" etc then padding the counter with zeros is required.
We can use the strRFix() and strLFix() methods to achieve this

static void testZeroPadding(Args _args)
{
    int i = 1;
    str padded;
    str finalResult;
    ;
    // Create a string, with a length of 5, ending with the value of i and padded with zeros
    padded = strRFix(int2str(i), 5, "0");
    finalResult = strFmt("ABC-%1", padded);

}

Result will be displayed ABC-00001. If we use strLFix instead of strRFix, it will pad to the right with zeros, giving us ABC-10000

Another way, also common thoughts, the approach is below sample

static str zeroPad(str item, int numZero)
{
    int i;
    str out;
    ;
    for(i=0;i<numZero-strlen(item); i++)
    {
        out += "0";
    }

    return(out + item);
}

 

D365 FinOps - How call a runnable class with URL parameters

We are already familiar with calling D365FO URL menu items or forms or Runnable class. To recap, I am giving few samples below To call  Disp...