Skip to content

[Event Request][Extensibility Request] Additional Events to resolve Ambiguous Integration Table Mappings with shared BC Table IDs (Codeunit "CRM Order Status Update Job", Codeunit "CRM Integration Management", Table "Coupling Record Buffer") #30429

Description

Why do you need this change?

We need this change because for one of our customers we extended the BC <-> CRM integration to synchronize sales quotes to crm by using the integration table mappings, the customer does not use the quote integration which pulls quotes from crm to bc.
Therefore we need a way to handle the integration not falsely finding the wrong Integration Table Mapping (Sales Order mapping).
This is mostly relevant when the integration is triggered from the UI context for example when decoupling records or manually coupling records.
But also for automated synchs. for example when the integration wants to log "Order Status Updates".

Describe the request

In codeunit "CRM Order Status Update Job", there is code that is filtering the "Integration Table Mapping" that is only filtering with the BC Table-Id ("Sales Header").
When a partner is extending the Integration Table Mappings by for example an additional "Sales Header" mapping that syncs quotes or other unposted sales documents the code in the function "UpdateSalesOrders" can create updae logs for the wrong integration table mapping and creates unwanted noises.

When defining couplings or deleting couplings manually through the BC UI we would also benefit greatly if the existing procedures will get additional event subscribers so when extending the integration and by any chance you have an integration mapping that has the same "Table-ID" (BC) multiple times but always other CRM destinations it will most likely find the wrong integration mapping, because of the ambiguity of only filtering with "Table-ID" from the BC Side of the integration, in a lot of places the source record is provided to the coupling procedures from which you could distinct the right integratione table mapping.

When the codeunit "CRM Order Status Update Job" logs update traces for sales order status updates it can potentially log messages into the wrong integration table mapping which creates unwanted noises.

local procedure UpdateSalesOrders(JobLogEntryNo: Integer)
var
    IntegrationTableMapping: Record "Integration Table Mapping";
    IntegrationTableSynch: Codeunit "Integration Table Synch.";
    SynchActionType: Option "None",Insert,Modify,ForceModify,IgnoreUnchanged,Fail,Skip,Delete;
    Counter: Integer;
begin
    IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
    IntegrationTableMapping.SetRange("Table ID", DATABASE::"Sales Header");
    -->
     // NEW EVENT
     OnBeforeFindIntegrationTableMapping(IntegrationTableMapping);
     // NEW EVENT
    <--
    if IntegrationTableMapping.FindFirst() then
        IntegrationTableSynch.BeginIntegrationSynchJob(TABLECONNECTIONTYPE::CRM, IntegrationTableMapping, DATABASE::"Sales Header")
    else
        IntegrationTableSynch.BeginIntegrationSynchJobLoging(TABLECONNECTIONTYPE::CRM, CODEUNIT::"CRM Order Status Update Job", JobLogEntryNo, DATABASE::"Sales Header");

    Counter := CreateStatusPostOnModifiedOrders();
    IntegrationTableSynch.UpdateSynchJobCounters(SynchActionType::Modify, Counter);

    IntegrationTableSynch.EndIntegrationSynchJobWithMsg(GetOrderStatusUpdateFinalMessage());
end;

The Event Subscriber signature should be as follwing, to add additional filters to the integration table mapping, for example adding the matching "Integration Table ID", so it will find the "Sales Order" Mapping.
Like you are already doing in for example codeunit "CRM Notes Synch Job"

[IntegrationEvent(false, false)]
local procedure OnBeforeFindIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping")
begin
end;

So when a user is manually coupling or deleting a coupling via the UI in BC it would be a great addition without relying on uncertain "hacks" to overwrite the integration table mapping.

Scenario #1 Invocation via "Define Coupling" of codeunit "CRM Integration Management"

In table "Coupling Record Buffer" in procedure "Initialize" there is no event that provides a way to overwrite the assignment of the "IntegrationTableMapping.Name" to the field "CRM Table Name" in the OnValidate-trigger of "NAV Table ID" which will result in the integration possibly assigning the wrong integration table mapping.
I think it would be a good addition to provide a event at the end of the procedure "Initialize" to overwrite the table mapping name when everything is assigned and set up by the base app code, so you have enough information to differentiate what table mapping to assign (via the "NAV Record ID"), and not rely on OnAfterValidateEvent triggers.

procedure Initialize(NAVRecordID: RecordID; IsOption: Boolean)
var
    IntegrationTableMapping: Record "Integration Table Mapping";
    RecordRef: RecordRef;
begin
  RecordRef := NAVRecordID.GetRecord();
  RecordRef.Find();

  Init();
  Validate("NAV Table ID", NAVRecordID.TableNo);
  "NAV Record ID" := NAVRecordID;
  "NAV Name" := NameValue(RecordRef);
  "Is Option" := IsOption;
  "CRM Table ID" := CRMSetupDefaults.GetCRMTableNo("NAV Table ID");
  if not IsOption then begin
      if CRMSetupDefaults.GetDefaultDirection("NAV Table ID") = IntegrationTableMapping.Direction::FromIntegrationTable then
          Validate("Sync Action", "Sync Action"::"From Integration Table")
      else
          Validate("Sync Action", "Sync Action"::"To Integration Table");

      if FindCRMId() then
          if CalcCRMName() then begin
              Validate("Sync Action", "Sync Action"::"Do Not Synchronize");
              "Saved CRM ID" := "CRM ID";
          end;
  end else begin
      if IntegrationTableMapping.FindMappingForTable(RecordRef.Number) then
          if IntegrationTableMapping.GetDirection() = IntegrationTableMapping.Direction::FromIntegrationTable then
              Validate("Sync Action", "Sync Action"::"From Integration Table")
          else
              Validate("Sync Action", "Sync Action"::"To Integration Table");
      if FindCRMOptionId() then begin
          Validate("Sync Action", "Sync Action"::"Do Not Synchronize");
          "Saved CRM Option Id" := "CRM Option Id";
      end;
  end;
 -->
// NEW EVENT
OnAfterInitialize(Rec)
<--
end;

The event subscriber signature should be the following

[IntegrationEvent(false, false)]
local procedure OnAfterInitialize(var Rec: Record "Coupling Record Buffer")
begin
end;

Scenario #2 Invocation via "Remove Coupling" of codeunit "CRM Integration Management"

When a user for example removes the coupling of a record from the UI right now there is no way to overwrite it finding the right integration table mapping when for example there are multiple integration table mappings for the same BC Table-ID.

internal procedure RemoveCoupling(var LocalRecordRef: RecordRef; Schedule: Boolean)
var
    IntegrationTableMapping: Record "Integration Table Mapping";
    IntegrationRecordSynch: Codeunit "Integration Record Synch.";
begin
    if IsCRMTable(LocalRecordRef.Number()) then begin
        Session.LogMessage('0000DHU', StrSubstNo(NotLocalTableTxt, GetTableCaption(LocalRecordRef.Number())), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
        exit;
    end;
    
    -->
    // NEW EVENT
    OnBeforeGetIntegrationTableMappingForUncoupling(IntegrationTableMapping, LocalRecordRef);
    <--
    if GetIntegrationTableMappingForUncoupling(IntegrationTableMapping, LocalRecordRef.Number()) then
        if Schedule then
            ScheduleUncoupling(IntegrationTableMapping, IntegrationRecordSynch.GetTableViewForLocalRecords(LocalRecordRef), '')
        else
            PerformUncoupling(IntegrationTableMapping, IntegrationRecordSynch.GetTableViewForLocalRecords(LocalRecordRef), '')
    else
        RemoveCouplingToRecord(LocalRecordRef);
end;

And the following event signature

[IntegrationEvent(false, false)]
local procedure OnBeforeGetIntegrationTableMappingForUncoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef)
begin
end;

Scenario #3 when record coupling is invocated for match based coupling in codeunit "CRM Integration Management" in procedure "MatchBasedCoupling"

local procedure MatchBasedCoupling(var LocalRecordRef: RecordRef; CouplingOption: Option None,Background,Foreground)
var
    IntegrationTableMapping: Record "Integration Table Mapping";
    IntegrationFieldMapping: Record "Integration Field Mapping";
    IntegrationRecordSynch: Codeunit "Integration Record Synch.";
    CoupledToCRMFieldRef: FieldRef;
begin
    if IsCRMTable(LocalRecordRef.Number()) then begin
        Session.LogMessage('0000EZP', StrSubstNo(NotLocalTableTxt, GetTableCaption(LocalRecordRef.Number())), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
        exit;
    end;
    
   -->
   // NEW EVENT
   OnBeforeGetIntegrationTableMappingForMatchBasedCoupling(IntegrationTableMapping, LocalRecordRef);
   <--
    if GetIntegrationTableMappingForCoupling(IntegrationTableMapping, LocalRecordRef.Number()) then begin
        IntegrationFieldMapping.SetMatchBasedCouplingFilters(IntegrationTableMapping);
        if Page.RunModal(Page::"Match Based Coupling Criteria", IntegrationFieldMapping) = Action::LookupOK then
            if CouplingOption in [CouplingOption::Background, CouplingOption::Foreground] then begin
                if FindCoupledToCRMField(LocalRecordRef, CoupledToCRMFieldRef) then
                    CoupledToCRMFieldRef.SetRange(false);
                if CouplingOption = CouplingOption::Background then
                    ScheduleCoupling(IntegrationTableMapping, IntegrationRecordSynch.GetTableViewForLocalRecords(LocalRecordRef), false)
                else
                    PerformCoupling(IntegrationTableMapping, IntegrationRecordSynch.GetTableViewForLocalRecords(LocalRecordRef), false);
            end;
    end;
end;

And the event signature should be as following

[IntegrationEvent(false, false)]
local procedure OnBeforeGetIntegrationTableMappingForMatchBasedCoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef)
begin
end;

Internal work item: AB#647520

Metadata

Metadata

Assignees

No one assigned

    Labels

    IntegrationGitHub request for Integration areaevent-requestRequest for adding an event

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions