Extending SysOperation contracts with DataMemberAttribute

Extending SysOperation contracts with DataMemberAttribute

Since January 2019, it has become possible to decorate new “parm” methods on extension classes with “DataMember” attributes. In other words, it is now feasible to add a new parameter onto a dialog window of a standard class, even if this class was implemented within the SysOperation framework.
This was enabled by the so-called “4th extension wave” in Platform update 23.

Before PU 23, the below code

				
					[ExtensionOf(ClassStr(AssetRollForwardContract))]
final class AssetRollForwardContract_Extension
{
    private boolean updateDeprInfo;
    
    [DataMemberAttribute(identifierStr(AssetAdditionalAcqDepreciation))]
    public boolean parmUpdateDeprInfo(boolean _updateDeprInfo = updateDeprInfo)
    {
        updateDeprInfo = _updateDeprInfo;
        return updateDeprInfo;
    }
}
				
			

did not compile but brought up the error message “DataMemberAttributeOnExtensionClassNotSupported: The DataMemberAttribute is not supported on extension classes.
SysOperationUI dialog

Now it works, and the new parameter is plugged in nicely at the desired place. Initializing the parameters with default values posed a challenge. As suggested by Mr. Dráb here, the class should be declared with the SysOperationInitializable interface, and amended by an initialize() method. However, if the original class did not implement SysOperationInitializable, your extension cannot do any better.
The only solution that worked was simple as a brick: an inline assignment of the respective field variable (which is another feature in X++, quite a new one):

				
					[ExtensionOf(ClassStr(AssetRollForwardContract))]
final class AssetRollForwardContract_Extension
{
    private boolean updateDeprInfo = true;

    [DataMemberAttribute(identifierStr(AssetAdditionalAcqDepreciation)),
        SysOperationLabelAttribute(literalstr("@SYS4080213")),
        SysOperationHelpTextAttribute(literalstr("@SYS321607")),
        SysOperationDisplayOrderAttribute('4')
        ]
    public boolean parmUpdateDeprInfo(boolean _updateDeprInfo = updateDeprInfo)
    {
        updateDeprInfo = _updateDeprInfo;
        return updateDeprInfo;
    }
}
				
			

The parameter becomes active by default, but the last value selected by the user is still serialized and de-serialized nicely, right as it should be.

Searchable product attributes

Searchable product attributes

Introduction

There is this cool feature in Dynamics 365 for Finance and Operations: the product attributes. You can assign an arbitrary number of flexible attributes to a product. Free text, lookup from a list, integer, real, boolean values are supported. The users are super excited and their first question is going to be: “Can I search by an attribute”?
No, you cannot. At least, not in the D365FO browser UI but only at a retail Point of sales. The excitement fades, and you should quickly choose a more pleasant topic for your demo.

In fact, you can. It is super tricky to configure but once configured and saved as a query, filtering by attribute becomes pretty straightforward.

Configuration

Disclaimer: the below walkthrough only works with product attributes assigned through a procurement hierarchy. Those imposed by a Retail hierarchy through attribute groups cannot be configured for the search. Which is a pity, because the Retail hierarchy is a candy. It can be used as a collection of item templates, by far more powerful than a usual product record template.

What we need here is the CatProdSerchableAttrFilterMaterialized (sic!) table. To get that table populated, you need a procurement hierarchy and a procurement catalogue.

  1. Create an attribute, for example of the a text attribute type: Product information management > Setup > Categories and attributes > Attributes.
  2. Make sure there is a category hierarchy given the Procurement category hierarchy role (Product information management > Setup > Categories and attributes > Category hierarchy role associations).
  3. In Procurement and sourcing > Procurement categories, select a category and add the attribute into the list of Product attributes.
  4. It is essential to set the Searchable mark in this EcoResCatalogControl record. This declares a subset of attributes available for searching.
  5. Choose a few released products, assign them to the above procurement category. The attribute should appear under the Product attributes button. Enter or import the attribute values.
  6. Open Procurement and sourcing > Catalogs > Procurement catalogs. Create a new dummy catalog, for instance “AttributeSearch”.
  7. Use the Publish catalog button. It is important to update the catalog on a recurring basis as you provide more products with the attribute.
  8. Now you should get your hands on the Synchronize product search data periodic function in the Procurement catalog menu. It may be disabled, but you can invoke it by the https://xxx.operations.dynamics.com/?mi=SysClassRunner&cls=CatProductFilterRefreshCacheBatch URL.
  9. This program populates the CatProdSerchableAttrFilterMaterialized table. You can configure it for recurring batch execution.

Design an advanced filter

    1. In the Released product list, open the Advanced filter… (Ctrl-Shift-F3) query.
    2. On the Joins tab, locate the “Products” and join the “CatProdSerchableAttrFilterMaterialized” to it.
    3. To the “CatProdSerchableAttrFilterMaterialized”, add 2 joins side by side: “Attribute” and “The base table for other value tables that each stores values of a different data type”. What a name!
    4. Finally, join the latter with one of the “The value of the Text data type for the attributes” tables depending on the type of the attribute, since every value type is stored in a separate table.
    5. By now, you should have gotten something like this:
    6. The goal is close. On the Range tab, apply a filter to the “Attributes (6)” and “The value of…” tables as shown below. The attribute name filter let the system search in this attribute only; there may be many searchable attributes with the same value type.
    7. Save the query for re-use and apply the filter. Enjoy the result!

SysExtension framework pitfall: avoid new()

SysExtension framework pitfall: avoid new()

This research has been inspired by a real bug in the D365FO v. 10 (preview) application. It turned out that the SysExtentionAppClassFactory doesn’t explicitly call the new() method the 2nd time when it takes a previously resolved class name from the cache.

This is just good enough to pass an automated test or to fool a lazy tester, but in real life this may potentially result in run-time errors or other misbehaviour. Any code, any internal variable initialization you placed in the new() constructor is going to be bypassed the second time you call the same operation within the same client session.

To illustrate this, let’s use the demo project from my other blog.

Add the following code into the new() method of the MealCourse class:
abstract public class MealCourse
{
...
protected void new()
{
info ("Yummy!");
}
}

Create a new action menu item to call this class, and place it on any of the D365FO menus. Call it once:

Right, yummy-yummy-yummy!

Call it again from in the same window (do not reload the browser but just click the menu item the 2nd time):

Nothing. The new() method has not been called.

Conclusion

Do not rely on the new() method when using the SysExtension framework. Let it be empty, do not put any logic at all inside. Perform the variable initialization in a dedicated method or any of the parm() methods, and call them explicitly in your constructor right after the SysExtensionAppClassFactory ::getClassFromSysAttribute() call.