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, 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.

AL, Business Central, Dataverse, Enum, Extension, Integration, Page, Table

Create Custom table & page in Business Central

This is the Fourth 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-3:

As a third Step we will Create New Table(s) and Page(s) in Business Central

Below are the AL code for Prospect table, Prospect card page & Prospect list page that will be created in Business Central via Extension, used for Integration with Dataverse.

Table in BC to Integrate from Dataverse

table 50120 "Prospect"
{
    DataClassification = ToBeClassified;
    DrillDownPageID = "Prospects";
    fields
    {
        field(1; "No."; Code[20])
        {
            DataClassification = ToBeClassified;
        }
        field(2; "Name"; Text[50])
        {
            DataClassification = ToBeClassified;
        }
        field(5; "Probability"; Integer)
        {
            DataClassification = ToBeClassified;
        }
        field(6; "Contract Amount"; Decimal)
        {
            DataClassification = ToBeClassified;
        }
        field(7; "Contract Amount (Base)"; Decimal)
        {
            DataClassification = ToBeClassified;
        }
        field(8; "Stage"; Enum "Stage Type")
        {
            DataClassification = ToBeClassified;
        }
        field(11; "Forecast Revenue"; Decimal)
        {
            DataClassification = ToBeClassified;
        }
        field(12; "Forecast Revenue (Base)"; Decimal)
        {
            DataClassification = ToBeClassified;
        }
    }

    keys
    {
        key(key1; Name)
        {
            Clustered = true;
        }
    }
}

Card Page for above Table

page 50121 "Prospect Card"
{
    Caption = 'Prospect Card';
    PageType = Card;
    UsageCategory = Administration;
    SourceTable = "Prospect";

    layout
    {
        area(Content)
        {
            group(General)
            {
                field("Name"; Rec."Name")
                {
                    ApplicationArea = All;
                }
                field(Probability; Rec.Probability)
                {
                    ApplicationArea = All;
                }
                field(Stage; Rec.Stage)
                {
                    ApplicationArea = All;
                }
            }

            group(Details)
            {
                field("Contract Amount"; Rec."Contract Amount")
                {
                    ApplicationArea = All;
                }
                field("Contract Amount (Base)"; Rec."Contract Amount (Base)")
                {
                    ApplicationArea = All;
                }
                field("Forecast Revenue"; Rec."Forecast Revenue")
                {
                    ApplicationArea = All;
                }
                field("Forcast Revenu (Base)"; Rec."Forecast Revenue (Base)")
                {
                    ApplicationArea = All;
                }
            }
        }
    }
}

List Page for above Table

page 50120 Prospects
{
    PageType = List;
    ApplicationArea = All;
    UsageCategory = Lists;
    SourceTable = Prospect;
    Caption = 'Prospect List';
    CardPageId = "Prospect Card";
    Editable = false;

    layout
    {
        area(Content)
        {
            repeater(GroupName)
            {
                field(Name; Rec.Name)
                {
                    ApplicationArea = All;

                }
                field(Probability; Rec.Probability)
                {
                    ApplicationArea = All;

                }
                field("Contract Amount"; Rec."Contract Amount")
                {
                    ApplicationArea = All;

                }
                field("Contract Amount (Base)"; Rec."Contract Amount (Base)")
                {
                    ApplicationArea = All;

                }
                field(Stage; Rec.Stage)
                {
                    ApplicationArea = All;

                }
                field("Forcast Revenue"; Rec."Forecast Revenue")
                {
                    ApplicationArea = All;

                }
                field("Forcast Revenue (Base)"; Rec."Forecast Revenue (Base)")
                {
                    ApplicationArea = All;

                }
            }
        }
    }
}

Enum for field Stage

enum 50120 "Stage Type"
{
    Extensible = true;
    AssignmentCompatibility = true;

    value(0; "Lead") { Caption = 'Lead'; }
    value(1; "Opportunity") { Caption = 'Opportunity'; }
    value(2; "Won") { Caption = 'Won'; }
    value(3; "Lost") { Caption = 'Lost'; }
}

Now you are good to proceed with Next Step.

You can jump to Next Step from here.

AL, BC18, Business Central, Code, Codeunit, Combinations, Development Tips, Dimension, Dynamics 365, Environment, Events, Extension, Extension Package, How To, Online, Page, Sandbox, Subscription, Table, Tip & Tricks, VS Code

Walkthrough Extension Development in Online Sandbox – Business Central

I have my Sandbox environment as below:

Details of the Sandbox as below:

Connecting VS code to above environment.

When you try to Publish the extension, it will ask you to authenticate.

Copy the Link and open in the browser and paste the code in the box as shown below.

Next it will ask for your Online Instance User Id & Password provide it and on confirmation close the page. It will start deploying the extension.

In this walkthrough I am using below scenario:

Requirement is we need to be able to define some code for dimension combinations. Let me say it will be Sales Code, you can choose name of your choice, this is not Salesperson code.

I am assuming these dimensions will follow the sequence as defined on my General Ledger Setup as below:

On Sales Order & Invoice user should be able to select this Sales Code and dimensions should be populated on order accordingly.

For tracking purpose this Sales Code should flow to Posted Sales Invoice and Customer Ledger.

So, Let’s Start with the development process:

Step-1 We will Create the Table

Here is the code for LookupDimValue Function, it will set filter for Dimension Code on Dimension Value table, as per the Dimension No passed. (1 is for Shortcut Dimension defined on General Ledger Setup, similarly for other 8 dimensions)

Step-2 Next, we will create the Page for this Setup

Step-3 Next, we will add the Sales Code field to all required Tables & Pages

Here is the code for AddDim Function. It is assumed that only combination provided in Sales Code Setup will be used. If you have defined Default dimensions or Combinations, those need to be preserved else this code will overwrite them. You will have to find the Data Set Entry, store them in temporary table used in below code and then add all the dimensions from the setup.

When you Select Sales Code on the Order or Invoice it will populate all the dimensions defined in the Setup.

Code for other Tables & Pages

Step-4 Next, we need to take care to flow the Sales Code to the Ledger and Posted documents.

For posted documents we need not to worry it will flow automatically provided we have defined the fields on same Id.

However, for ledger we will require to use Events to pass the data to their destinations. In this case we are only passing to Customer Ledger Entry. For this we will create a Codeunit.

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 the even you are looking for.

When pressing Enter to select an event entry, an event subscriber for the event will be inserted at the cursor position in the active AL code editor window.

Here is the Codeunit Code:

This is not the final code; much more can be done or need to be done before it can be delivered to customer for their use. Purpose of this walkthrough was to demo the way we can customize the solution using extensions and publishing to Online tenant in Sandbox.

Hope you enjoyed the information. Will come with more similar information in my next posts. Till then keep exploring, learning and sharing your knowledge with others.

Remain safe, take care of your loved ones, put your mask, maintain safe distance and don’t forget to get vaccinated.