Business Central Power Automate Document Approvals – Cancel โŒ

Have you noticed when using Power Automate for BC document approvals you have the same approval buttons? One for sending and one for cancelling. The sending one works fine. Cancel on the other hand isnโ€™t doing what we need. The approvers will still have an outstanding approval and the flow will keep waiting to run. Eventually when it does run it will fail as the request has been cancelled BC side. If no one approves and the rest of the flow doesn’t run then you have a flow run ongoing until it expires…These things are ok. I just think it makes the administration more difficult.

My goal with this post is to have the standard cancel action to actually cancel my Power Automate approval and my Power Automate flow run. Full housekeeping ๐Ÿ˜„๐Ÿก. Let’s cover some basics first:

The approval entries can be viewed by selecting the “Approvals” button. If using Power Automate a different page will appear. It will show a single entry each time an approval is sent. That entry will be adjusted if the approver accepts the request or rejects the request.
Standard BC document approvals would show a line for each approval entry created. With Power Automate we don’t have that detail in BC. This is as much as we get.

With Power Automate you might be using different methods to get the approval dealt with – MS Approvals, Teams, Outlook, other. I will look to solve the MS Approvals scenario as itโ€™s the most common.

So what do we need? Well first thing is a record of information from Power Automate back in BC. So for starters a table extension to the “Workflow Webhook Entry” table. This is where the data from the “Flow Entries” page (above) lives. We have 3 fields as I want to capture details on numerous attributes so I can cancel all the relevant parts.

tableextension 50100 PA_Approvals extends "Workflow Webhook Entry"
        field(50100; "Power Automate Approval ID"; Guid)
            Caption = 'Power Automate Approval ID';
            DataClassification = CustomerContent;

        field(50101; "Power Automate Flow ID"; Guid)
            Caption = 'Power Automate Flow ID';
            DataClassification = CustomerContent;
        field(50102; "Power Automate Run_ID"; Text[100])
            DataClassification = CustomerContent;
            Caption = 'Power Automate Run ID';

Alongside our new fields we are going to need an API page so we can make updates to the BC data. Just showing the properties as this is the important part. I made only my new fields editable

page 50100 PA_Approvals
    APIGroup = 'jaGroup';
    APIPublisher = 'ja';
    APIVersion = 'v2.0';
    Caption = 'paApprovals';
    DelayedInsert = true;
    EntityName = 'paApproval';
    EntitySetName = 'paApprovals';
    PageType = API;
    SourceTable = "Workflow Webhook Entry";
    ODataKeyFields = "Workflow Step Instance ID";
    ModifyAllowed = true;
    InsertAllowed = false;
    Permissions = tabledata "Workflow Webhook Entry" = RIMD;

More AL DEV follows but let’s tackle the Power Automate Document approval next. Start off with the standard template for whichever document you need this for. I’ve chosen Purchase Orders.

Kept the BC connector steps in to show the start of the flow. The changes and additions can be seen after the BC connector steps. The rest of the flow fits the standard template.

The compose action “Workflow ID” contains the expression workflow()?[‘name’] so that I can record the GUID of the workflow. The “Workflow Run ID” contains the expression workflow()?[‘run’][‘name’] so that I can record the text string name of the flow run. The template for a BC document approval comes with a “Start and Wait” approval. I’ve removed this and added the “Create an approval” so that I can find out and record the ID of the created approval. You then need the “Wait for an approval” so the flow has the response. The final change/addition is the HTTP step.

Used this instead of the BC connector – where you can select custom API pages – as I got an error about permissions. I believe this is due to the If-Match * being required as a header to the request. Add the URL to the API page and make reference to the “Workflow Step ID” from the BC Connector – this filters on that record only. The request will then pass a body to the three new fields. Note that each of the values come from the previously mentioned changes/additions to the document approval template.
The result will be the values of the specific Power Automate Flow back in BC.

So all that remains is the cancellation. For the data in BC to end up back in Power Automate a codeunit is required:

codeunit 50100 PA_CancelApproval
    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Workflow Webhook Management", 'OnCancelWorkflow', '', false, false)]
    local procedure CancelPowerAutomateApprovalEntry(WorkflowWebhookEntry: Record "Workflow Webhook Entry")

        if IsNullGuid(WorkflowWebhookEntry."Power Automate Approval ID") then
            SendToFlow(WorkflowWebhookEntry."Power Automate Approval ID", WorkflowWebhookEntry."Power Automate Flow ID", WorkflowWebhookEntry."Power Automate Run_ID");

    local procedure SendToFlow(ApprovalID: Guid; FlowID: Guid; RunID: Text)
        client: HttpClient;
        cont: HttpContent;
        header: HttpHeaders;
        response: HttpResponseMessage;
        Jobject: JsonObject;
        tmpString: Text;
        Jobject.Add('ApprovalID', ApprovalID);
        Jobject.Add('FlowID', FlowID);
        Jobject.Add('RunID', RunID);
        header.Add('Content-Type', 'application/json');
        client.Post('<Add your URL here', cont, response);

The idea here is that another Power Automate flow will be setup to receive a POST. Note that you will need to add the URL to your Power Automate flow for this to work.

Here are the main steps to the flow. Note that the final step is a custom connector. Use this excellent blog to create/download the connector: Everything to know about Power Automate REST API to manage and administer your flows โ€“ Mohamed Ashiq Faleel

The URL shown here is the one you need in the codeunit. Add it to a table if you would rather store it in a visual place. Add a schema of the expected JSON body – link available to paste in a sample payload*
Locate the Dataverse connector and choose the Approvals table. This will remove the approval from a users open queue ๐Ÿ‘ The “Row ID” has an expression as the JSON contains a GUID with speech marks and Dataverse doesn’t like that ๐Ÿ™ˆ to bypass that use substring(triggerBody()?[‘ApprovalID’],1,36). The same thing is needed for the GUID of the FlowID
A user keeps an audit trail though. Note the “Outcome” is “Canceled”.
The Flow run history will show you any that are legitimately waiting on an approvers review with the “Status” of “Running”. Any that have been passed to the cancellation flow are dealt with accordingly. ๐Ÿก house back in order ๐Ÿ˜…

Use of a webhook could be done here as well. I opted not to use that as you need something in place to renew the webhook and it would have to POST the information to flow I chose anyway. Hardcoding the endpoint is a bit ๐Ÿคฎ but it is simple