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.

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.

AL, BC14, BC15, BC16, BC17, Business Central, Codeunit, Development Tips, Enum, Extension, How To, Install, JSON, SOAP, Tip & Tricks, V1, V2, Visual Studio Code, Wave 1, Wave 2, Web Client, Web Services, XML

Update Tracking Line, Post Shipment using Web Services in Business Central

Hi, today I will discuss Web Service with below requirement. You can check other earlier post on this topic using search on right side of this blog page.

I got one request on topic from one of my customer cum blog follower, case study is as follows:

a) Will update Qty to Ship on document using Web Service from other application

b) Update Tracking Line for the Shipment using Web Service from other application

c) Post the Shipment using Web Service from other application

To get this we will create an Extension using VS Code which will have:

a) Codeunit with some functions which will be called using Web Service

b) A XML file to automatically expose above codeunit upon publishing this extension

Let us start how to achieve above requirement:

I have created this in BC16, will be same for other versions too.

Creating a AL Project:

Update your app.json & launch.json as per your environment, authentication, dependencies, port etc.

Creating a Codeunit: (TrackingCodeWS.al)

This function will be used to update “Qty. to Ship” on Sales Line

InitLot function as name suggests.

GetNextEntryNo function as name suggests.

It will depend on how you design your codeunit, you may require or not depends on logic how you use them.

AssignLotSalesLine is function which fills the lot details to temp Reservation Entry Table.

CreateReservationEntrySalesLine is the main function which actually makes your Tracking Lines and assign to Sales Line as per information filled in TempReservationEntry table in above function.

PostSalesOrder function is used for posting your Shipment.

Creating XML file to Publish Web Service

This XML file will ensure publishing of Web Service on Install of the Extension. You can directly make entry to Web Service table but benefit of using XML is when to Uninstall your extension the Web Service too will be removed, else if entry made to table you will have to take care to same yourself.

After Install of Extension, your Web Service is automatically Published.

Consume Web Service from Visual Studio

Below is the C# code to consume Web Service created above, you can modify code as per your requirement.

In above code we added Service Reference to Web Service and called functions created in Codeunit.

You can see earlier posts for step wise instruction how to add Web Reference to the Web Service in Visual Studio.

UpdateQtyToShipSalesLine:

Here “1” is used for Document Type = Order,”1008″ is my order no, 10000 is the Line No., 2 is the Quantity to Ship.

AssignLotSalesLine:

“L0001” & “L0002” is my Lot No, Serial No. is blank in this case, 1 is the Quantity, last three parameter is same as in above function call.

PostSalesOrder:

First 2 Parameter is same as above function call Document Type & Order No, third parameter is Ship = TRUE, Fourth Parameter is Invoice = FALSE.

Conclusion

This post gives you overall Idea how you can use Web Service to handle Sales Document from Web Service, you can make required modification to achieve exactly as per your requirement.

AL, API, Application, AppVersion, BC14, BC17, Business Central, Code, Codeunit, DataVersion, Dependencies, Development Tips, Dynamics 365, Extension, GetCurrentModuleInfo, GetModuleInfo, How To, Information, Instalation & Configuration, Install, Name, on-premises, OnInstallAppPerCompany, Publisher, Tip & Tricks, Version.Create, Visual Studio Code, Wave 1, Wave 2

Extension Install Code

Extension install code is executed when:

  • An extension is installed for the first time.
  • An uninstalled version is installed again.

This gives you control to write different logics for first time installation of extension and reinstallations of uninstalled extensions.

This is achieved by defining Install Codeunit in your Extension.

First thing first:

  1. Subtype property of codeunit need to be set to Install
  2. OnInstallAppPerCompany trigger is triggered when the Extension is Installed first time or subsequent install of same version on Extension.
  3. DataVersion property one of the important properties which tells you what version of data that you’re dealing with.
  4. AppVersionDataVersionDependenciesIDName, and Publisher. These properties are encapsulated in a ModuleInfo data type. You can access these properties by using the NavApp.GetCurrentModuleInfo and NavApp.GetModuleInfo methods.
  5. If the DataVersion property equals Version.Create(0,0,0,0), then it’s the first time that the extension is installed because no data exists in the archive.

Sample codeunit can be similar to below:

codeunit <ID> “Name of Codeunit

{

    // Install Logic

    Subtype = Install;

    trigger OnInstallAppPerCompany();

    var

        myAppInfo: ModuleInfo;

    begin

        NavApp.GetCurrentModuleInfo(myAppInfo);

// Get info about the currently executing module

        if myAppInfo.DataVersion = Version.Create(0, 0, 0, 0) then

// A ‘DataVersion’ of 0.0.0.0 indicates a ‘fresh/new’ install

            HandleFreshInstall

        else

            HandleReinstall;

// If not a fresh install, then we are Reinstalling the same version of the extension

    end;

    local procedure HandleFreshInstall();

    begin

        // Logic to execute on first time this extension is ever installed for this tenant.

        // Some possible usages: – Initial data setup for use

    end;

    local procedure HandleReinstall();

    begin

        // Logic to execute on reinstalling the same version of this extension back on this tenant.

        // Some possible usages: – Data ‘patchup’ work, for example, detecting if new ‘base’

// records have been changed while you have been working ‘offline’.

    end;

}

Happy Learning.