Electronic Reporting (ER) Cookbook 2: new tips from the kitchen

Electronic Reporting (ER) Cookbook 2: new tips from the kitchen

Since my last blog in 2017 a few things have changed. The developers in Moscow have been extending the module massively, and they seem to have fully embraced the Continuous delivery approach. Every new version or hotfix bears surprises, both pleasant and unpleasant. Here is a small personal excerpt:

      1. Configurations may be declared dependent on a particular application version or even hotfix. Should the developer of a configuration have failed to declare a dependency, the following message may appear on an attempt to import the configuration: “Method parmXXX not found in class ERxxx”, for example “Method parmERTextFormatExcelTemplate not found in class ERTextFormatExcelFileComponent”. It means the configuration definition cannot be de-serialized from an XML file in this old application version.
      2. From version 7.3 onward, parts of the application logic of the ER module had been extracted to the Microsoft.Dynamics365.LocalizationFramework .NET assembly for better performance. Now not only the configuration relies on a particular application version, but the application too demands a certain binary hotfix. For example, the SEPA camt.054 bank statement requires application hotfixes KB4096419, KB4092831, KB4089190, KB4077379, while the designer UI will not start without the ER cumulative update KB4090174 i.e. binary version 7.0.4709.41183
      3. From version 7.3 it had become possible to create inbound electronic formats i.e. those reading files instead of writing them. In 7.2 you could configure the same, but it was simply failing to work properly. It was not giving you any errors but failing to traverse records in the CSV file.
      4. Since version 7.2 you may define a Sharepoint folder destination (Organization administration > Electronic reporting > Electronic reporting destination). Consider the following scenario: the electronic report runs in a batch and saves files in a Sharepoint folder. A client-side process synchronizes the folder with the local network, and a local daemon picks up the files to push them through a legacy banking software or a tax authority middleware.
      5. When reading or writing data in D365FO, the ER is able to scan 1:n and n:1 table and entity relations for foreign keys. However, your new tables and entities are not going to appear under the Relations node of the data model mapping in the Designer, as the ER module maintains its own list of table relations in the database. Even worse, it may import and run a custom configuration with no errors but silently skip the custom table on reading and writing. Apply Organization administration > Electronic reporting > Rebuild table references to make your custom tables available.
      6. When developing or customizing configurations, it is not necessary to Complete the versions every time to test them. The ‘integration point’ e.g. a payment journal may execute the latest Draft version of the format too. Use the button Advanced settings / User parameters / Run settings, then flip the Run draft slider that appears.
      7. After upgrading to 7.3 you may find yourself unable to edit configurations anymore. The reason may be the new parameter Enable design mode available at the Electronic reporting workspace, form Electronic reporting parameters.
      8. It seems that they’ve protected configurations made by Microsoft from editing in one of the recent updates. Instead, you are supposed to always derive a configuration from the base one, and assign it a custom Provider. This corresponds to the extension paradigm as opposed to ‘overlayering’ of a configuration. Now, what are you going to do with your existing customized formats? Create an own provider (Organization administration > Electronic reporting > Configuration provider table), give it a name and a web address of your organization. Make this provider Active at the ER workspace. Export your configuration into an XML file, then delete it from the list of solutions in ER. Replace the XML tag with your provider, then re-import the configuration from the file. The system creates a new Draft version you are now able to edit.

Copy-paste with keyboard script 2: from Excel to D365FO

Copy-paste with keyboard script 2: from Excel to D365FO

Again an entity was missing in D365FO to import the data, this time a custom one. Building upon my work Copy-paste automation in D365 FO with a keyboard script, I created a Wunderwaffe script to copy a simple table with a Code and a Description directly from Excel into the D365FO browser window.

Close all other windows and put the browser with D365FO and Excel side by side. Place the cursor in Excel in the Code column at the first row to copy, then hit Ctrl-J. The script will start copying data from Excel and pasting records in D365FO by switching windows forth and back:
^j::
; --------- Place the focus on the code column in Excel, start with Ctrl-J
Loop 200 {
; --------- Copy the content of the Code from Excel
Send, ^c
Sleep, 500
; --------- Switch the window: Alt-Tab
Send, !{TAB}
Sleep, 1000
; --------- Create a new record in the D365FO window
Send, {Alt Down}
Sleep, 500
Send, n
Sleep, 500
Send, {Alt Up}
Sleep, 1000
; --------- Go to the next field and back in the D365FO to get into the edit mode
Send, {TAB}
Sleep, 1000
Send, +{TAB}
; --------- Paste the code
Send, ^v
; --------- Switch the window and go back to Excel
Send, !{TAB}
Sleep, 1000
; --------- Copy the content of the Description column from Excel
Send, {Right}
Send, ^c
Sleep, 1000
; --------- Switch the window for D365FO
Send, !{TAB}
Sleep, 1000
Send, {TAB}
Sleep, 500
; --------- Paste the description
Send, ^v
; --------- Switch the window for Excel
Send, !{TAB}
Sleep, 1000
; ---------Scroll down to the next record, and repeat the cycle
Send, {Down}{Left}
}
Return
; Press F1 in panic
F1::ExitApp

X++ Decorator pattern in Dynamics 365

X++ Decorator pattern in Dynamics 365

A couple of times in my career I stumbled upon a development requirement to perform a set of loosely connected actions with a similar interface upon a certain business object depending on parameters or conditions:

  • Sometimes do this
  • Sometimes do that
  • Sometimes do this and that
  • In future, you may do something else in addition.

In Dynamics 365 for Finance and Operations, this usually results in a hierarchy of classes. The classes are typically bound to some parameter table containing checkboxes or – better – an enumeration field. This enumeration directs the SysExtension class factory which objects to instantiate.
A simple while_select loop traverses the parameter table and executes the instantiated objects one by one. However, if the classes implement more than one action i.e. method, this external loop may become repetitive. I ended up with a certain design pattern which turned out to have a name in object-oriented programming: a Decorator an internal iterator.
Errata: as readers pointed out, there is more of a Decorator pattern in this, than an Iterator pattern.

The constructor instantiates an object and gives it the previous object as a parameter, thus forming a linked chain of objects. The external actor only needs to take one parameter – the topmost object in the chain – and to call one operation .doThis() upon it. This .doThis() method is exclusively implemented in the parent class of the hierarchy, and the parent class knows how to iteratively call the objects.

Adding a new grid with a parameter table to an existing form is more difficult than adding new field(s) into an existing table via extensions. If the number of possible actions is countable and small, you may think of de-normalizing the parameter table and making an array field with possible options in one record. Instead of iterating records, the constructor will be iterating fields.

Below is an application of this pattern to a simple task: calculation of the nutrition value of one meal, where a meal may consist of an Entrée, an Entrée and a Main course, a Main course and a Dessert and so on:

 

				
					class MealCourseAtrribute extends SysAttribute implements SysExtensionIAttribute
{
    MealCourseEnum mealCourseEnum;
    public void new(MealCourseEnum _mealCourseEnum)
    {
        super();
        mealCourseEnum = _mealCourseEnum;
    }
    public str parmCacheKey()
    {
        return classStr(MealCourseAtrribute) + enum2Symbol(enumNum(MealCourseEnum), mealCourseEnum);
    }
    public boolean useSingleton()
    {
        return true;
    }
}

abstract public class MealCourse
{
    private MealCourse prevMealCourse;
    abstract protected MealKcal kcal()
    {
    }
    final public MealKcal kcalTotal()
    {
        MealKcal ret = this.kcal();
        if (prevMealCourse)
        {
            ret += prevMealCourse.kcalTotal();
        }
    return ret;
    }
    private MealCourse prevMealCourse(MealCourse _prevMealCourse = prevMealCourse)
    {
        prevMealCourse = _prevMealCourse;
        return prevMealCourse;
    }
    protected void new()
    {
    }
    public static MealCourse construct(Meal _meal)
    {
        MealCourse mealCourse, prevMealCourse;
        MealCourseAtrribute attr;
        MealCourseEnum mealCourseEnum;
        int i;
        for (i = 1; i <= dimOf(_meal); i++)
        {
            mealCourseEnum = _meal[i];
            attr = new MealCourseAtrribute(mealCourseEnum);
            mealCourse = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(MealCourse), attr);
            if (prevMealCourse)
            {
                if (mealCourseEnum == MealCourseEnum::None)
                {
                    continue;
                }
                mealCourse.prevMealCourse(prevMealCourse);
            }
            prevMealCourse = mealCourse;
        }
        return prevMealCourse;
    }
    public static void main(Args _args)
    {
        info(strFmt("Total calories in this meal is %1", MealParameters::find().meal().kcalTotal()));
    }
}

[MealCourseAtrribute(MealCourseEnum::Entree)]
public class MealCourseEntree extends MealCourse
{
    public MealKcal kcal()
    {
        return 140;
    }
}

[MealCourseAtrribute(MealCourseEnum::Main)]
public class MealCourseMain extends MealCourse
{
    public MealKcal kcal()
    {
        return 600;
    }
}

[MealCourseAtrribute(MealCourseEnum::Dessert)]
public class MealCourseDessert extends MealCourse
{
    public MealKcal kcal()
    {
        return 200;
    }
}
				
			

Dynamics 365 FO may return no records for a certain table, but it cannot return an NULL value for a field; in addition, I would like to spare the caller an if (! parameter) {return}; validation. This is why the caller is always given at least one object of the MainCourseNone type which returns a zero and does nothing. The API then reduces to a one-liner:
… = MealParameters::find().meal().kcalTotal();
here for a main course and a dessert:
Decorator pattern: Nutrition value calculation

Adding a new meal course type e.g. a soup is as simple as adding a new enumeration element to the MealCourseEnum, adding a new array element to the Meal extended data type (but only if the Soup may be ordered in addition to the other courses), and implementing a class tagged with this enumeration element as an attribute:

				
					[MealCourseAtrribute(MealCourseEnum::Soup)]
public class MealCourseSoup extends MealCourse
{
    public MealKcal kcal()
    {
        return 100;
    }
}
				
			

Beware of the current limitations in the array fields support in D365FO: they cannot be exposed in Excel or imported properly in the Data management module.
You may download the source code here: TheMealProject