Azure, Azure Function Extension, Azure Functions, DotNet, Http Trigger, node.js, npm, nuget package, QR Code, QRCoder, SDK, Tip & Tricks, Visual Studio Code

Creating Azure Functions to generate QR Code

This is second post in this series, if you wish to see previous post, you can access here.

Today in this post I will discuss about first part, how to create Azure Functions.

Before we start, you need some installation to support creating Azure Functions. Use below Links to download & install these packages on the system, where you wish to write, compile & test the Azure functions.

Installation Links:

https://docs.npmjs.com/downloading-and-installing-node-js-and-npm

https://nodejs.org/en/download/

https://dotnet.microsoft.com/download/dotnet/thank-you/sdk-3.1.414-windows-x64-installer

Obviously, you must have an active Azure subscription, to create an Azure Functions App. If you don’t have, you can create an account that gives you a credit to explore Microsoft Azure for 30 days.

Head to Azure portal and sign in with your account.

Create New Function App.

Select your Azure subscription and a resource group or create a new one. Provide a name for your function app. This name will be the prefix for the URL.

You can select the .NET Core runtime and the region that matches your location. Select the Review + create button.

Now, you can create new functions. A function app can contain multiple functions. Each function will be available on:

Before you start to create Function, you need to Install dependencies:

  1. You should install Visual Studio Code
  2. You should also install Node.JS 
  3. Run the following command to install the Core Tools package:

npm install -g azure-functions-core-tools@3 –unsafe-perm true

Create an Azure Functions project

Click the Create New Project… icon in the Azure: Functions panel.

You will be prompted to choose a directory for your app. Choose an empty directory.

You will then be prompted to select a language for your project. Choose dotnet.

Create a function

If you skipped to provide details, for Functions in above process, you can still initiate later.

Click the Create Function… icon in the Azure: Functions panel.

You will be prompted to choose a template for your function. Select HTTP trigger for getting started.

Screens will be same as shown in above process.

Sample project will be created

As per the information provided by you, in above screens.

KSDQRGenerator.cs auto generated file code will be as below:

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Company.Function
{
    public static class KSDQRGenerator
    {
        [FunctionName("KSDQRGenerator")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }
    }
}

Now you have the Http Template, we will make necessary changes as per our requirement.

In this example we will be using a library – QRCoder to generate the QR Code.

So first we have created a function with Http Trigger.

Made changes to the function as:

/*string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);*/

Replace this part as below code:

            string ReqData = req.Query["ReqData"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            ReqData = ReqData ?? data?.url;
            if (string.IsNullOrEmpty(ReqData))
            {
                return new BadRequestResult();
            }

            var generator = ReqData;
            var payload = generator.ToString();

            using (var qrGenerator = new QRCodeGenerator())
            {
                var qrCodeData = qrGenerator.CreateQrCode(payload, QRCodeGenerator.ECCLevel.Q);
                var qrCode = new PngByteQRCode(qrCodeData);
                var qrCodeAsPng = qrCode.GetGraphic(20);
                return new FileContentResult(qrCodeAsPng, "image/png");
            }

Final Code should look like this:

There are some errors, correct. Next step will resolve the same.

Next, we will add the QRCode nuget package to the dotnet core project using 

dotnet add package QRCoder –version 1.4.1

Post completing the command execution, all errors are resolved.

Run your function project locally

Press F5 to run your function app.

The runtime will output a URL for any HTTP functions, which can be copied and run in your browser’s address bar.

Copy the URL of the function & Switch to browser:

In my case: http://localhost:7071/api/KSDQRGenerator?ReqData=Test

If you scan above QRCode, you will get ‘Test’ as we have passed this as a parameter (?ReqData=Test)

To stop debugging, press Shift + F5.

Deploy your code to Azure

Click the Deploy to Function App… () icon in the Azure: Functions panel.

Sign in to your Azure Account, if not done earlier. To test locally you need not to sign in to Azure. But at deployment is must.

When prompted to select a function app, choose KSDQRCodeGenerator (in my case), Function App Created on Azure portal in the starting of this blog post.

Confirm to deployment dialog.

On completion you will get the Live AZURE URL, use the URL in same way as for local, to pass parameter append at the end of URL as (?ReqData=Test)

Replace [Test] with the information for which you want to generate the QR Code.

That’s all for this post, but visit again to learn from upcoming posts in this series, will post soon.

See you again in next post, Till then keep exploring, learning and sharing with others.

Advertisement
Azure, Azure Functions, Business Central, QR Code, Quick Response Code, Tip & Tricks

QR Code Generator using Azure Functions & Integration with Business Central Cloud Version

Today in this post I will explain how and why you should consider create your own QR Code programmatically internally without the use of public available options.

Quick Response code, is a code that can be easily read by a device having camera attached. QR Codes are commonly used today across various industries.

In todays word hardly you will find any product which don’t have either Barcode or QR Code.

You may find many free QR Code generators available on the internet. It is very convenient to source and quickly generate QR Codes but using their services have areas of risks and concerns for organization to consider.

The potential security risks such as phishing, malware, Malicious website and data tracking few to list.

Solution to this could be organizations should prefer to find an enterprise paid service or to create their own. If any organization decides to build their own QR Code generator, then there are open-source QR Code libraries where they can rely on.

Now to build a full-fledged application will require to consider for all the typical application infrastructure considerations such as networking, security, monitoring, patching and the list keeps growing. There could be many considerations just to provide a simple service and is it worth? Another big fat questions.

Here comes the Azure Functions.

Microsoft Azure is a cloud computing service that is used for building, testing, deploying, and managing applications.

It provides

  • software as a service (SaaS),
  • platform as a service (PaaS), and
  • infrastructure as a service (IaaS).

Azure Functions runs on the Microsoft Azure platform and is used to run small pieces of code, or functions, in the cloud.

With Azure Functions, you only need to focus on a specific function without worrying on the whole application or the infrastructure to run it. Additionally, Azure Functions lets you run your code in a serverless environment without creating a virtual machine or publish any web application.

For Azure functions, you can use development language of your choice, such as C#, F#, Node.js, or PHP.

Because Microsoft Azure is a service, no doubt you have to pay for it. Azure Functions has two types of pricing plans:

Consumption plan – Microsoft Azure provides all the necessary computational resources. Therefore, you don’t need to concentrate on resource management, and you only pay for the time that your code runs.

App service plan – With the app service plan, you can run your functions as you do other web and API applications on Azure. If you are already using app services for other applications, you can run Azure Functions on the same plan without additional costs.

Check for latest Plan online on Azure portal as and when you decide to use the service, or to grab a free trial account. You can list free services available to you if you have azure account using this link here.

Azure Functions Pricing you can check here and free grant per month.

Above links and further links from there will provide detailed information on everything you may require to go with Azure Functions.

Enough for introduction, what we are going to Explore in this series:

  1. Creating Azure Functions to generate QR Code & Sample Test of Azure Function
  2. Integrating Azure Function with Business Central
  3. Integrating with Business Central – using Web Service
  4. Testing of Extension functionality for QR Code Generation.

Anything else I decide to add more will update this post with links and other topics related to this series.

Make a book mark to this post, I will update the Links in above list as I complete my next post, and add more topics to the list, I further decide to include in this series.

Go through the above links to educate yourself, so that what I explain in upcoming posts, you better co-relate them.

That’s all for this post, but visit again to learn from upcoming posts in this series, will post soon.

See you again in next post, Till then keep exploring, learning and sharing with others.

Business Central, Dataverse, Dynamics 365 Sales, Integration, Synchronize, Testing, Tip & Tricks

Test the Solution Integration of Business Central with Dataverse

This is the Twelfth post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-Testing:

As a Eleventh Step we will Verify & Test the Integration between Business Central and Dataverse for Custom table we created in both the environments.

Initial Stage post Deploying the Extension, we access the Pages for first time.

We have below data in Dataverse Prospects table:

In BC when we access to CDS Prospects Page:

In BC when we access to Prospects Page:

First we will Add one Record in Dataverse:

Now if we check CDS Prospects Page:

If you remember on CDS Prospects Page we have written code on OnInit trigger to run codeunit – CRM Integration Management, so when page is opened, the data is synchronized to CDS Prospects Page.

On CDS Prospects Page, we have also added action Create in Business Central, if we choose this action:

The record in Dataverse will get transferred to Prospect Table in Business Central

Now Let us Add one Record in Business Central, from Prospect Page.

Business logic for calculating Forcast Revenue is not written in BC, but we have in Dataverse table.

When we synchronize data from BC to Dataverse, that will get calculated automatically.

Select the record we just added in BC. Action-> Dataverse-> Coupling-> Setup Coupling

On the new page that opens, Select Create New, And click OK.

Since we have selected Synchronize After Coupling and direction BC to Dataverse, the record will get transferred to Dataverse Table.

Also you can see the other fields too got calculated. Now if we Synchronize back to BC these fields will get updated in BC.

You can see the Synchronization is happening in both directions.

You can make necessary changes to code in sample walkthrough to achieve your desired results.

Hope you enjoyed learning this trick, and will use in your projects, the skill learned from this series of posts.

I will come up with more similar stuffs. Till then keep exploring, learning and sharing with others.

Take care of yourself and your loved ones. Put your mask, maintain safe distance and don’t forget to get vaccinated.

Business Central, Business Central Dataverse Integration, Business Central Integration Business Central to Common Data Service, Dataverse, Dynamics 365 Sales, Integration, Organization, Permission, Power Apps, Roles, Security, Synchronize

Check & Assign Permissions in Power Apps Environment

This is the Eleventh post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-10:

As a Tenth Step we will Verify & Assign Permissions in Power Apps Environment.

This is the final Step, and we will be done with the goal we set in our first post of this series.

Open your Dynamics 365 Sales Environment and Go To Settings.

Under Users + permissions -> Application users.

On next screen Verify “Business Central Integration Business Central to Common Data Service” is selected. If not then select it from “Edit Security Roles”.

From Settings -> Users + permissions -> Security roles

From Security roles select the Business Central Dataverse Integration Then Edit

Select Custom Entities tab and find your table Prospects. Select each circle 4 times to assign Organization, circle should turn completely filled green color.

Select Save and Close.

Now you All done with the steps, Ready to test the solution.

In next post we will test the solution, we created in past 10 posts.

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

Business Central, Dataverse, Dataverse Connection Setup, Dynamics 365 Connection Setup, Fields, Integration, Mappings, Setup, Synchronize

Updating the default Mappings

This is the Tenth post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-9:

As a Nineth Step we will Update the default Mappings.

Open the Dataverse Connection Setup using TellMe.

From Actions -> Use Default Synchronization Setup.

Respond Yes to the Confirmation Message.

You will get the confirmation as completed, say OK to continue.

Next open the Microsoft Dynamics 365 Connection Setup from TellMe.

Go To Mapping -> Integration Mappings.

Now you can find your Custom table Prospects.

Go To Fields, you will see Field Mappings for you table as defined in Integration Codeunit in previous step.

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

Assign, Business Central, Deploy, Extension, Package, Permission, XML

Package & Deploy the Extension

This is the Nineth post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-8:

As a Eighth Step we will Package & Deploy the Extension to Business Central

Now it’s time to Build and Deploy the Extension, but before that one more step required for creating Permission Set.

I have made some modification to the file generated in above process. Below is the complete file.

Sample Permission Set

<?xml version="1.0" encoding="utf-8"?>
<PermissionSets>
  <PermissionSet RoleID="BC_DV_DEMO" RoleName="BC_DV_Demo">
    <Permission>
      <ObjectID>50120</ObjectID>
      <ObjectType>0</ObjectType>
      <ReadPermission>1</ReadPermission>
      <InsertPermission>1</InsertPermission>
      <ModifyPermission>1</ModifyPermission>
      <DeletePermission>1</DeletePermission>
      <ExecutePermission>0</ExecutePermission>
      <SecurityFilter />
    </Permission>
    <Permission>
      <ObjectID>50120</ObjectID>
      <ObjectType>1</ObjectType>
      <ReadPermission>0</ReadPermission>
      <InsertPermission>0</InsertPermission>
      <ModifyPermission>0</ModifyPermission>
      <DeletePermission>0</DeletePermission>
      <ExecutePermission>1</ExecutePermission>
      <SecurityFilter />
    </Permission>
    <Permission>
      <ObjectID>50126</ObjectID>
      <ObjectType>0</ObjectType>
      <ReadPermission>1</ReadPermission>
      <InsertPermission>1</InsertPermission>
      <ModifyPermission>1</ModifyPermission>
      <DeletePermission>1</DeletePermission>
      <ExecutePermission>0</ExecutePermission>
      <SecurityFilter />
    </Permission>
    <Permission>
      <ObjectID>50126</ObjectID>
      <ObjectType>1</ObjectType>
      <ReadPermission>0</ReadPermission>
      <InsertPermission>0</InsertPermission>
      <ModifyPermission>0</ModifyPermission>
      <DeletePermission>0</DeletePermission>
      <ExecutePermission>1</ExecutePermission>
      <SecurityFilter />
    </Permission>
    <Permission>
      <ObjectID>50121</ObjectID>
      <ObjectType>8</ObjectType>
      <ReadPermission>0</ReadPermission>
      <InsertPermission>0</InsertPermission>
      <ModifyPermission>0</ModifyPermission>
      <DeletePermission>0</DeletePermission>
      <ExecutePermission>1</ExecutePermission>
      <SecurityFilter />
    </Permission>
    <Permission>
      <ObjectID>50120</ObjectID>
      <ObjectType>8</ObjectType>
      <ReadPermission>0</ReadPermission>
      <InsertPermission>0</InsertPermission>
      <ModifyPermission>0</ModifyPermission>
      <DeletePermission>0</DeletePermission>
      <ExecutePermission>1</ExecutePermission>
      <SecurityFilter />
    </Permission>
    <Permission>
      <ObjectID>50122</ObjectID>
      <ObjectType>8</ObjectType>
      <ReadPermission>0</ReadPermission>
      <InsertPermission>0</InsertPermission>
      <ModifyPermission>0</ModifyPermission>
      <DeletePermission>0</DeletePermission>
      <ExecutePermission>1</ExecutePermission>
      <SecurityFilter />
    </Permission>
    <Permission>
      <ObjectID>50120</ObjectID>
      <ObjectType>5</ObjectType>
      <ReadPermission>0</ReadPermission>
      <InsertPermission>0</InsertPermission>
      <ModifyPermission>0</ModifyPermission>
      <DeletePermission>0</DeletePermission>
      <ExecutePermission>1</ExecutePermission>
      <SecurityFilter />
    </Permission>
  </PermissionSet>
</PermissionSets>

Build the Package & Deploy.

Permission Set will also get deployed along with Extension.

You can Assign this Permission Set to respective Users.

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

Action, AL, Business Central, Coupling, Dataverse, Dynamics 365 Sales, Extension, Integration, Page, Procedure, Synchronize, Triggers, Varables, VS Code

Create Actions on the Page for managing Coupling and Synchronization

This is the Eighth post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-7:

As a Seventh Step we will Create Actions on the Page for managing Coupling and Synchronization created in previous post.

On page 50120 Prospects created in previous post

Add Variables:

var
   CRMIntegrationManagement: Codeunit "CRM Integration Management";
   CRMCouplingManagement: Codeunit "CRM Coupling Management";
   CDSIntegrationEnabled: Boolean;
   CDSIsCoupledToRecord: Boolean;

Add Triggers:

trigger OnOpenPage()
begin
    CDSIntegrationEnabled := CRMIntegrationManagement.IsCDSIntegrationEnabled();
end;

trigger OnAfterGetCurrRecord()
begin
    if CDSIntegrationEnabled then
        CDSIsCoupledToRecord := CRMCouplingManagement.IsRecordCoupledToCRM(Rec.RecordId);
end;

Add Actions:

    Actions
    {
        area(Processing)
        {
            group(ActionGroupCDS)
            {
                Caption = 'Dataverse';
                Visible = CDSIntegrationEnabled;

                action(CDSGotoProspect)
                {
                    Caption = 'Prospect';
                    Image = CoupledCustomer;
                    ToolTip = 'Open the coupled Dataverse Prospect.';
                    ApplicationArea = All;

                    trigger OnAction()
                    var
                        CRMIntegrationManagement: Codeunit "CRM Integration Management";
                    begin
                        CRMIntegrationManagement.ShowCRMEntityFromRecordID(Rec.RecordId);
                    end;
                }
                action(CDSSynchronizeNow)
                {
                    Caption = 'Synchronize';
                    ApplicationArea = All;
                    Visible = true;
                    Image = Refresh;
                    Enabled = CDSIsCoupledToRecord;
                    ToolTip = 'Send or get updated data to or from Microsoft Dataverse.';

                    trigger OnAction()
                    var
                        CRMIntegrationManagement: Codeunit "CRM Integration Management";
                    begin
                        CRMIntegrationManagement.UpdateOneNow(Rec.RecordId);
                    end;
                }
                action(ShowLog)
                {
                    Caption = 'Synchronization Log';
                    ApplicationArea = All;
                    Visible = true;
                    Image = Log;
                    ToolTip = 'View integration synchronization jobs for the Prospect table.';

                    trigger OnAction()
                    var
                        CRMIntegrationManagement: Codeunit "CRM Integration Management";
                    begin
                        CRMIntegrationManagement.ShowLog(Rec.RecordId);
                    end;
                }
                group(Coupling)
                {
                    Caption = 'Coupling';
                    Image = LinkAccount;
                    ToolTip = 'Create, change, or delete a coupling between the Business Central record and a Microsoft Dataverse row.';

                    action(ManageCDSCoupling)
                    {
                        Caption = 'Set Up Coupling';
                        ApplicationArea = All;
                        Visible = true;
                        Image = LinkAccount;
                        ToolTip = 'Create or modify the coupling to a Microsoft Dataverse Prospect.';

                        trigger OnAction()
                        var
                            CRMIntegrationManagement: Codeunit "CRM Integration Management";
                        begin
                            CRMIntegrationManagement.DefineCoupling(Rec.RecordId);
                        end;
                    }
                    action(DeleteCDSCoupling)
                    {
                        Caption = 'Delete Coupling';
                        ApplicationArea = All;
                        Visible = true;
                        Image = UnLinkAccount;
                        Enabled = CDSIsCoupledToRecord;
                        ToolTip = 'Delete the coupling to a Microsoft Dataverse Prospect.';

                        trigger OnAction()
                        var
                            CRMCouplingManagement: Codeunit "CRM Coupling Management";
                        begin
                            CRMCouplingManagement.RemoveCoupling(Rec.RecordId);
                        end;
                    }
                }
            }
        }
    }

On page 50122 “CDS Prospect List” created above

Add Variables:

var
    CurrentlyCoupledCDSProspect: Record "CDS cr95d_Prospects";

Add Trigger:

trigger OnInit()
begin
    Codeunit.Run(Codeunit::"CRM Integration Management");
end;

Add Procedure:

procedure SetCurrentlyCoupledCDSProspect(CDSProspect: Record "CDS cr95d_Prospects")
begin
    CurrentlyCoupledCDSProspect := CDSProspect;
end;

Add Action:

Actions
    {
        area(processing)
        {
            action(CreateFromCDS)
            {
                ApplicationArea = All;
                Caption = 'Create in Business Central';
                Promoted = true;
                PromotedCategory = Process;
                ToolTip = 'Generate the table from the coupled Microsoft Dataverse Prospect.';

                trigger OnAction()
                var
                    CDSProspect: Record "CDS cr95d_Prospects";
                    CRMIntegrationManagement: Codeunit "CRM Integration Management";
                begin
                    CurrPage.SetSelectionFilter(CDSProspect);
                    CRMIntegrationManagement.CreateNewRecordsFromCRM(CDSProspect);
                end;
            }
        }
    }

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

AL, Business Central, Codeunit, Dataverse, Events, Extension, Integration, Procedures, Subscription, VS Code

Create Integration Codeunit for Business Central & Dataverse Integration

This is the Seventh post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-6:

As a Sixth Step we will Create Integration Codeunit in Business Central

codeunit 50120 CDSDataverseEvent
{
}

Add these Procedures

local procedure LookupCDSProspect(SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text): Boolean
    var
        CDSProspect: Record "CDS cr95d_Prospects";
        OriginalCDSProspect: Record "CDS cr95d_Prospects";
        OriginalCDSProspectList: Page "CDS Prospect List";
    begin
        if not IsNullGuid(CRMId) then begin
            if CDSProspect.Get(CRMId) then
                OriginalCDSProspectList.SetRecord(CDSProspect);
            if not IsNullGuid(SavedCRMId) then
                if OriginalCDSProspect.Get(SavedCRMId) then
                    OriginalCDSProspectList.SetCurrentlyCoupledCDSProspect(OriginalCDSProspect);
        end;

        CDSProspect.SetView(IntTableFilter);
        OriginalCDSProspectList.SetTableView(CDSProspect);
        OriginalCDSProspectList.LookupMode(true);
        if OriginalCDSProspectList.RunModal = ACTION::LookupOK then begin
            OriginalCDSProspectList.GetRecord(CDSProspect);
            CRMId := CDSProspect.cr95d_ProspectsId;
            exit(true);
        end;
        exit(false);
    end;

local procedure AddEntityTableMapping(CRMEntityTypeName: Text; TableID: Integer; var TempNameValueBuffer: Record "Name/Value Buffer" temporary)
    begin
        TempNameValueBuffer.Init();
        TempNameValueBuffer.ID := TempNameValueBuffer.Count + 1;
        TempNameValueBuffer.Name := CopyStr(CRMEntityTypeName, 1, MaxStrLen(TempNameValueBuffer.Name));
        TempNameValueBuffer.Value := Format(TableID);
        TempNameValueBuffer.Insert();
    end;

local procedure InsertIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; MappingName: Code[20]; TableNo: Integer; IntegrationTableNo: Integer; IntegrationTableUIDFieldNo: Integer; IntegrationTableModifiedFieldNo: Integer; TableConfigTemplateCode: Code[10]; IntegrationTableConfigTemplateCode: Code[10]; SynchOnlyCoupledRecords: Boolean)
    begin
        IntegrationTableMapping.CreateRecord(MappingName, TableNo, IntegrationTableNo, IntegrationTableUIDFieldNo, IntegrationTableModifiedFieldNo, TableConfigTemplateCode, IntegrationTableConfigTemplateCode, SynchOnlyCoupledRecords, IntegrationTableMapping.Direction::Bidirectional, 'CDS');
    end;

procedure InsertIntegrationFieldMapping(IntegrationTableMappingName: Code[20]; TableFieldNo: Integer; IntegrationTableFieldNo: Integer; SynchDirection: Option; ConstValue: Text; ValidateField: Boolean; ValidateIntegrationTableField: Boolean)
    var
        IntegrationFieldMapping: Record "Integration Field Mapping";
    begin
        IntegrationFieldMapping.CreateRecord(IntegrationTableMappingName, TableFieldNo, IntegrationTableFieldNo, SynchDirection,
            ConstValue, ValidateField, ValidateIntegrationTableField);
    end;

To Add Event Subscriptions, Use the new Shift+Alt+E shortcut in the AL code editor to invoke a list of all events.

Search for Event OnGetCDSTableNo“CRM Setup Defaults”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnGetCDSTableNo', '', false, false)]

local procedure OnGetCDSTableNo(BCTableNo: Integer; var CDSTableNo: Integer; var handled: Boolean);
    begin
        if BCTableNo = DATABASE::"Prospect" then begin
            CDSTableNo := DATABASE::"CDS cr95d_Prospects";
            handled := true;
        end;
    end;

Search for Event OnLookupCRMTables“Lookup CRM Tables”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Lookup CRM Tables", 'OnLookupCRMTables', '', false, false)]

local procedure OnLookupCRMTables(CRMTableID: Integer; NAVTableId: Integer; SavedCRMId: Guid; var CRMId: Guid; IntTableFilter: Text; var Handled: Boolean);
    begin
        if CRMTableID = Database::"CDS cr95d_Prospects" then
            Handled := LookupCDSProspect(SavedCRMId, CRMId, IntTableFilter);
    end;

Search for Event OnAddEntityTableMapping“CRM Setup Defaults”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"CRM Setup Defaults", 'OnAddEntityTableMapping', '', false, false)]

local procedure OnAddEntityTableMapping(var TempNameValueBuffer: Record "Name/Value Buffer");
    begin
        AddEntityTableMapping('Prospect', DATABASE::"CDS cr95d_Prospects", TempNameValueBuffer);
    end;

Search for Event OnAfterResetConfiguration “CDS Setup Defaults”

[EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Setup Defaults", 'OnAfterResetConfiguration', '', false, false)]

local procedure OnAfterResetConfiguration(CDSConnectionSetup: Record "CDS Connection Setup");
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        IntegrationFieldMapping: Record "Integration Field Mapping";
        CDSProspect: Record "CDS cr95d_Prospects";
        Prospect: Record "Prospect";
    begin
        InsertIntegrationTableMapping(
            IntegrationTableMapping, 'Prospect',
            DATABASE::"Prospect", DATABASE::"CDS cr95d_Prospects", CDSProspect.FieldNo(cr95d_ProspectsId), CDSProspect.FieldNo(ModifiedOn), '', '', true);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("No."), CDSProspect.FieldNo(cr95d_ProspectsId), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo(Name), CDSProspect.FieldNo(cr95d_ProspectName), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo(Probability), CDSProspect.FieldNo(cr95d_Probability), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Contract Amount"), CDSProspect.FieldNo(cr95d_ContractAmount), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Contract Amount (Base)"), CDSProspect.FieldNo(cr95d_contractamount_Base), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo(Stage), CDSProspect.FieldNo(cr95d_Stage), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Forecast Revenue"), CDSProspect.FieldNo(cr95d_ForcastedRevenue), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);

        InsertIntegrationFieldMapping('Prospect', Prospect.FieldNo("Forecast Revenue (Base)"), CDSProspect.FieldNo(cr95d_forcastedrevenue_Base), IntegrationFieldMapping.Direction::Bidirectional, '', true, false);
    end;

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

AL, AL Table Proxy Generator Tool, altpgen, Business Central, Dataverse, Extension, Generator, Integration, Page, Proxy, Table, Tool

Using AL Table Proxy Generator Tool to create Integration Table(s) in Business Central for the Dataverse table

This is the Sixth post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-5:

As a fifth Step we will Create Integration Table(s) in Business Central for the Dataverse table created in Step-4

When table(s) are present in Microsoft Dataverse, but not in Business Central, this tool can be run to generate integration or proxy table(s) for the specified table(s).

The AL Table Proxy Generator tool you can find in AL Language extension by name altpgen.exe in the equivalent folder [C:\Users\ashwi\.vscode\extensions\ms-dynamics-smb.al-7.4.502459\bin\]

Run PowerShell ISE as Administrator

Change to folder containing altpgen.exe

Above command should be single line, I have break for clarity of parameters. Below is the sample command.

.\altpgen -project:"C:\Userdata\AL Project\DataVerseIntegration" -packagecachepath:"C:\Userdata\AL Project\DataVerseIntegration\packagecachepath" -serviceuri:"https://xxxxxxxxxxx.crm8.dynamics.com/" -entities:cr95d_prospects -baseid:50125 -tabletype:CDS

When you run the command, it will ask for authentication, provide and continue.

Once you Accept the Permissions request, you can see the output of the command.

Don’t worry for warnings, Your AL file will be generated in specified folder.

Below is the sample of generated file, it is suggested to generate this file using the tool, don’t try to create manually.

table 50126 "CDS cr95d_Prospects"
{
  ExternalName = 'cr95d_prospects';
  TableType = CDS;
  Description = '';

  fields
  {
    field(1;cr95d_ProspectsId;GUID)
    {
      ExternalName = 'cr95d_prospectsid';
      ExternalType = 'Uniqueidentifier';
      ExternalAccess = Insert;
      Description = 'Unique identifier for entity instances';
      Caption = 'Prospects';
    }
    field(2;CreatedOn;Datetime)
    {
      ExternalName = 'createdon';
      ExternalType = 'DateTime';
      ExternalAccess = Read;
      Description = 'Date and time when the record was created.';
      Caption = 'Created On';
    }
    field(4;ModifiedOn;Datetime)
    {
      ExternalName = 'modifiedon';
      ExternalType = 'DateTime';
      ExternalAccess = Read;
      Description = 'Date and time when the record was modified.';
      Caption = 'Modified On';
    }
    field(24;statecode;Option)
    {
      ExternalName = 'statecode';
      ExternalType = 'State';
      ExternalAccess = Modify;
      Description = 'Status of the Prospects';
      Caption = 'Status';
      InitValue = " ";
      OptionMembers = " ", Active, Inactive;
      OptionOrdinalValues = -1, 0, 1;
    }
    field(26;statuscode;Option)
    {
      ExternalName = 'statuscode';
      ExternalType = 'Status';
      Description = 'Reason for the status of the Prospects';
      Caption = 'Status Reason';
      InitValue = " ";
      OptionMembers = " ", Active, Inactive;
      OptionOrdinalValues = -1, 1, 2;
    }
    field(28;VersionNumber;BigInteger)
    {
      ExternalName = 'versionnumber';
      ExternalType = 'BigInt';
      ExternalAccess = Read;
      Description = 'Version Number';
      Caption = 'Version Number';
    }
    field(29;ImportSequenceNumber;Integer)
    {
      ExternalName = 'importsequencenumber';
      ExternalType = 'Integer';
      ExternalAccess = Insert;
      Description = 'Sequence number of the import that created this record.';
      Caption = 'Import Sequence Number';
    }
    field(30;OverriddenCreatedOn;Date)
    {
      ExternalName = 'overriddencreatedon';
      ExternalType = 'DateTime';
      ExternalAccess = Insert;
      Description = 'Date and time that the record was migrated.';
      Caption = 'Record Created On';
    }
    field(31;TimeZoneRuleVersionNumber;Integer)
    {
      ExternalName = 'timezoneruleversionnumber';
      ExternalType = 'Integer';
      Description = 'For internal use only.';
      Caption = 'Time Zone Rule Version Number';
    }
    field(32;UTCConversionTimeZoneCode;Integer)
    {
      ExternalName = 'utcconversiontimezonecode';
      ExternalType = 'Integer';
      Description = 'Time zone code that was in use when the record was created.';
      Caption = 'UTC Conversion Time Zone Code';
    }
    field(33;cr95d_ProspectName;Text[100])
    {
      ExternalName = 'cr95d_prospectname';
      ExternalType = 'String';
      Description = 'Required name field';
      Caption = 'Prospect Name';
    }
    field(34;cr95d_ContractAmount;Decimal)
    {
      ExternalName = 'cr95d_contractamount';
      ExternalType = 'Money';
      Description = '';
      Caption = 'Contract Amount';
    }
    field(37;ExchangeRate;Decimal)
    {
      ExternalName = 'exchangerate';
      ExternalType = 'Decimal';
      ExternalAccess = Read;
      Description = 'Exchange rate for the currency associated with the entity with respect to the base currency.';
      Caption = 'Exchange Rate';
    }
    field(38;cr95d_contractamount_Base;Decimal)
    {
      ExternalName = 'cr95d_contractamount_base';
      ExternalType = 'Money';
      ExternalAccess = Read;
      Description = 'Value of the Contract Amount in base currency.';
      Caption = 'Contract Amount (Base)';
    }
    field(39;cr95d_Probability;Integer)
    {
      ExternalName = 'cr95d_probability';
      ExternalType = 'Integer';
      Description = '';
      Caption = 'Probability';
    }
    field(40;cr95d_Stage;Option)
    {
      ExternalName = 'cr95d_stage';
      ExternalType = 'Picklist';
      Description = '';
      Caption = 'Stage';
      InitValue = Lead;
      OptionMembers = Lead, Opportunity, Won, Lost;
      OptionOrdinalValues = 256080000, 256080001, 256080002, 256080003;
    }
    field(42;cr95d_ForcastedRevenue;Decimal)
    {
      ExternalName = 'cr95d_forcastedrevenue';
      ExternalType = 'Money';
      Description = '';
      Caption = 'Forcasted Revenue';
    }
    field(43;cr95d_forcastedrevenue_Base;Decimal)
    {
      ExternalName = 'cr95d_forcastedrevenue_base';
      ExternalType = 'Money';
      ExternalAccess = Read;
      Description = 'Value of the Forcasted Revenue in base currency.';
      Caption = 'Forcasted Revenue (Base)';
    }
  }
  keys
  {
    key(PK;cr95d_ProspectsId)
    {
      Clustered = true;
    }
    key(Name;cr95d_ProspectName)
    {
    }
  }
  fieldgroups
  {
    fieldgroup(DropDown;cr95d_ProspectName)
    {
    }
  }
}

Create a Page for above table

page 50122 "CDS Prospect List"
{
    Caption = 'CDS Prospect List';
    PageType = List;
    SourceTable = "CDS cr95d_Prospects";
    Editable = false;
    ApplicationArea = All;
    UsageCategory = Lists;
    RefreshOnActivate = true;

    layout
    {
        area(Content)
        {
            repeater(Group)
            {
                field("No."; Rec.cr95d_ProspectsId)
                {
                    Caption = 'No.';
                    ApplicationArea = All;
                }
                field(Name; Rec.cr95d_ProspectName)
                {
                    Caption = 'Name';
                    ApplicationArea = All;
                }
                field(Stage; Rec.cr95d_Stage)
                {
                    Caption = 'Stage';
                    ApplicationArea = All;
                }
                field("Probability"; Rec.cr95d_Probability)
                {
                    Caption = 'Probability';
                    ApplicationArea = All;
                }
                field("Contract Amount"; Rec.cr95d_ContractAmount)
                {
                    Caption = 'Contract Amount';
                    ApplicationArea = All;
                }
                field("Contract Amount (Base)"; Rec.cr95d_contractamount_Base)
                {
                    Caption = 'Contract Amount (Base)';
                    ApplicationArea = All;
                }
                field("Forecast Revenue"; Rec.cr95d_ForcastedRevenue)
                {
                    Caption = 'Forecast Revenue';
                    ApplicationArea = All;
                }
                field("Forecast Revenue (Base)"; Rec.cr95d_forcastedrevenue_Base)
                {
                    Caption = 'Forecast Revenue (Base)';
                    ApplicationArea = All;
                }
                field("Exchange Rate"; Rec.ExchangeRate)
                {
                    Caption = 'Exchange Rate';
                    ApplicationArea = All;
                }
            }
        }
    }
}

You can read more about AL Table Proxy Generator Tool from Microsoft docs

AL Table Proxy Generator

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

Business Central, Dataverse, Power Apps, Synchronize, Table

Create Custom table in Dataverse/ Power Apps

This is the Fifth post in the series. If you want to go to previous post click here.

From the series of steps this post is dedicated to Step-4:

As a fourth Step we will Create New Table(s) in Dataverse, below is the structure of the table.

This table will be used to Sync Data between Business Central and Dataverse.

Define all required columns. Same as we have done in Business Central, in previous post.

Next we will define Business Rule, This will make Probability field as mandatory.

See the properties of each component.

Sample data for the table.

If you are not comfortable with creating table in Power Apps environment, I will add one post step wise step how this table was created and data was uploaded in this table. Once this series is completed, will provide link to that post here.

Now you are good to proceed with Next Step.

You can jump to Next Step from here.