How to : generate an external trigger after the completion of BYODW export using the business event in Dynamics 365 Finance and Operations

In BI projects for Dynamics 365 Finance and Operations, the most common requirement is the trigger when the BYODW batch job completes the data population. Usually, to start another process on exported data.

After some research, we found the best solution for this is to create a custom business event. Business events in Dynamics 365 are robust and flexible enough to be used for many different scenarios.

To understand the Business events framework, please go through https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/business-events/home-page

The business events framework provides different endpoints for the consumption of the trigger.


  • Azure Service Bus Queue
  • Azure Service Bus Topic
  • Azure Event Grid
  • Azure Event Hub
  • HTTPS
  • Microsoft Power Automate

Below is the custom code required for the generation of events.

We need 4 classes: 
1. BYODWBusinessEvent 
2. BYODWBusinessEventContract
3. DMFPackageExporter_Extension (This is for manual execution)
4. DMFExportEntityWriter_Extension (This is for a batch execution)

1. BYODWBusinessEvent class extends the BusinessEventsBase class.

/// <summary>
/// BYODW business event.
/// </summary>
[BusinessEvents(classStr(BYODWBusinessEventContract), 'BYODW Business Event', 'BYODW Business Event', ModuleAxapta::DIXF)]
public final class BYODWBusinessEvent extends BusinessEventsBase
{
    private DMFExecution dmfExecution;

    private DMFExecution parmDMFExecution(DMFExecution _dmfExecution = dmfExecution)
    {
        dmfExecution = _dmfExecution;

        return dmfExecution;
    }

    /// <summary>
    /// Creates a <c>BYODWBusinessEvent</c> from a <c>DMFExecution</c> record.
    /// </summary>
    /// <param name = "_transferTable"> A <c>DMFExecution</c> record.</param>
    /// <returns>A <c>BYODWBusinessEvent</c>.</returns>
    public static BYODWBusinessEvent newFromDMFExecution(DMFExecution _dmfExecution)
    {
        BYODWBusinessEvent businessEvent = new BYODWBusinessEvent();
        businessEvent.parmDMFExecution(_dmfExecution);

        return businessEvent;
    }

    private void new()
    {
    }

    [Wrappable(true), Replaceable(true)]
    public BusinessEventsContract buildContract()
    {
        return BYODWBusinessEventContract::newFromDMFExecution(dmfExecution);
    }

}

2. Contract class for BYODWBusinessEvent extends the BusinessEventsContract class.

/// <summary>
/// The data contract for a <c>BYODWBusinessEventContract</c>.
/// </summary>
[DataContract]
public final class BYODWBusinessEventContract extends BusinessEventsContract
{
    private DMFExecutionSummaryStatus   dmfExecutionSummaryStatus;
    private EndDateTime                 endDateTime;
    private DMFExecutionId              dmfExecutionId;
    private DMFDefinitionGroupName      dmfDefinitionGroupName;

    /// <summary>
    /// Creates a <c>BYODWBusinessEventContract</c> from a <c>InventTransferTable</c> record.
    /// </summary>
    /// <param name = "_transferOrder">DMFExecutionId</param>
    /// <returns>A <c>BYODWBusinessEventContract</c>.</returns>
    public static BYODWBusinessEventContract newFromDMFExecution(DMFExecution _dmfExecution) 
    {
        var contract = new BYODWBusinessEventContract();
        contract.initialize(_dmfExecution);
        return contract;
    }

    private void initialize(DMFExecution _dmfExecution)
    {
        dmfExecutionSummaryStatus   = _dmfExecution.GetExecutionSummaryStatus();
        endDateTime                 = _dmfExecution.getExecutionEndDateTime();
        dmfExecutionId              = _dmfExecution.ExecutionId;
        dmfDefinitionGroupName      = _dmfExecution.DefinitionGroup;
    }

    [DataMember('DMFExecutionSummaryStatus'), BusinessEventsDataMember("DMFExecutionSummaryStatus")] 
    public DMFExecutionSummaryStatus parmDMFExecutionSummaryStatus(DMFExecutionSummaryStatus _dmfExecutionSummaryStatus = dmfExecutionSummaryStatus)
    {
        dmfExecutionSummaryStatus = _dmfExecutionSummaryStatus;

        return dmfExecutionSummaryStatus;
    }

    [DataMember('EndDateTime'), BusinessEventsDataMember("EndDateTime")]
    public EndDateTime parmEndDateTime(EndDateTime _endDateTime = endDateTime)
    {
        endDateTime = _endDateTime;

        return endDateTime;
    }

    [DataMember('DMFExecutionId'), BusinessEventsDataMember("DMFExecutionId")]
    public DMFExecutionId parmDMFExecutionId(DMFExecutionId _dmfExecutionId = dmfExecutionId)
    {
        dmfExecutionId = _dmfExecutionId;

        return dmfExecutionId;
    }

    [DataMember('DMFDefinitionGroupName'), BusinessEventsDataMember("DMFDefinitionGroupName")]
    public DMFDefinitionGroupName parmDMFDefinitionGroupName(DMFDefinitionGroupName _dmfDefinitionGroupName = dmfDefinitionGroupName)
    {
        dmfDefinitionGroupName = _dmfDefinitionGroupName;

        return dmfDefinitionGroupName;
    }

}

3. DMFPackageExporter_Extension which generates the trigger in manual execution

/// <summary>
/// Extension class for DMFPackageExporter
/// </summary>
[ExtensionOf(classstr(DMFPackageExporter))]
final class DMFPackageExporter_Extension
{
    /// <summary>
    /// Export a package for the specified definition group
    /// </summary>
    /// <param name = "packageName">Name of package to generate</param>
    /// <param name = "packageUrl">Url to upload the package to</param>
    /// <param name = "definitionGroupId">Definition group name</param>
    /// <param name = "executionId">Job id</param>
    /// <param name = "reExecute">Indicate if data from target to staging is to be moved</param>
    /// <param name = "legalEntityId">Legal entity context for the operation</param>
    /// <param name = "createPackage">Specify if only the files are needed or if the package is also to be generated</param>
    /// <param name="entitySyncVersion">List of entity and the associated sync version range. This is only required for rerun scenario for older executions. Not enabled for AX UI</param>
    /// <param name="previewCount">Preview count for export.</param>
    /// <param name="_messageId">Message ID.</param>
    /// <returns>ExecutionId for preoject execution.</returns>
    public DMFExecutionId exportToPackageV2(DMFPackageName packageName, SharedServiceUnitURL packageUrl, 
        DMFDefinitionGroupName definitionGroupId, DMFExecutionId executionId, boolean reExecute, 
        DataAreaName legalEntityId, boolean createPackage, Map entitySyncVersion, int previewCount, str _messageId)

    {
        DMFExecutionId localDMFExecutionId = next exportToPackageV2(packageName,packageUrl, definitionGroupId, executionId, reExecute,
            legalEntityId, createPackage, entitySyncVersion, previewCount, _messageId);
        BYODWBusinessEvent businessEvent;
        DMFExecution    localDMFExecution = DMFExecution::find(localDMFExecutionId);
        DMFDefinitionGroup  dmfDefinitionGroup = DMFDefinitionGroup::find(localDMFExecution.DefinitionGroup);
        DMFExecutionSummaryStatus DMFExecutionSummaryStatus = localDMFExecution.GetExecutionSummaryStatus();
    
        switch(DMFExecutionSummaryStatus)
        {
            case DMFExecutionSummaryStatus::Succeeded:
                businessEvent= BYODWBusinessEvent::newFromDMFExecution(localDMFExecution); 
                if(businessEvent && dmfDefinitionGroup.BusinessEventSucess)
                {
                    businessEvent.send();
                }
                break;
            case DMFExecutionSummaryStatus::PartiallySucceeded:
                businessEvent = BYODWBusinessEvent::newFromDMFExecution(localDMFExecution);
                if(businessEvent && dmfDefinitionGroup.BusinessEventPartialSucess)
                {
                    businessEvent.send();
                }
                break;
            case DMFExecutionSummaryStatus::Failed:
                businessEvent = BYODWBusinessEvent::newFromDMFExecution(localDMFExecution);
                if(businessEvent && dmfDefinitionGroup.BusinessEventFail)
                {
                    businessEvent.send();
                }
                break;

        }
        return localDMFExecutionId;
    }

}



4. DMFExportEntityWriter_Extension which generates the trigger in batch execution

/// <summary>
/// Extension class for DMFExportEntityWriter for the business event trigger when export is happening in Batch mode
/// </summary>
[ExtensionOf(classstr(DMFExportEntityWriter))]
final class PgonDMFExportEntityWriter_Extension
{
     public void run()
    {
        next run();
        PgonBYODWBusinessEvent businessEvent;
        DMFExecution    localDMFExecution = DMFExecution::find(this.parmExecutionId());
        DMFDefinitionGroup  dmfDefinitionGroup = DMFDefinitionGroup::find(localDMFExecution.DefinitionGroup);
        DMFExecutionSummaryStatus DMFExecutionSummaryStatus = localDMFExecution.GetExecutionSummaryStatus();
 
        switch(DMFExecutionSummaryStatus)
        {
            case DMFExecutionSummaryStatus::Succeeded:
                businessEvent= PgonBYODWBusinessEvent::newFromDMFExecution(localDMFExecution);
                if(businessEvent && dmfDefinitionGroup.PgonBusinessEventSucess)
                {
                    businessEvent.send();
                }
                break;
            case DMFExecutionSummaryStatus::PartiallySucceeded:
                businessEvent = PgonBYODWBusinessEvent::newFromDMFExecution(localDMFExecution);
                if(businessEvent && dmfDefinitionGroup.PgonBusinessEventPartialSucess)
                {
                    businessEvent.send();
                }
                break;
            case DMFExecutionSummaryStatus::Failed:
                businessEvent = PgonBYODWBusinessEvent::newFromDMFExecution(localDMFExecution);
                if(businessEvent && dmfDefinitionGroup.PgonBusinessEventFail)
                {
                    businessEvent.send();
                }
                break;

        }

    }


}

We also added the parameters in data projects as follows:


Now we just need to configure the business events in System administrator -> Setup -> Business events - > Business events catalog

Business event catalog :


Below is the contents of generated events from Azure Service Bus.


We had configured the Azure Service Bus to capture the event and trigger 3rd party code to process the data further. We can configure Microsoft Power automate (FLOW) to process the event as well.

  

Comments

Popular posts from this blog

How to : get the Deep URL for a particular form with filter in Dynamics 365 Finance and Operations

How to: Solve issue when DVT script for service model MRApplicationService on machine XXX failed

How to: Display individual financial dimension value in grid control