A new way of thinking
It is known by everyone that Power Automate can integrate with Business Central seamlessly nowadays. However, if you are a Business Central consultant, you are probably aware of the limitations this solution offers. Contrary to how events work in BC, Power Automate’s triggers/actions do not allow you to integrate your flow’s logic at BC runtime. Let me illustrate:
It would be amazing if we could find a way to execute a Power Automate flow exactly at the same time as an event subscriber gets triggered. The reason for this is that there are multiple cases where we would want to change how the ERP behaves, not only the data.
Case Study
Let’s think of an imaginary situation where our client doesn’t want Sales Orders to get deleted after posting if “Amount Including VAT” is greater than €500 (it doesn’t make too much sense, but that’s not the point). As BC consultants, we know there is an event that gets triggered in the background that allows us to take this decision by changing the parameter SkipDelete to true:
What we would normally do from a BC perspective is to create a field somewhere in the system, read that field every time the event gets triggered, and evaluate the condition to know if have to skip deletion or not.
But this logic can not be implemented by Power Automate’s BC actions. First, because this event is not included (only Insert, Modify, Delete and some approval flows) and second because BC code execution doesn’t wait on Power Automate’s Flow to be done. It just triggers it and keeps running.
Http Call
Last weekend a colleague of mine, Niels Minnee mentioned my workmate Victor Sijtsma to read this great article (They are great Power Platform consultants, btw).
It immediately came to my mind to use the same behavior for Business Central, let’s check the silly implementation of the solution:
Power Automate flow
We trigger the flow by an HTTP call requested by Business Central when the event is triggered. We send the whole Sales Header record as the body of the request.
We check if the field “Amount Including VAT” is greater than 500. Depending on the output, we set the variable to true or false, and we send it back on the response body.
Now let’s check the Business Central part:
There are 3 easy procedures. Mainly, we just build a function that subscribes to the corresponding event, takes the record Sales Header, converts it into JSON, and makes the HTTP call to the Power Automate Flow. It finally reads the response and sets the boolean accordingly.
codeunit 50100 PowerAutomate
{
//Function 1 the event subscriber
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post", 'OnBeforeDeleteAfterPosting', '', false, false)]
local procedure OnBeforeDeleteAfterPosting(var SalesHeader: Record "Sales Header"; var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesCrMemoHeader: Record "Sales Cr.Memo Header"; var SkipDelete: Boolean; CommitIsSuppressed: Boolean; EverythingInvoiced: Boolean);
var
SalesHeaderRecRef: RecordRef;
JsonResponse: JsonObject;
JToken: JsonToken;
begin
SalesHeaderRecRef.Open(Database::"Sales Header");
SalesHeaderRecRef.GetTable(SalesHeader);
JsonResponse := CallService(RecToJson(SalesHeaderRecRef));
if JsonResponse.Get('SkipDelete', JToken) then
SkipDelete := JToken.AsValue().AsBoolean()
end;
//Function 2 the HTTP Call
procedure CallService(RequestBodyJson: JsonObject) ResponseBody: JsonObject
var
Client: HttpClient;
RequestHeaders: HttpHeaders;
RequestContent: HttpContent;
ResponseMessage: HttpResponseMessage;
RequestMessage: HttpRequestMessage;
ResponseText: Text;
contentHeaders: HttpHeaders;
RequestUrl: Text;
Body: Text;
JsonResponse: JsonObject;
JsonToken: JsonToken;
begin
//Change the URL for the one given to you by your Power Automate Flow
RequestUrl := 'https://prod-44.westus.logic.azure.com:443/workflows/688fd823d7ef472a80adffbbba597a0a/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=QswAv71snh2aGHxnpalHbUBuugvyt8fqC1yYFAxCCbk';
RequestHeaders := Client.DefaultRequestHeaders();
RequestBodyJson.WriteTo(Body);
RequestContent.WriteFrom(Body);
RequestContent.GetHeaders(contentHeaders);
contentHeaders.Clear();
contentHeaders.Add('Content-Type', 'application/json');
Client.Post(RequestURL, RequestContent, ResponseMessage);
if ResponseMessage.IsSuccessStatusCode then begin
ResponseMessage.Content().ReadAs(ResponseText);
JsonResponse.ReadFrom(ResponseText);
exit(JsonResponse);
end;
end;
//Function 3 Builds the Record as a JSON
procedure RecToJson(RecRef: RecordRef) RecJson: JsonObject
var
FieldRef: FieldRef;
Field: Record Field;
pDecimal: Decimal;
pText: Text;
pDate: Date;
pInteger: Integer;
begin
Clear(RecJson);
Field.SetRange(TableNo, RecRef.Number);
if Field.FindSet() then
repeat
FieldRef := RecRef.Field(Field."No.");
if Field.Class = Field.Class::FlowField then
FieldRef.CalcField();
//Obviously incomplete
case Field.Type of
Field.Type::Decimal:
begin
pDecimal := FieldRef.Value;
RecJson.Add(Field.FieldName, pDecimal);
end;
Field.Type::Integer:
begin
pInteger := FieldRef.Value;
RecJson.Add(Field.FieldName, pInteger);
end;
Field.Type::Text, Field.Type::Code:
begin
pText := FieldRef.Value;
RecJson.Add(Field.FieldName, pText);
end;
Field.Type::Date:
begin
pDate := FieldRef.Value;
RecJson.Add(Field.FieldName, pDate);
end;
else begin
begin
RecJson.Add(Field.FieldName, Format(FieldRef.Value));
end;
end;
end;
until Field.Next() = 0;
end;
}
Conclusion
It’s proven one more time that Microsoft is giving us more and more ways of integrating new solutions to offer our customers a better way to work with their systems. Obviously, this example is just a proof of concept to show how we can move part of the decision-making logic into an external software, where functional users can keep working in a low-code environment.
I hope you enjoyed reading the blog. If you found it interesting, it would be nice if you could share it on LinkedIn.
For any questions or suggestions about how to implement this, please reach out to me :) I am more than happy to help you. You can reach me via: gonazalo.rios.ley@dynamicpeople.com or call +31 20 303 24 70.