MSDyn365FO. How-to set brand id for a report.

Document branding is a really cool feature, unfortunately it is not installed by default and has only several reports available, so not widely used. However, besides SSRS report designs there is a setup available under Organization administration -> Setup -> Document branding, allowing you to define your brand and set logos, background colors, address and contact information, making your reports more configurable from UI. There is another gotcha here, by default your brand is a company id. But in some cases, you may have different business inside of one company and want to have your brand per business unit, for example.

It’s quite an easy change, all we need to do, is to set  SysDocuBrandContract. parmBrandId(). Below is an example for Sales Invoice report

[ExtensionOf(classStr(SalesInvoiceController))]
public final class SalesInvoiceController_IM_Extension
{
    protected void preRunModifyContract()
    {
        next preRunModifyContract();

        SrsReportDataContract reportDataContract = this.parmReportContract();

        if (!reportDataContract.parmDocuBrandContract())
        {
            reportDataContract.parmDocuBrandContract(new SysDocuBrandContract());
        }

        var brandId = DimensionAttributeValueSetStorage::find(custInvoiceJour.DefaultDimension).getDisplayValueByDimensionAttribute(DimensionHelper_IM::getBusinessUnitDimensionAttribute());

        reportDataContract.parmDocuBrandContract().parmBrandId(brandId);
    }
}

As you can see, instead of getting dimension value by name or backing table id, I have a helper class that retrieves dimension attribute. Usually, it is stored in parameters table and gives you flexibility to change dimension name at any time and does not cause issues if you have 2 dimensions with same backing table, like “From BU” and “To BU”.

Another thing to consider is creation of SysDocuBrandContract. In my example, I populate only brandId, however, you can set branding details per report and per design, so they should be populated as well. Framework does it by default for all preprocessed reports, however, for query-based reports you need to set parmReportName and parmDesignName manually.
You may say that no one does SSRS reports these days and that’s a reasonable remark. ER is great but even there you have SysDocuBrandDetails table available, so it can be used to avoid hardcoded colors and logos!

MSDyn365FO. OData integration, how-to set financial dimensions.

As we know, we can add financial dimensions as separate columns to Excel templates, but what about OData integration?

To set dimensions you must assign a value to DefaultDimensionDisplayValue or AccountDisplayValue fields. They can have different names, but essentially, we are talking about DisplayValue field on DimensionCombinationEntity and DimensionSetEntity entities. This value represents all dimensions separated by a delimiter. You have to configure it on the “Financial dimension configuration for integrating applications” form.

For example, we have 3 dimensions: CostCenter, Department, BusinessUnit. Delimiter set to “-“. Values are “A”, B”, “C”, so we need to use “A-B-C” string. However, we do not want to hard-code delimiter or order of the dimensions. We want to make sure that we use only dimensions configured!

There are 2 OData actions available: getSetDisplayValue on DimensionSetEntity entity and getCombinationDisplayValue on DimensionCombinationEntity entity.

getSetDisplayValue accepts two arrays: array of attribute names and array of attribute values. It skips dimensions that are not configured and returns delimited string.

POST /data/DimensionSets/Microsoft.Dynamics.DataEntities.getSetDisplayValue

Body
{
    "_attributeNames" : ["CostCenter", "Department", "BusinessUnit"],
    "_attributeValues" : ["A", "B", "C"]
}

Successful Response:

HTTP/1.1 200 OK
{
    "@odata.context" : "https:///data/$metadata#Edm.String",
    "value" : "A-B-C”
}

getCombinationDisplayValue accepts entity name, dimension field name, two arrays (array of attribute names and array of attribute values) and account type.

POST /data/DimensionCombinations/Microsoft.Dynamics.DataEntities.getCombinationDisplayValue

Body
{
    "_entityName" : "CustomerPaymentJournalLine",
    "_propertyName" : "OffsetAccount",
    "_attributeNames" : ["MainAccount", "CostCenter"],
    "_attributeValues" : ["1000", "A"],
    "_ledgerJournalACType" : "Ledger"
}

Successful Response:

HTTP/1.1 200 OK
{
    "@odata.context" : "https:///data/$metadata#Edm.String",
    "value" : "1000-A"
}

Both methods use DimensionResolver::getEntityDisplayValue() under the hood that replaced AxdDimensionUtil in current version.

MSDyn365FO. How-to automatically start a build VM for a build

While we are waiting for Microsoft-hosted agents support (build without a VM) to be released, we have to have a dedicated build VM that generally sits there and does nothing. To save costs we can setup a schedule to turn it on and off, however, you have to align your build schedule with it, and it does not make a lot of sense if you do not have active development and do changes once in a while.

Here is a quick and dirty workaround. You can get Azure Virtual Machine Manager extension from visual studio marketplace.

Azure Virtual Machine Manager

It adds one task to stop or start a VM.

Now we need to modify standard build definition and add new task to start build VM.

You cannot add it to existing agent job, because this one is self-hosted and requires VM to be up and running, catch 22! So, add another agent job:

MSHostedAgentJob.jpg

Bring it forward and add Azure VM task:

AzureVMStart.jpg

Now your pipeline should look like this:

BuildPipeline

And to make it work we need one more change. In the build options it demands DynamicsSDK and obviously new agent won’t have it and will fail, so I simply removed DynamicsSDK from the list, that’s why I call this quick and dirty!

BuildDemand

To stop a VM after the build I put Azure VM task in the very beginning of the release pipeline that is automatically triggered after a successful build.

ReleasePipeLine.jpg

Using this neat extension, we can automatically start a VM before a build starts and then immediately turn it off after the build. I deliberately put stop task in the release pipeline, so it won’t stop VM if build fails and I can investigate or quickly rerun it without waiting for VM startup. Obviously, one day we will get ability to use MS hosted agent but meanwhile it may help you to save some $$.

MSDyn365FO. Electronic Reporting. Parse a text file with different record types.

In the previous blog post series we learned how to import simple CSV file. However, CSV and other text files may contain records of different types that should be imported to different tables or process differently. In this post we will enhance format and mapping created. Let’s say in our example first column represents records type (a, b or c):

CSVImportTestFile

In this case we need to change format and add new “CASE” element:

FormatCase

And 3 record sequences instead of one:

FormatCaseRecords

Note that each sequence has a predefined value for the String field, that’s how we tell ER that if record has “a” in the first column it should be parsed with RecordA sequence. Also, we changed Multiplicity to “One many” for the sequences, to tell ER that there is at least 1 record for each type. It could be set to “Zero many” if a record is optional or “Exactly one” if we expect it only once in a file.

Now we need to change mapping. Each sequence has system field “isMatched” that is populated if file record is matched to sequence pattern. We will use it to bind 3 record types to same model field, but in a real life examples different records may go to different tables, like header and lines.

MapModelToFormatCase

Expression used is pretty simple, it takes value from the first record if it is mapped, if not, it checks second record and if it is not mapped as well it takes value from the third.

IF(@.Types.RecordA.IsMatched, @.Types.RecordA.Data.FieldInt, IF(@.Types.RecordB.IsMatched, @.Types.RecordB.Data.FieldInt, @.Types.RecordC.Data.FieldInt))

Also using IsMatched you can provide default values when it is missing in a file.

MSDyn365FO. How to Import CSV file using Electronic Reporting. Part 4 – Map Model to Datasource.

That’s the last step. We have Format to parse data to a model, now we need to write data from model back to actual tables. Open model designer and click “Map model to datasource” in the action pane.

Create new record, set name and description. Select direction, for import it should be “To destination”:

MapDatasourceMapping

Open designer. In the Data Sources area add data model:

MapDatasourceMappingAddModel

Set name, select model and definition:

MapDatasourceMappingAddModelProperties

Add new destination:

MapDatasourceMappingAddDestination

Select table you want to insert to, set name, specify if it should support insert or update\insert and if you need validation to be triggered:

MapDatasourceMappingAddDestinationSelectTable

Bind Record List from model to tables’ record list and bind field accordingly:

MapDatasourceMappingBind

Save and we are ready to test it!

Go back to Model to Datasource mapping screen, select mapping record and click run in the action pane:

MapDatasourceMappingRun

Select file and check the result. I do not have UI for this table, so just going to use table browser:

ImportCSV_TableBrowser

It’s not that nice to trigger import from the mapping record but that’s the only way I know and in the next post we will look how to trigger it from X++ code, so we could have a button somewhere.

As you can see ER is a really powerful tool that allows users to do tasks that were not possible without development before.

MSDyn365FO. How to Import CSV file using Electronic Reporting. Part 3 – Map format to model.

In this blog post we will create new mapping to map format to model. On the format designer screen, click “Map format to model” button. Create new record, select model definition, specify name and description:

MapModelToFormat

Open designer. In the designer bind Record List from the format to Record List in the model and then bind fields accordingly.

MapModelToFormatBinding

Finally, we can test our format. For the test I’m going to use simple CSV file:

CSVImportTestFile

Go back to Model to Datasource mapping form, click run in the action pane and upload the file.

MapModelToFormatTest

If we’ve done everything right, we will get XML file that contains data mapped to model or errors, if any:

MapModelToFormatTestResult

At this stage we have Data Model, Format and Mapping that we’ve tested. In the next blog post we will do the last piece of the setup – map Model to Destination and test the whole import.

 

MSDyn365FO. How to Import CSV file using Electronic Reporting. Part 2 – Format.

In this blog post we will create new Format. It represents document schema and is used to parse it. Go to Organization administration > Workspaces > Electronic reporting, select Data Model created in the previous post and create new configuration:

CSVImportFormat

In the format designer Add root –> File:

FormatRoot

Add sequence and set delimiter to New line – Windows (CR LF). It will tell ER that file has lines split by CR LF. It is possible to select CR for Mac or LF for Linux or specify a custom delimiter.

FormatRootSequence

Add new sequence. This sequence will represent lines. Set Multiplicity to “One many” to say that at least one line is required.

FormatRootLine

Add another sequence. It will represent individual lines. Set delimiter to ‘,’, to split fields by comma. Use another delimiter, if required.

FormatRootRecord

Add 3 fields:

FormatFeilds

In the end you should have format like this:

Format.png

In the next post we will map format to model and test it!

 

 

MSDyn365FO. How to Import CSV file using Electronic Reporting. Part 1 – Data Model.

In this post series I will show how to use Electronic Reporting (ER) to import a CSV file. This tool allows us to create new import process without a line of X++ code. It can be maintained by end users without developer’s help and does not require deployments, because can be easily transferred by XML export and import between environments.

I used documentation available, but it does not have enough details, so here I will try to explain the process step by step. We will start from a Data Model creation and go thought all the stages below:

er-overview-import.png

For simplicity, I’m going to use a custom table that has 3 fields: String, Real and Int.

TestImportTable.png

Data Model is an abstraction over destination\source tables and could be used by multiple different formats. In our case, it will be similar to destination table because it’s quite simple. To create it, go to Organization administration > Workspaces > Electronic reporting.

CSVImportModel

In the designer create model root node:

ModelRoot

Add Records List:

ModelLines.png

Now add 3 fields, one for each in a source file:

ModelFIelds

In the end you should get this structure:

DataModel

Change model status from Draft to Completed, it is required for next step.

In the next blog post we will create new Format.

MSDyn365FO. Code to build cross reference data without a full compile.

After 8.1 there is no need to do code upgrade and build standard code supplied by Microsoft or at least we’ve been told so. However, it’s impossible to refresh cross references without building model with Visual Studio tools and we need this because now hotfixes and monthly updates are cumulative and include binaries and X++ code as well, so cross reference data on dev VMs becomes outdated quite quickly.

While MS is working on actual solution, I dug a bit into xppc that compiles X++ code and build cross references and here you are, this code can be used to build cross references for a module without compile, that is way faster!

using Microsoft.Dynamics.AX.Framework.Xlnt.XReference;
using Microsoft.Dynamics.AX.Metadata.XppCompiler;
using System;

class XRefBuilder
{
    static void Main(string[] args)
    {
        try
        {
            string moduleName = "ApplicationCommon";
            string metaDataPath = @"K:\AosService\PackagesLocalDirectory";

            ICrossReferenceProvider xRefProvider = CrossReferenceProviderFactory.CreateSqlCrossReferenceBatchProvider(".", "DYNAMICSXREFDB", moduleName, true, true, new DiagnosticsHandler());
            xRefProvider.StartBatch();
            new MetadataXRefSweeper( metaDataPath, moduleName, xRefProvider, DateTime.MinValue, new DiagnosticsHandler()).Run();
            xRefProvider.CommitBatch();
        }
        catch (AggregateException ae)
        {
            ae.Handle(ex => {
                    Console.WriteLine(ex.InnerException != null ? ex.InnerException.Message : ex.Message);
                return true;
            });

        }
    }
}

Note catch section, it was a big surprise for me, but standard code has lots of compile issues, for example, KanbanMultiDelete action menu item has EnumParameter property populated but EnumTypeParameter is not. In 2012 days it was not possible to do this, but now you can literally type anything into EnumParameter without specifying enum and save it. Cross reference builder would try to find that enum and throw an exception saying that cannot find an enum with empty name. That’s why I have that catch section and that’s what xppc does, just skip all these errors and probably log them somewhere.

Another gotcha here is OutOfMemoryException that you can get with ApplicationSuite model, so don’t forget to handle it as well.

 

UPDATED:

MetadataXRefSweeper build cross references only for objects like EDT or Tables, but does not cover source code. To build cross references for source code you have to actually compile it 😦