Business Central Multi Company Approvals ✅❌🔀

I have some things to state upfront as there are things to justify for why the solution is shaped in a certain way. The premise of the post is to solve the fact a user who works across many companies might have many approvals. The standard UX is a big driver for why I’ve chosen the topic. If using out the box BC approvals you can get emails. However, they don’t have any action buttons.

So you’re forced to login. You’d have to do that for all companies you have approvals for. If you’re using Power Automate approvals you do have action buttons in emails. You do have to read every email though to access them.

Is there a better way?! 🤔

I figured the “newish” company hub app would be a good starting point. Have you seen it? A user can view all user tasks they have in each company they have access. Sounds close to what I am thinking 🧐. However, I found the table for this it’s not extendable. The method for how it gathers user tasks seemed fairly involved. I liked the idea of shoehorning it in there but felt that an easier route existed. Access to the detail from the role centre is certainly the way to go though. But access elsewhere is fine too.

Company Hub view users can access

My answer is to use a PowerApp embedded in a Power BI report. Handles plenty of the circumstances required. Here is a version of the app running through some basic functions it could offer to a user:

I have added few other considerations at the end of the post. I will cover off the main steps I took to create the above experience – **which covers approvals controlled by BC Workflows only**. A full explanation of how the entire thing was built would be best by video. In absence of that I will adopt a high level approach for readability, calling out key components.

Components required and where I got some of my details. The order of them is important:

Code for the new api page can be copied from here: https://github.com/JAng13sea/Blogs/blob/master/Multi%20Company%20Approvals/Pag57250.MultiCompApprovalActions.al

Why do we need the custom actions? Doesn’t the BC connector for Power Automate already have those? Yes, they do. However, they are for approvals done via Power Automate only. I am yet to figure out a company based difference in the Dataverse table to bring this type of experience solely for approvals done via Power Automate. **Watch this space**

Using the new API page and the companies endpoint as queries in your PBI report file. Follow Steven Renders blog to ensure the approvalActions is for all companies.
Create a relationship between the two tables with the “Company Name” value being the link
So that a user only sees their approvals add in security so the approverEmail must match up with the user that is logged in and using the report.

Now the Power Apps visual can be added to the report. Use this guide if it is a new experience: https://docs.microsoft.com/en-us/power-apps/maker/canvas-apps/powerapps-custom-visual

A copy of my PowerApp and the power automate flow used can be found here: https://github.com/JAng13sea/Blogs/blob/master/Multi%20Company%20Approvals/MultiCompanyApprovalsApp_20220525155716.zip

One of the big takeaways from the app design is that the user works with a collection instead of the direct data from the PBI report. When the app starts the initial collection is made and that is displayed to the user.
Other components that are crucial are the action buttons to approving, rejecting and substituting. Each button is the same and only the “Type” value is altered on each. Idea being that a new collection for only the entries we need to action are taken and sent to Power Automate. The original collection which is what the user sees is then updated. This is because the PBI report doesn’t get refreshed constantly – it will be on a defined schedule:

Now for more detail on the Power Automate flow. In a standard scenario the connector for BC is not dynamic to a company. In this scenario it has to be. It is a general possibility to pass custom text to connectors in Power Automate and they can be based on variables you setup. The same as setting a header on a HTTP call.

Conclusion: This method certainly falls into the low code bracket but I also think it has caveats which might not work if you have to scale it out to many users. Users do often set aside a time in their working day or week to do approvals so having a one stop shop approach is a bonus. The refreshing of the data is a drawback because of it being held together and formed by the PBI report. On the other hand it it such a small and discreet thing a user has to do so living with some level of drawbacks might suffice because the standard experience is far worse IMO. The time and effort to do approach this in AL is probably worth it in the long run and it’s an obvious thing to have in the “Company Hub” app. The nicest takeaway is the ability to make the BC connector in Power Automate be company dynamic. Have to see what other scenarios crop up where that could be used 😏

I think this should be in the Company Hub app and I have logged it as an idea which will need votes: https://experience.dynamics.com/ideas/idea/?ideaid=658e8d1c-e0dc-ec11-bea1-0003ff45e08b

Business Central Images to a Canvas Power App Gallery

For those who missed it I did a session for BC Tech Talk last year:

During that session I showed how to get a single image for an item record with an API call (35mins and beyond). The method was very simple to create a custom connector for the standard Picture API and just add a extra bit of text. Similar to this: https://api.businesscentral.dynamics.com/v1.0/8d0632b1-47bf-41fe-ba13-a838adf98c48/sandbox/api/v1.0/companies({companyID})/items({itemID})/picture({pictureID})/content

The green text pulls the image directly into an image control in the canvas app 👍. I mention in the video that you could have the picture images in the main gallery view – it will be slower this way but I was asked to at least explain how. The standard API isn’t that helpful here – VERY HAPPY TO BE TOLD HOW TO USE IT CORRECTLY – the endpoint doesn’t interact in the same was as a HTTP request would and it wants to produce a table of data and no matter how I work with the connector with the available formulas I just get errors. Can see from the Power Users forum this is a similar theme with others:

So to get around this a new page is needed with some extra capability. The end goal here is to create a custom connector for use in powerapps that gives the base64 image value for BC item records. I took inspiration for the solution from this blog which I’ve adapted slightly for latest version (at the time of writing being 17.2 – may have changed since): How to get picture from MediaSet through standard APIs. – Dynamics 365 Business Central Community

Final code is on github – Code: https://github.com/JAng13sea/Blogs/tree/master/ItemImages – but in essence you need a new API page for the item table. A field on the API page which is populated by a codeunit return value of text which has been converted into base64:

    procedure "Pic as JSON"(ItemNo: Code[20]) : Text;
    var
        Item: Record Item;
        TenantMedia: Record "Tenant Media";
        PicText: Text;
        PicInstr: InStream;
        JObject: JsonObject;
        JToken: JsonToken;
        TempBlob: Codeunit "Temp Blob";
        PicOStr: OutStream;
        Base64: Codeunit "Base64 Convert";
    begin
        Item.Get(ItemNo);
        If Item.Picture.Count = 0 then
            exit('');
        TenantMedia.Get(Item.Picture.Item(1));
        TenantMedia.CalcFields(Content);
        if TenantMedia.Content.HasValue then begin
            Clear(PicText);
            Clear(PicInstr);
            TenantMedia.Content.CreateInStream(PicInstr);
            PicText := Base64.ToBase64(PicInstr);
            JObject.Add('picture',PicText);
            JObject.SelectToken('picture',JToken);
        end;
        exit(JToken.AsValue().AsText());
    end;

Note that it has been added into a JSON structure to make it easy for other solutions to read it. From here you will need to create a custom connector for Power Platform. I did another video on this so check it out if you need guidance on that part:

Once the connector is in place and you have pulled the data from BC into a collection in PowerApps you can address the image value in the gallery. When using a gallery in PowerApps a predefined method exists for stating each row of the gallery – ThisItem – the value passed from BC is in Base64 but it needs decoding in PowerApps for an image control to display it. In the image control add the text “data:image; application/octet-stream; base64,” & ThisItem.picture

Depending on the volume of items with images this type of call could be very long so tread with caution. If you happen to be using images with other data or storing other blobs this pattern is a way to get the data into PowerApps.

PowerApps biz card reader to Business Central

This one comes with a premium as the PowerApps business card reader isn’t included in the license for BC. However, it’s a really good use of AI and something that could be utilised in the right scenario. Ironic that I’m blogging about something that won’t be getting much use right now. I haven’t acquired a new business card since March 2020. However, it feels as though the exchange of fancy pieces of card might be behind us and scanning something is a cleaner option, in more ways than one 😊

A number of other online resources can explain the PowerApps build up so I will highlight the important parts. The goal here will be to take an image of a business card and have the details from the AI model sent to BC so a company or person contact can be created.

Add the business card reader from the AI builder and then add text input boxes which reference the properties of the business card reader. Use text input boxes as the data from the reader won’t always be perfect so some editing might be needed.

As you can see here the Company Name doesn’t quite work out so some manual adjustment is needed.

The next thing is to have the data passed and to do this I’ve used Power Automate. A button will be created in PowerApps that calls the flow that is needed. A few steps to the flow so I’ll break it into sections. Use a PowerApps trigger as the starting point:

In PowerApps we will build a JSON so add a Parse JSON and request the content from PowerApps. Note that it will most likely change the name as mine is above. A sample schema is needed:

{    “type”: “array”,    “items”: {        “type”: “object”,        “properties”: {            “Address1”: {                “type”: “string”            },            “AddressCity”: {                “type”: “string”            },            “CompanyName”: {                “type”: “string”            },            “Country”: {                “type”: “string”            },            “Email”: {                “type”: “string”            },            “FirstName”: {                “type”: “string”            },            “JobTitle”: {                “type”: “string”            },            “LastName”: {                “type”: “string”            },            “Mobile”: {                “type”: “string”            },            “OfficePhone”: {                “type”: “string”            },            “PostCode”: {                “type”: “string”            },            “Website”: {                “type”: “string”            }        },        “required”: [            “Address1”,            “AddressCity”,            “CompanyName”,            “Country”,            “Email”,            “FirstName”,            “JobTitle”,            “LastName”,            “Mobile”,            “OfficePhone”,            “PostCode”,            “Website”        ]    }}

Next up an “Apply to each” section is required where the body of the JSON is used. In our case there will be one value at a time but a JSON can handle multiples. A series of HTTP triggers will follow along:

  1. A GET to determine if the “Company Name” value can be found in BC already. For this to happen I have published page 5050 as a web service and particular ODATA filtering is needed ?$filter=Type%20eq%20%27Company%27%20and%20Company_Name%20eq%20%27′<CompanyName>’%27
  2. A POST for a Company Contact for the occasions where this detail isn’t available yet. The displayed “Headers” will be needed and the “Body” will be made up of a mix of static values and those from the PowerApps JSON:

3. A final POST to handle the creation of a person contact. This will be repeated so use the “Copy to Clipboard” feature to save time having to type it out again.

A “Condition” has been added off the back of using the GET command and the returned “Body” is checked to see if the Company Name from the PowerApps JSON exists in the JSON of the GET Company HTTP. This will result in the “Yes” command or the “No” commands taking place. Once this is saved then it is ready for hooking up to PowerApps.

Create a new button in PowerApps and use the “Action” part of the ribbon to call on Power Automate. Choose the flow that was devised from the earlier steps. This will add one line to the formula area of PowerApps. Use the shift key and enter to add some additional lines above that inserted line. Some earlier steps are needed before the detail for Power Automate is ready. I’ll break this into sections too:

A Collection will be built up of all the values from the Biz card reader. Refer to the online documentation to understand the PowerApps formulas further:

ClearCollect(BizCardContact,{CompanyName: CompanyName.Text,FirstName:Concatenate(firstname.Text,” “,Lastname.Text),LastName: Lastname.Text,Email:Email.Text,Address1: BusinessCardReader1.AddressStreet,AddressCity: BusinessCardReader1.AddressCity,PostCode: BusinessCardReader1.AddressPostalCode, Country: BusinessCardReader1.AddressCountry,JobTitle: JobTitle.Text,OfficePhone: OfficePhone.Text,Mobile:MobilePhone.Text,Website: Website.Text});

A JSON is required in Power Automate and it can be built in PowerApps using the collection from the formula above:

Set(ContactJSON,JSON(BizCardContact));

Last thing is to pass the JSON to Power Automate:

‘Copyof-BizCardReadertoBC’.Run(ContactJSON)

In full flight you will get one of two results, two contacts or just a person contact. Note the green ticks in the top right of each window to show what has been executed: