CRM Integration: The magic behind CompanyId, aka multi-company BC-CRM integration

by Jul 29, 2023CRM Integration

Home 9 Development 9 CRM Integration 9 CRM Integration: The magic behind CompanyId, aka multi-company BC-CRM integration

See all articles in CRM Integration | MSDyn365 Business Central – Tomas Kapitan (kepty.cz) category to get max from the OOTB and custom integration.


How does it work?

It’s simple. When the BC-CRM integration is enabled, one of the steps is to deploy the solution to Dataverse. I’ll go through what’s in this solution in one of the next posts, but for now, it’s important to know that this solution adds a field called “bcbi_companyid” to standard Dataverse/CRM entities (such as Account or Salesorder entity).

This field has a relation to the “bcbi_company” entity (part of the same solution). This table specifies relations between CRM and BC company, where CRM company is the primary key, and BC company is defined in the “bcbi_externalid” field. It is not important what’s the content of these fields, but if you want to know more, check codeunit 7201 “CDS Integration Impl.”, procedure GetCompanyExternalId(…).

This field is also added to pages in CRM to allow users to specify to which company the record belongs to.

In BC, it’s all done automatically. For BC to CRM synchronization, it will automatically populate the CompanyId when the record is synchronized (created or modified). For CRM to BC integration, the filter that specifies which records should be synchronized is defined in the “Integration Table Mapping“. The filter is automatically defined as CurrentCompanyId OR EmptyCompanyId, so if you have a project with more companies, I recommend removing the OR statement and allowing only records from the CurrentCompanyId.

And what if I need to add support for CompanyId to custom integration?

Besides the changes needed in CRM (add a custom field with relation to the CDS Company table and some logic directly in CRM to allow users to specify the company), there are not many changes needed.

First of all, the field must be named “CompanyId”. Why? When synchronizing records from BC to CRM, there is a procedure that tries to find the CompanyId field. It’s not extendible (at least not now in 22.4), and searches for a GUID field called “CompanyId“). This should be fine as long as we do not need to add the CompanyId field to the table from a different extension (or base app). I have never had such a request yet, so I don’t think it will be a huge issue. See below for details of this procedure (it’s located in codeunit 7201 “CDS Integration Impl.”).

    internal procedure FindCompanyIdField(var RecRef: RecordRef; var CompanyIdFldRef: FieldRef): Boolean
    var
        Field: Record "Field";
        TableNo: Integer;
        FieldNo: Integer;
    begin
        TableNo := RecRef.Number();
        if CachedCompanyIdFieldNo.ContainsKey(TableNo) then
            FieldNo := CachedCompanyIdFieldNo.Get(TableNo)
        else begin
            Field.SetRange(TableNo, TableNo);
            Field.SetRange(Type, Field.Type::GUID);
            Field.SetRange(FieldName, 'CompanyId');
            if Field.FindFirst() then
                FieldNo := Field."No."
            else
                FieldNo := 0;
            CachedCompanyIdFieldNo.Add(TableNo, FieldNo);
        end;
        if FieldNo = 0 then
            exit(false);
        CompanyIdFldRef := RecRef.Field(FieldNo);
        exit(true);
    end;

See below. It does not matter how the field is called in the Dataverse table; it just needs to be named “CompanyId” in the BC table.

        field(750; CompanyId; GUID) // Name must be "CompanyId"
        {
            Caption = 'Company Id';
            Description = 'Unique identifier of the company that owns the order.';
            ExternalName = 'tka_company';
            ExternalType = 'Lookup';
            TableRelation = "CDS Company".CompanyId;
        }

Then, we need to subscribe to the event OnHasCompanyIdField in codeunit “CDS Integration Mgt.” and specify all custom tables with the CompanyId field.

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Integration Mgt.", 'OnHasCompanyIdField', '', false, false)]
    local procedure HandleOnHasCompanyIdField(TableId: Integer; var HasField: Boolean)
    begin
        case TableId of
            Database::"TKA My Custom Table":
                HasField := true;
        end;
    end;

Now we need to update the CRM record when create/updated and set the company ID. For this, we need to subscribe to OnBeforeInsertRecord and OnBeforeModifyRecord in codeunit “Integration Rec. Synch. Invoke”.

To set the company ID, we can use the procedure SetCompanyId(…) from “CDS Integration Mgt.”; see below.

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeInsertRecord', '', false, false)]
    local procedure OnBeforeInsertRecord(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
    begin
        case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
            CRMCodesMgt.GetCRMMyCustomTableMappingCode():
                SetCompanyId(DestinationRecordRef);
        end;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnBeforeModifyRecord', '', false, false)]
    local procedure OnBeforeModifyRecord(IntegrationTableMapping: Record "Integration Table Mapping"; SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef)
    begin
        case GetSourceDestCode(SourceRecordRef, DestinationRecordRef) of
            CRMCodesMgt.GetCRMMyCustomTableMappingCode():
                SetCompanyId(DestinationRecordRef);
        end;
    end;

    procedure SetCompanyId(var DestinationRecordRef: RecordRef)
    var
        CDSIntegrationMgt: Codeunit "CDS Integration Mgt.";
    begin
        if not CDSIntegrationMgt.IsIntegrationEnabled() then
            exit;
        if CDSIntegrationMgt.CheckCompanyId(DestinationRecordRef) then
            exit;
        CDSIntegrationMgt.SetCompanyId(DestinationRecordRef);
    end;

Lastly, we need to set the filter in the mapping. It can be done manually in Business Central in the “Integration Table Mapping” or in your ResetXXX() procedure. It just requires two lines, one to get the CDS Company for the current BC company (using CDSIntegrationMgt.GetCDSCompany(…) procedure) and set the filter.

    local procedure ResetMyCustomTableMapping(IntegrationTableMappingName: Code[20]; EnqueueJobQueEntry: Boolean)
    begin
        ...

        MyCustomTable.Reset();
        if CDSIntegrationMgt.GetCDSCompany(CDSCompany) then
            MyCustomTable.SetFilter(CompanyId, StrSubstno(OrFilterTok, CDSCompany.CompanyId, EmptyGuid));
        IntegrationTableMapping.SetIntegrationTableFilter(GetTableFilterFromView(Database::"TKA My Custom Table", MyCustomTable.TableCaption(), MyCustomTable.GetView()));
        IntegrationTableMapping.Modify();

        ...
    end;

Recent Articles from the category

Sign Up for News

Certifications

Highest certification
Microsoft Data Management and
also in D365 Business Central

Microsoft Certified: Dynamics 365 Business Central Functional Consultant Associate

See other certifications here