Codeunit 5330 CRM Integration Management, source in 29
Source29
src/Layers/W1/BaseApp/Integration/Dataverse/CRMIntegrationManagement.Codeunit.al4272 lines, Copyright (c) Microsoft Corporation. MIT
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace Microsoft.Integration.Dataverse;
using Microsoft.CRM.Comment;
using Microsoft.CRM.Contact;
using Microsoft.CRM.Opportunity;
using Microsoft.CRM.Profiling;
using Microsoft.CRM.Team;
using Microsoft.Finance.Currency;
using Microsoft.Finance.Dimension;
using Microsoft.Foundation.PaymentTerms;
using Microsoft.Foundation.Shipping;
using Microsoft.Foundation.UOM;
using Microsoft.Integration.D365Sales;
using Microsoft.Integration.SyncEngine;
using Microsoft.Inventory.Item;
using Microsoft.Projects.Resources.Resource;
using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Archive;
using Microsoft.Sales.Customer;
using Microsoft.Sales.Document;
using Microsoft.Sales.History;
using Microsoft.Sales.Pricing;
using Microsoft.Utilities;
using System;
using System.Apps;
using System.Environment;
using System.Environment.Configuration;
using System.Media;
using System.Reflection;
using System.Telemetry;
using System.Threading;
using System.Utilities;
codeunit 5330 "CRM Integration Management"
{
SingleInstance = true;
InherentEntitlements = X;
InherentPermissions = X;
Permissions = tabledata "Sales Invoice Header" = rm,
tabledata "CDS Connection Setup" = r,
tabledata "CRM Connection Setup" = r;
trigger OnRun()
begin
CheckOrEnableCRMConnection();
end;
var
CRMProductName: Codeunit "CRM Product Name";
CachedCoupledToCRMFieldNo: Dictionary of [Integer, Integer];
CachedDisableEventDrivenSynchJobReschedule: Dictionary of [Integer, Boolean];
CachedIsCRMIntegrationRecord: Dictionary of [Integer, Boolean];
CachedDoesJobActOnTable: Dictionary of [Text, Boolean];
CRMEntityUrlTemplateTxt: Label '%1/main.aspx?pagetype=entityrecord&etn=%2&id=%3', Locked = true;
NewestUIAppIdParameterTxt: Label '&appid=%1', Locked = true;
UnableToResolveCRMEntityNameFrmTableIDErr: Label 'The application is not designed to integrate table %1 with %2.', Comment = '%1 = table ID (numeric), %2 = Dataverse service name';
CouplingNotFoundErr: Label 'The record is not coupled to %1.', Comment = '%1 = Dataverse service name';
NoCardPageActionDefinedForTableIdErr: Label 'The open page action is not supported for Table %1.', Comment = '%1 = Table ID';
IntegrationTableMappingNotFoundErr: Label 'No %1 was found for table %2.', Comment = '%1 = Integration Table Mapping caption, %2 = Table caption for the table which is not mapped';
UpdateNowUniDirectionQst: Label 'Send data update to Dataverse.,Get data update from Dataverse.';
UpdateNowBiDirectionQst: Label 'Send data update to Dataverse.,Get data update from Dataverse.,Merge data.';
UpdateOneNowTitleTxt: Label 'Synchronize data for %1?', Comment = '%1 = Table caption and value for the entity we want to synchronize now.';
UpdateMultipleNowTitleTxt: Label 'Synchronize data for the selected records?';
ManageCouplingQst: Label 'The %1 record is not coupled to %2. Do you want to create a coupling?', Comment = '%1=The record caption (type), %2 = Dataverse service name';
SyncNowFailedMsg: Label 'The synchronization failed.';
SyncNowScheduledMsg: Label 'The synchronization has been scheduled.';
SyncNowSkippedMsg: Label 'The synchronization has been skipped.';
SyncMultipleMsg: Label 'The synchronization has been scheduled for %1 of %4 records. %2 records failed. %3 records were skipped.', Comment = '%1,%2,%3,%4 are numbers of records';
SyncSkippedMsg: Label 'The record will be skipped for further synchronization due to a repeatable failure.';
SyncRestoredMsg: Label 'The record has been restored for synchronization.';
SyncMultipleRestoredMsg: Label '%1 records have been restored for synchronization.', Comment = '%1 - an integer, a count of records.';
UncoupleFailedMsg: Label 'The uncoupling failed.';
UncoupleScheduledMsg: Label 'The uncoupling has been scheduled.';
UncoupleSkippedMsg: Label 'The uncoupling has been skipped.';
UncoupleMultipleMsg: Label 'The uncoupling has been scheduled for %1 of %4 records. %2 records failed. %3 records were skipped.', Comment = '%1,%2,%3,%4 are numbers of records';
CouplingFailedMsg: Label 'The coupling failed.';
CouplingScheduledMsg: Label 'The coupling has been scheduled.';
CouplingSkippedMsg: Label 'The coupling has been skipped.';
CouplingMultipleMsg: Label 'The coupling has been scheduled for %1 of %4 records. %2 records failed. %3 records were skipped.', Comment = '%1,%2,%3,%4 are numbers of records';
DetailsTxt: Label 'Details.';
UpdateOneNowToCRMQst: Label 'Send data update to %2 for %1?', Comment = '%1 = Table caption and value for the entity we want to synchronize now., %2 = Dataverse service name';
UpdateOneNowToModifiedCRMQst: Label 'The %3 record coupled to %1 contains newer data than the %2 record. Do you want to overwrite the data in %3?', Comment = '%1 = Table caption and value for the entity we want to synchronize now. %2 - product name, %3 = Dataverse service name';
UpdateOneNowFromCRMQst: Label 'Get data update from %2 for %1?', Comment = '%1 = Table caption and value for the entity we want to synchronize now., %2 = Dataverse service name';
UpdateOneNowFromOldCRMQst: Label 'The %2 record %1 contains newer data than the %3 record. Get data update from %3, overwriting data in %2?', Comment = '%1 = Table caption and value for the entity we want to synchronize now. %2 - product name, %3 = Dataverse service name';
UpdateMultipleNowToCRMQst: Label 'Send data update to %1 for the selected records?', Comment = '%1 = Dataverse service name';
UpdateMultipleNowFromCRMQst: Label 'Get data update from %1 for the selected records?', Comment = '%1 = Dataverse service name';
AccountStatisticsUpdatedMsg: Label 'The customer statistics have been successfully updated in %1.', Comment = '%1 = Dataverse service name';
BothRecordsModifiedBiDirectionalConflictMsg: Label 'Both the %1 record and the %3 %2 record have been changed since the last synchronization, or synchronization has never been performed. Bi-directional synchronization is forbidden as a changed bidirectional field was detected, but you can continue continue with uni-derictional synchronization. If you continue, data on one of the records will be lost and replaced with data from the other record.', Comment = '%1 and %2 area captions of tables such as Customer and CRM Account, %3 = Dataverse service name';
BothRecordsModifiedBiDirectionalNoConflictMsg: Label 'Both the %1 record and the %3 %2 record have been changed since the last synchronization, or synchronization has never been performed. No one changed bidirectional field was detected, therefore you can continue continue with both bi- and uni-directional synchronization. If you continue, data will be updated in accordance with the chosen synchronization direction and fields mapping.', Comment = '%1 and %2 area captions of tables such as Customer and CRM Account, %3 = Dataverse service name';
BothRecordsModifiedToCRMQst: Label 'Both %1 and the %4 %2 record have been changed since the last synchronization, or synchronization has never been performed. If you continue with synchronization, data in %4 will be overwritten with data from %3. Are you sure you want to synchronize?', Comment = '%1 is a formatted RecordID, such as ''Customer 1234''. %2 is the caption of a Dataverse table. %3 - product name, %4 = Dataverse service name';
BothRecordsModifiedToNAVQst: Label 'Both %1 and the %4 %2 record have been changed since the last synchronization, or synchronization has never been performed. If you continue with synchronization, data in %3 will be overwritten with data from %4. Are you sure you want to synchronize?', Comment = '%1 is a formatted RecordID, such as ''Customer 1234''. %2 is the caption of a Dataverse table. %3 - product name, %4 = Dataverse service name';
CRMIntegrationEnabledState: Option " ","Not Enabled",Enabled,"Enabled But Not For Current User";
CDSIntegrationEnabledState: Option " ","Not Enabled",Enabled,"Enabled But Not For Current User";
NotEnabledForCurrentUserMsg: Label '%3 Integration is enabled.\However, because the %2 Users Must Map to %4 Users field is set, %3 integration is not enabled for %1.', Comment = '%1 = Current User Id %2 - product name, %3 = CRM product name, %4 = Dataverse service name';
CRMIntegrationEnabledLastError: Text;
ImportSolutionConnectStringTok: Label '%1api%2/XRMServices/2011/Organization.svc', Locked = true;
UserDoesNotExistCRMErr: Label 'There is no user with email address %1 in %2. Enter a valid email address.', Comment = '%1 = User email address, %2 = Dataverse service name';
EmailAndServerAddressEmptyErr: Label 'The Integration User Email and Server Address fields must not be empty.';
CRMSolutionFileNotFoundErr: Label 'A file for a CRM solution could not be found.';
MicrosoftDynamicsNavIntegrationTxt: Label 'MicrosoftDynamicsNavIntegration', Locked = true;
AdminEmailPasswordWrongErr: Label 'Enter valid %1 administrator credentials.', Comment = '%1 = CRM product name';
OrganizationServiceFailureErr: Label 'The setup failed due to a generic error. Please sign in to the Dataverse organization with an account that has the System Administrator role. Ensure that no other import or deletion of a PowerApps solution is currently in progress. Additionally, verify that the solution file is not broken and that the specified administrator has sufficient privileges.';
InvalidUriErr: Label 'The value entered is not a valid URL.';
MustUseHttpsErr: Label 'The application is set up to support secure connections (HTTPS) to %1 only. You cannot use HTTP.', Comment = '%1 = CRM product name';
MustUseHttpOrHttpsErr: Label '%1 is not a valid URI scheme for %2 connections. You can only use HTTPS or HTTP as the scheme in the URL.', Comment = '%1 is a URI scheme, such as FTP, HTTP, chrome or file, %2 = CRM product name';
ReplaceServerAddressQst: Label 'The URL is not valid. Do you want to replace it with the URL suggested below?\\Entered URL: "%1".\Suggested URL: "%2".', Comment = '%1 and %2 are URLs';
CRMConnectionURLWrongErr: Label 'The URL is incorrect. Enter the URL for the %1 connection.', Comment = '%1 = CRM product name';
NoOf: Option ,Scheduled,Failed,Skipped,Total;
NotEnabledMsg: Label 'To perform this action you must be connected to %1. You can set up the connection to %1 from the %2 page. Do you want to open it now?', Comment = '%1 = Dataverse service name, %2 = Assisted Setup page caption.';
ConnectionStringFormatTok: Label 'Url=%1; UserName=%2; Password=%3; ProxyVersion=%4; %5', Locked = true;
OAuthConnectionStringFormatTok: Label 'Url=%1; AccessToken=%2; ProxyVersion=%3; %4', Locked = true;
CRMDisabledErrorReasonNotificationIdTxt: Label 'd82835d9-a005-451a-972b-0d6532de2072';
ConnectionBrokenMsg: Label 'The connection to Dynamics 365 Sales is disabled due to the following error: %1.\\Please contact your system administrator.', Comment = '%1 = Error text received from D365 for Sales';
ConnectionDisabledNotificationMsg: Label 'Connection to Dynamics 365 is broken and that it has been disabled due to an error: %1', Comment = '%1 = Error text received from D365 for Sales';
CRMConnectionSetupTxt: Label 'Set up %1 connection', Comment = '%1 = CRM product name';
VideoUrlSetupCRMConnectionTxt: Label '', Locked = true;
ConnectionDisabledReasonTxt: Label 'The connection to %1 was disabled because integration user %2 has insufficient privileges to run the synchronization.', Comment = '%1 = a URL, %2 - an email address';
CannotAssignRoleToTeamErr: Label 'Cannot assign role %3 to team %1 for business unit %2.', Comment = '%1 = team name, %2 = business unit name, %3 = security role name';
CannotAssignRoleToTeamTxt: Label 'Cannot assign role to team.', Locked = true;
IntegrationRoleNotFoundErr: Label 'There is no integration role %1 for business unit %2.', Comment = '%1 = role name, %2 = business unit name';
TeamNotFoundErr: Label 'Cannot find the default owning team for the coupled business unit %1 selected on page %2. To continue, you can select another business unit or revert to the default business unit that was created during setup.', Comment = '%1 = business unit name, %2 = setup page caption';
TeamNotFoundTxt: Label 'The team was not found.', Locked = true;
RoleNotFoundForBusinessUnitTxt: Label 'Integration role is not found for business unit.', Locked = true;
CategoryTok: Label 'AL Dataverse Integration', Locked = true;
TimeoutTxt: Label 'timeout', Locked = true;
RetryAfterTimeoutErr: Label 'The operation timed out. Try again.\\%1', Comment = '%1 - exception message ';
ConnectionFailureTxt: Label 'Connection failure.', Locked = true;
DisableIntegrationTxt: Label 'Disable integration.', Locked = true;
ClearDisabledReasonTxt: Label 'Clear disabled reason.', Locked = true;
IntegrationDisabledTxt: Label 'Integration is disabled.', Locked = true;
IntegrationNotConfiguredTxt: Label 'Integration is not configured.', Locked = true;
NoPermissionsTxt: Label 'No permissions.', Locked = true;
NotLocalTableTxt: Label 'Table %1 is not local.', Locked = true;
UpdateConflictHandledFromIntTxt: Label 'Update conflict handled by getting update from Dynamics 365 Sales.', Locked = true;
UpdateConflictHandledToIntTxt: Label 'Update conflict handled by sending update to Dynamics 365 Sales.', Locked = true;
UpdateConflictHandledSkipTxt: Label 'Update conflict handled by skipping the updated record.', Locked = true;
DeletionConflictHandledRemoveCouplingTxt: Label 'Deletion conflict handled by removing the coupling to the deleted record.', Locked = true;
DeletionConflictHandledRestoreRecordTxt: Label 'Deletion conflict handled by restoring the deleted record.', Locked = true;
ResetAllCustomIntegrationTableMappingsLbl: Label 'One or more of the selected integration table mappings is custom.\\Restoring the default table mapping for a custom table mapping will restore all custom table mappings to their default.\\Do you want to continue?';
DeletedRecordWithZeroTableIdTxt: Label 'CRM Integration Record with zero Table ID has been deleted. Integration ID: %1, CRM ID: %2', Locked = true;
AllRecordsMarkedAsSkippedTxt: Label 'All of selected %1 records are marked as skipped.', Comment = '%1 = table caption';
RecordMarkedAsSkippedTxt: Label 'The %1 record is marked as skipped.', Comment = '%1 = table caption';
AllRecordsAlreadyCoupledTxt: Label 'All of the selected records are already coupled.', Comment = '%1 = table caption';
RecordAlreadyCoupledTxt: Label 'The record is already coupled.', Comment = '%1 = table caption';
DetailedNotificationMessageTxt: Label '%1 %2', Comment = '%1 - notification message, %2 - details', Locked = true;
CommonNotificationNameTxt: Label 'Notify the user about scheduled Dataverse synchronization jobs.', Comment = 'Dataverse is a name of a Microsoft service and must not be translated.';
SkippedRecordsNotificationNameTxt: Label 'Notify the user about records that are skipped during Dataverse synchronization jobs.', Comment = 'Dataverse is a name of a Microsoft service and must not be translated.';
CommonNotificationDescriptionTxt: Label 'Turns the user''s attention to the Integration Synchronization Jobs page.';
SkippedRecordsNotificationDescriptionTxt: Label 'Turns the user''s attention to the Coupled Data Synchronization Errors page.';
UserDisabledNotificationTxt: Label 'The user disabled notification ''%1''.', Locked = true;
DisableNotificationTxt: Label 'Disable this notification.';
UserOpenedIntegrationSynchJobListViaNotificationTxt: Label 'User opened Integration Synchronization Pobs via the notification.', Locked = true;
BrokenCouplingsFoundAndMarkedAsSkippedForMappingTxt: Label 'Broken couplings were found and marked as skipped. Mapping: %1 - %2. Direction: %3. Count: %4.', Locked = true;
BrokenCouplingsFoundAndMarkedAsSkippedTotalTxt: Label 'Broken couplings were found and marked as skipped. Total count: %1.', Locked = true;
NoBrokenCouplingsFoundTxt: Label 'No broken couplings were found.', Locked = true;
AccountRelationshipTypeNotSupportedErr: Label 'Dynamics 365 Sales account should have relationship type of Customer or Vendor.';
ProductTypeNotSupportedErr: Label 'Dynamics 365 Sales product should have type of Sales Inventory or Services.';
UpdateUnitGroupMappingJQEDescriptionTxt: Label 'Updating CRM Unit Group Mapping';
RescheduledTaskTxt: label 'Rescheduled task %1 for Job Queue Entry %2 (%3) to run not before %4', Locked = true;
SalesProDefaultSettingsPrivilegeNameTxt: label 'prvReadmsdynce_salesprodefaultsettings', Locked = true;
SalesProIntegrationSolutionImportedTxt: label 'Integration solution for Sales Professional is imported.', Locked = true;
CompanyParameterTok: label '?company=', Locked = true;
MultipleCompanyLinkLbl: Label 'https://go.microsoft.com/fwlink/?linkid=2259003', Locked = true;
FSIntegrationAppSourceLinkTxt: Label 'https://go.microsoft.com/fwlink/?linkid=2277825', Locked = true;
FieldServiceIntegrationAppIdLbl: Label '1ba1031e-eae9-4f20-b9d2-d19b6d1e3f29', Locked = true;
procedure IsCRMIntegrationEnabled(): Boolean
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if not CRMConnectionSetup.ReadPermission() then
exit(false);
if CRMIntegrationEnabledState = CRMIntegrationEnabledState::" " then begin
ClearLastError();
CRMIntegrationEnabledState := CRMIntegrationEnabledState::"Not Enabled";
Clear(CRMIntegrationEnabledLastError);
if CRMConnectionSetup.Get() then begin
CRMConnectionSetup.RestoreConnection();
if CRMConnectionSetup."Is Enabled" then begin
if not HasTableConnection(TABLECONNECTIONTYPE::CRM, GetDefaultTableConnection(TABLECONNECTIONTYPE::CRM)) then
CRMConnectionSetup.RegisterConnection();
CRMIntegrationEnabledState := CRMIntegrationEnabledState::Enabled;
OnAfterCRMIntegrationEnabled();
end;
end;
end;
exit(CRMIntegrationEnabledState = CRMIntegrationEnabledState::Enabled);
end;
procedure IsCDSIntegrationEnabled(): Boolean
var
isEnabled: Boolean;
initConnectionHandled: Boolean;
ConnectionName: text;
begin
OnIsCDSIntegrationEnabled(isEnabled);
if isEnabled then begin
OnInitCDSConnection(ConnectionName, initConnectionHandled);
CDSIntegrationEnabledState := CDSIntegrationEnabledState::Enabled;
end else
CDSIntegrationEnabledState := CDSIntegrationEnabledState::"Not Enabled";
exit(isEnabled);
end;
[Scope('OnPrem')]
procedure IsIntegrationEnabled(): Boolean
var
CRMConnectionSentup: Record "CRM Connection Setup";
begin
if not CRMConnectionSentup.ReadPermission() then
exit(false);
if not CRMConnectionSentup.Get() then
exit(false);
if not CRMConnectionSentup."Is Enabled" then
exit(false);
exit(true);
end;
[IntegrationEvent(false, false)]
local procedure OnIsCDSIntegrationEnabled(var isEnabled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
procedure OnInitCDSConnection(var ConnectionName: Text; var handled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
procedure OnGetCDSIntegrationUserId(var IntegrationUserId: Guid; var handled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
procedure OnGetCDSServerAddress(var CDSServerAddress: Text; var handled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
procedure OnTestCDSConnection(var handled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
procedure OnCloseCDSConnection(ConnectionName: Text; var handled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterCRMIntegrationEnabled()
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeOpenRecordCardPage(RecordID: RecordID; var IsHandled: Boolean)
begin
end;
procedure IsCRMSolutionInstalled(): Boolean
begin
if TryTouchCRMSolutionEntities() then
exit(true);
ClearLastError();
exit(false);
end;
[Scope('OnPrem')]
procedure CheckSolutionVersionOutdated(): Boolean
var
CDSIntegrationMgt: Codeunit "CDS Integration Mgt.";
NavTenantSettingsHelper: DotNet NavTenantSettingsHelper;
Version: DotNet Version;
SolutionVersion: Text;
begin
if CDSIntegrationMgt.GetSolutionVersion(MicrosoftDynamicsNavIntegrationTxt, SolutionVersion) then
if Version.TryParse(SolutionVersion, Version) then
exit(Version.CompareTo(NavTenantSettingsHelper.GetPlatformVersion()) < 0);
exit(true);
end;
[TryFunction]
local procedure TryTouchCRMSolutionEntities()
var
CRMNAVConnection: Record "CRM NAV Connection";
CRMAccountStatistics: Record "CRM Account Statistics";
Cnt: Integer;
begin
Cnt := CRMAccountStatistics.Count();
Cnt := CRMNAVConnection.Count();
if Cnt > 0 then
exit;
end;
procedure SetCRMNAVConnectionUrl(WebClientUrl: Text[250])
var
CRMNAVConnection: Record "CRM NAV Connection";
HttpUtility: DotNet HttpUtility;
NewConnection: Boolean;
begin
if StrPos(WebClientUrl, CompanyParameterTok) = 0 then
WebClientUrl += CompanyParameterTok + HttpUtility.UrlPathEncode(CompanyName());
CRMNAVConnection.SetRange("Dynamics NAV URL", WebClientUrl);
if not CRMNAVConnection.FindFirst() then begin
CRMNAVConnection.Init();
NewConnection := true;
end;
CRMNAVConnection."Dynamics NAV URL" := WebClientUrl;
CRMNAVConnection.Name := CopyStr(CompanyName(), 1, MaxStrLen(CRMNAVConnection.Name));
if NewConnection then
CRMNAVConnection.Insert()
else
CRMNAVConnection.Modify();
end;
internal procedure RemoveCRMNAVConnectionUrl(WebClientUrl: Text[250]): Boolean
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if CRMConnectionSetup.Get() then
if CRMConnectionSetup."Is Enabled" then
exit(TryRemoveCRMNAVConnectionUrl(CRMConnectionSetup, WebClientUrl));
end;
[TryFunction]
local procedure TryRemoveCRMNAVConnectionUrl(var CRMConnectionSetup: Record "CRM Connection Setup"; WebClientUrl: Text[250])
var
CRMNAVConnection: Record "CRM NAV Connection";
HttpUtility: DotNet HttpUtility;
begin
if CRMConnectionSetup.Get() then
if CRMConnectionSetup."Is Enabled" then begin
if not HasTableConnection(TABLECONNECTIONTYPE::CRM, GetDefaultTableConnection(TABLECONNECTIONTYPE::CRM)) then
CRMConnectionSetup.RegisterConnection();
if StrPos(WebClientUrl, CompanyParameterTok) = 0 then
WebClientUrl += CompanyParameterTok + HttpUtility.UrlPathEncode(CompanyName());
CRMNAVConnection.SetRange("Dynamics NAV URL", WebClientUrl);
if not CRMNAVConnection.FindFirst() then
exit;
CRMNAVConnection.Delete();
end;
end;
procedure UpdateMultipleNow(RecVariant: Variant)
begin
UpdateMultipleNow(RecVariant, false);
end;
procedure UpdateMultipleNow(RecVariant: Variant; IsOption: Boolean)
var
RecRef: RecordRef;
RecordCounter: array[4] of Integer;
begin
RecordCounter[NoOf::Total] := GetRecordRef(RecVariant, RecRef);
if RecordCounter[NoOf::Total] = 0 then
exit;
if RecRef.Number = DATABASE::"CRM Integration Record" then
UpdateCRMIntRecords(RecRef, RecordCounter)
else
if IsOption then
UpdateOptions(RecRef, RecordCounter)
else
UpdateRecords(RecRef, RecordCounter);
end;
local procedure UpdateCRMIntRecords(var RecRef: RecordRef; var RecordCounter: array[4] of Integer)
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
SourceRecRef: RecordRef;
RecId: RecordId;
SelectedDirection: Integer;
Direction: Integer;
Unused: Boolean;
LocalTableId: Integer;
MappingName: Code[20];
RecordCount: Integer;
TotalCount: Integer;
IdFilter: Text;
IdFilterList: List of [Text];
LocalTableList: List of [Integer];
LocalIdList: List of [Guid];
CRMIdList: List of [Guid];
MappingDictionary: Dictionary of [Integer, Code[20]];
LocalIdDictionary: Dictionary of [Code[20], List of [Guid]];
CRMIdDictionary: Dictionary of [Code[20], List of [Guid]];
TableCaption: Text;
begin
if RecordCounter[NoOf::Total] = 1 then begin
RecRef.SetTable(CRMIntegrationRecord);
LocalTableId := CRMIntegrationRecord."Table ID";
CRMIntegrationRecord.FindRecordId(RecId);
GetIntegrationTableMapping(IntegrationTableMapping, RecId);
SourceRecRef.Get(RecId);
SelectedDirection :=
GetSelectedSingleSyncDirection(IntegrationTableMapping, SourceRecRef, CRMIntegrationRecord."CRM ID", Unused)
end else begin
IntegrationTableMapping.Direction := IntegrationTableMapping.Direction::Bidirectional;
SelectedDirection := GetSelectedMultipleSyncDirection(IntegrationTableMapping);
end;
if SelectedDirection < 0 then
exit; // The user cancelled
repeat
RecRef.SetTable(CRMIntegrationRecord);
CRMIntegrationRecord.FindRecordId(RecId);
LocalTableId := CRMIntegrationRecord."Table ID";
if not MappingDictionary.ContainsKey(LocalTableId) then begin
GetIntegrationTableMapping(IntegrationTableMapping, RecId);
MappingDictionary.Add(LocalTableId, IntegrationTableMapping.Name);
end;
MappingName := MappingDictionary.Get(LocalTableId);
if not LocalIdDictionary.ContainsKey(MappingName) then begin
Clear(LocalIdList);
LocalIdDictionary.Add(MappingName, LocalIdList);
end;
if not CRMIdDictionary.ContainsKey(MappingName) then begin
Clear(CRMIdList);
CRMIdDictionary.Add(MappingName, CRMIdList);
end;
LocalIdList := LocalIdDictionary.Get(MappingName);
CRMIdList := CRMIdDictionary.Get(MappingName);
LocalIdList.Add(CRMIntegrationRecord."Integration ID");
CRMIdList.Add(CRMIntegrationRecord."CRM ID");
TotalCount += 1;
until RecRef.Next() = 0;
if TotalCount = 0 then begin
if MappingDictionary.Keys().Count() = 1 then
TableCaption := GetTableCaption(MappingDictionary.Keys().Get(1));
if RecordCounter[NoOf::Total] > 1 then
SendNotification(StrSubstNo(DetailedNotificationMessageTxt, SyncNowSkippedMsg, StrSubstNo(AllRecordsMarkedAsSkippedTxt, TableCaption)))
else
SendNotification(StrSubstNo(DetailedNotificationMessageTxt, SyncNowSkippedMsg, StrSubstNo(RecordMarkedAsSkippedTxt, TableCaption)));
exit;
end;
LocalTableList := MappingDictionary.Keys();
foreach LocalTableId in LocalTableList do begin
MappingName := MappingDictionary.Get(LocalTableId);
LocalIdList := LocalIdDictionary.Get(MappingName);
RecordCount := LocalIdList.Count();
if RecordCount > 0 then begin
CRMIdList := CRMIdDictionary.Get(MappingName);
IntegrationTableMapping.Get(MappingName);
if IntegrationTableMapping.Direction = IntegrationTableMapping.Direction::Bidirectional then
Direction := SelectedDirection
else
Direction := IntegrationTableMapping.Direction;
if EnqueueSyncJob(IntegrationTableMapping, LocalIdList, CRMIdList, Direction, false) then begin
IntegrationRecordSynch.GetIdFilterList(LocalIdList, IdFilterList);
foreach IdFilter in IdFilterList do
if IdFilter <> '' then begin
CRMIntegrationRecord.SetFilter("Integration ID", IdFilter);
CRMIntegrationRecord.ModifyAll(Skipped, false);
end;
RecordCounter[NoOf::Scheduled] += RecordCount;
end else
RecordCounter[NoOf::Failed] += RecordCount;
end;
end;
SendSyncNotification(RecordCounter);
end;
local procedure UpdateRecords(var LocalRecordRef: RecordRef; var RecordCounter: array[4] of Integer)
var
IntegrationTableMapping: Record "Integration Table Mapping";
SelectedDirection: Integer;
CRMID: Guid;
Unused: Boolean;
Skipped: Boolean;
RecordCount: Integer;
LocalId: Guid;
LocalIdList: List of [Guid];
CRMIdList: List of [Guid];
begin
GetIntegrationTableMapping(IntegrationTableMapping, LocalRecordRef.RecordId());
if RecordCounter[NoOf::Total] = 1 then
if GetCoupledCRMID(LocalRecordRef.RecordId(), CRMID) then
SelectedDirection :=
GetSelectedSingleSyncDirection(IntegrationTableMapping, LocalRecordRef, CRMID, Unused)
else begin
DefineCouplingIfNotCoupled(LocalRecordRef.RecordId(), CRMID);
exit;
end
else
SelectedDirection := GetSelectedMultipleSyncDirection(IntegrationTableMapping);
if SelectedDirection < 0 then
exit; // The user cancelled
repeat
Skipped := false;
if RecordCounter[NoOf::Total] > 1 then begin
Skipped := not GetCoupledCRMID(LocalRecordRef.RecordId(), CRMID);
if not Skipped then
Skipped := WasRecordModifiedAfterLastSynch(IntegrationTableMapping, LocalRecordRef, CRMID, SelectedDirection);
end;
if not Skipped then
Skipped := IsRecordSkipped(LocalRecordRef.RecordId());
if Skipped then
RecordCounter[NoOf::Skipped] += 1
else begin
LocalId := LocalRecordRef.Field((LocalRecordRef.SystemIdNo())).Value();
LocalIdList.Add(LocalId);
CRMIdList.Add(CRMID);
end;
until LocalRecordRef.Next() = 0;
RecordCount := LocalIdList.Count();
if RecordCount = 0 then begin
if RecordCounter[NoOf::Total] > 1 then
SendNotification(StrSubstNo(DetailedNotificationMessageTxt, SyncNowSkippedMsg, StrSubstNo(AllRecordsMarkedAsSkippedTxt, GetTableCaption(LocalRecordRef.Number()))))
else
SendNotification(StrSubstNo(DetailedNotificationMessageTxt, SyncNowSkippedMsg, StrSubstNo(RecordMarkedAsSkippedTxt, GetTableCaption(LocalRecordRef.Number()))));
exit;
end;
if EnqueueSyncJob(IntegrationTableMapping, LocalIdList, CRMIdList, SelectedDirection, IntegrationTableMapping."Synch. Only Coupled Records") then
RecordCounter[NoOf::Scheduled] += RecordCount
else
RecordCounter[NoOf::Failed] += RecordCount;
SendSyncNotification(RecordCounter);
end;
local procedure UpdateOptions(var RecRef: RecordRef; var RecordCounter: array[4] of Integer)
var
IntegrationTableMapping: Record "Integration Table Mapping";
CRMOptionMapping: Record "CRM Option Mapping";
SelectedDirection: Integer;
CRMOptionId: Integer;
CRMOptionIds: List of [Integer];
LocalIds: List of [Guid];
begin
GetIntegrationTableMapping(IntegrationTableMapping, RecRef.RecordId());
if RecordCounter[NoOf::Total] = 1 then
if not GetMappedCRMOptionId(RecRef.RecordId(), CRMOptionId) then begin
DefineOptionMappingIfNotMapped(RecRef.RecordId(), CRMOptionId);
exit;
end;
SelectedDirection := GetSelectedMultipleSyncDirection(IntegrationTableMapping);
if SelectedDirection < 0 then
exit; // The user cancelled
if SelectedDirection = IntegrationTableMapping.Direction::FromIntegrationTable then begin
repeat
if GetMappedCRMOptionId(RecRef.RecordId(), CRMOptionId) then
CRMOptionIds.Add(CRMOptionId)
else
RecordCounter[NoOf::Skipped] += 1
until RecRef.Next() = 0;
if EnqueueOptionSyncJobFromIntegrationTable(IntegrationTableMapping, CRMOptionIds, SelectedDirection, true) then
RecordCounter[NoOf::Scheduled] += CRMOptionIds.Count()
else
RecordCounter[NoOf::Failed] += CRMOptionIds.Count();
end;
if SelectedDirection = IntegrationTableMapping.Direction::ToIntegrationTable then begin
repeat
CRMOptionMapping.SetRange("Record ID", RecRef.RecordId());
if not CRMOptionMapping.IsEmpty() then
LocalIds.Add(RecRef.Field(RecRef.SystemIdNo).Value())
else
RecordCounter[NoOf::Skipped] += 1
until RecRef.Next() = 0;
if EnqueueOptionSyncJobToIntegrationTable(IntegrationTableMapping, LocalIds, SelectedDirection, true) then
RecordCounter[NoOf::Scheduled] += LocalIds.Count()
else
RecordCounter[NoOf::Failed] += LocalIds.Count();
end;
SendSyncNotification(RecordCounter);
end;
procedure UpdateOneNow(RecordID: RecordID)
begin
// Extinct method. Kept for backward compatibility.
UpdateMultipleNow(RecordID)
end;
procedure UpdateSkippedNow(var CRMIntegrationRecord: Record "CRM Integration Record")
begin
UpdateSkippedNow(CRMIntegrationRecord, false);
end;
procedure UpdateSkippedNow(var CRMIntegrationRecord: Record "CRM Integration Record"; var CRMOptionMapping: Record "CRM Option Mapping")
begin
UpdateSkippedNow(CRMIntegrationRecord, CRMOptionMapping, false);
end;
procedure UpdateSkippedNow(var CRMIntegrationRecord: Record "CRM Integration Record"; SkipNotification: Boolean)
var
CRMOptionMapping: Record "CRM Option Mapping";
begin
UpdateSkippedNow(CRMIntegrationRecord, CRMOptionMapping, SkipNotification);
end;
procedure UpdateSkippedNow(var CRMIntegrationRecord: Record "CRM Integration Record"; var CRMOptionMapping: Record "CRM Option Mapping"; SkipNotification: Boolean)
var
RecId: RecordId;
RecRef: RecordRef;
RestoredRecCounter: Integer;
begin
if CRMIntegrationRecord.FindSet() then
repeat
if CRMIntegrationRecord.Skipped then
if CRMIntegrationRecord.FindRecordId(RecId) then begin
CRMIntegrationRecord.Validate(Skipped, false);
CRMIntegrationRecord.Modify();
RestoredRecCounter += 1;
end;
until CRMIntegrationRecord.Next() = 0;
if CRMOptionMapping.FindSet() then
repeat
if CRMOptionMapping.Skipped then begin
RecRef.Open(CRMOptionMapping."Table ID");
if RecRef.Get(CRMOptionMapping."Record ID") then begin
CRMOptionMapping.Skipped := false;
CRMOptionMapping.Modify();
RestoredRecCounter += 1;
end;
end;
until CRMOptionMapping.Next() = 0;
if not SkipNotification then
SendRestoredSyncNotification(RestoredRecCounter);
end;
procedure UpdateAllSkippedNow()
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMOptionMapping: Record "CRM Option Mapping";
RestoredRecCounter: Integer;
begin
CRMIntegrationRecord.SetRange(Skipped, true);
CRMOptionMapping.SetRange(Skipped, true);
RestoredRecCounter := CRMIntegrationRecord.Count() + CRMOptionMapping.Count();
if CRMIntegrationRecord.Count() > 0 then
CRMIntegrationRecord.ModifyAll(Skipped, false);
if CRMOptionMapping.Count() > 0 then
CRMOptionMapping.ModifyAll(Skipped, false);
SendRestoredSyncNotification(RestoredRecCounter);
end;
local procedure WasRecordModifiedAfterLastSynch(IntegrationTableMapping: Record "Integration Table Mapping"; RecRef: RecordRef; CRMID: Guid; SelectedDirection: Option): Boolean
var
IntegrationRecSynchInvoke: Codeunit "Integration Rec. Synch. Invoke";
CRMRecordRef: RecordRef;
RecordModified: Boolean;
CRMRecordModified: Boolean;
begin
RecordModified := IntegrationRecSynchInvoke.WasModifiedAfterLastSynch(IntegrationTableMapping, RecRef);
IntegrationTableMapping.GetRecordRef(CRMID, CRMRecordRef);
CRMRecordModified := IntegrationRecSynchInvoke.WasModifiedAfterLastSynch(IntegrationTableMapping, CRMRecordRef);
exit(
((SelectedDirection = IntegrationTableMapping.Direction::ToIntegrationTable) and CRMRecordModified) or
((SelectedDirection = IntegrationTableMapping.Direction::FromIntegrationTable) and RecordModified))
end;
procedure CheckOrEnableCRMConnection()
var
CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
GuidedExperience: Codeunit "Guided Experience";
FeatureTelemetry: Codeunit "Feature Telemetry";
AssistedSetup: Page "Assisted Setup";
GuidedExperienceType: Enum "Guided Experience Type";
begin
if IsCDSIntegrationEnabled() then
exit;
if IsCRMIntegrationEnabled() then
exit;
if CRMIntegrationEnabledLastError <> '' then
Error(CRMIntegrationEnabledLastError);
if GuiAllowed then
if CRMIntegrationEnabledState = CRMIntegrationEnabledState::"Enabled But Not For Current User" then
Message(NotEnabledForCurrentUserMsg, UserId, PRODUCTNAME.Short(), CRMProductName.SHORT(), CRMProductName.CDSServiceName())
else begin
FeatureTelemetry.LogUptake('0000H7D', 'Dynamics 365 Sales', Enum::"Feature Uptake Status"::Discovered);
FeatureTelemetry.LogUptake('0000H7E', 'Dataverse', Enum::"Feature Uptake Status"::Discovered);
if Confirm(StrSubstNo(NotEnabledMsg, CRMProductName.CDSServiceName(), AssistedSetup.Caption())) then begin
CDSIntegrationImpl.RegisterAssistedSetup();
GuidedExperience.Run(GuidedExperienceType::"Assisted Setup", ObjectType::Page, Page::"CDS Connection Setup Wizard");
if IsCDSIntegrationEnabled() then
exit;
end;
end;
Error('');
end;
local procedure GetRecordRef(RecVariant: Variant; var RecordRef: RecordRef): Integer
begin
case true of
RecVariant.IsRecord:
RecordRef.GetTable(RecVariant);
RecVariant.IsRecordId:
if RecordRef.Get(RecVariant) then
RecordRef.SetRecFilter();
RecVariant.IsRecordRef:
RecordRef := RecVariant;
else
exit(0);
end;
if RecordRef.FindSet() then
exit(RecordRef.Count);
exit(0);
end;
procedure CreateNewRecordsInCRM(RecVariant: Variant)
var
IntegrationTableMapping: Record "Integration Table Mapping";
RecordRef: RecordRef;
LocalIdListDictionary: Dictionary of [Code[20], List of [Guid]];
LocalIdList: List of [Guid];
LocalId: Guid;
begin
if GetRecordRef(RecVariant, RecordRef) = 0 then
exit;
GetIntegrationTableMappingFromCRMRecord(IntegrationTableMapping, RecordRef);
LocalIdListDictionary.Add(IntegrationTableMapping.Name, LocalIdList);
repeat
LocalId := RecordRef.Field(RecordRef.SystemIdNo()).Value();
LocalIdList.Add(LocalId);
until RecordRef.Next() = 0;
CreateNewRecordsInCRM(LocalIdListDictionary);
end;
procedure CreateNewOptionsInCRM(RecVariant: Variant)
var
IntegrationTableMapping: Record "Integration Table Mapping";
CRMOptionMapping: Record "CRM Option Mapping";
RecRef: RecordRef;
RecordCounter: array[4] of Integer;
LocalIdList: List of [Guid];
begin
if GetRecordRef(RecVariant, RecRef) = 0 then
exit;
GetIntegrationTableMapping(IntegrationTableMapping, RecRef.RecordId());
repeat
RecordCounter[NoOf::Total] += 1;
CRMOptionMapping.SetRange("Record ID", RecRef.RecordId());
if not CRMOptionMapping.IsEmpty() then
RecordCounter[NoOf::Skipped] += 1
else
LocalIdList.Add(RecRef.Field(RecRef.SystemIdNo()).Value());
until RecRef.Next() = 0;
if EnqueueOptionSyncJobToIntegrationTable(IntegrationTableMapping, LocalIdList, IntegrationTableMapping.Direction::ToIntegrationTable, false) then
RecordCounter[NoOf::Scheduled] += LocalIdList.Count()
else
RecordCounter[NoOf::Failed] += LocalIdList.Count();
SendCreateNewNotification(RecordCounter);
end;
procedure CreateNewOptionsInCRM(var LocalIdListDictionary: Dictionary of [Code[20], List of [Guid]])
var
IntegrationTableMapping: Record "Integration Table Mapping";
CRMOptionMapping: Record "CRM Option Mapping";
LocalRecordRef: RecordRef;
IntegrationTableMappingName: Code[20];
LocalId: Guid;
RecordCounter: array[4] of Integer;
IntegrationTableMappingNameList: List of [Code[20]];
LocalIdList: List of [Guid];
SynchronizeLocalIdList: List of [Guid];
begin
IntegrationTableMappingNameList := LocalIdListDictionary.Keys();
foreach IntegrationTableMappingName in IntegrationTableMappingNameList do begin
IntegrationTableMapping.Get(IntegrationTableMappingName);
LocalIdList := LocalIdListDictionary.Get(IntegrationTableMappingName);
foreach LocalId in LocalIdList do begin
RecordCounter[NoOf::Total] += 1;
LocalRecordRef.Open(IntegrationTableMapping."Table ID");
LocalRecordRef.GetBySystemId(LocalId);
CRMOptionMapping.SetRange("Record ID", LocalRecordRef.RecordId());
if not CRMOptionMapping.IsEmpty() then
RecordCounter[NoOf::Skipped] += 1
else
SynchronizeLocalIdList.Add(LocalId);
LocalRecordRef.Close();
end;
end;
if EnqueueOptionSyncJobToIntegrationTable(IntegrationTableMapping, SynchronizeLocalIdList, IntegrationTableMapping.Direction, false) then
RecordCounter[NoOf::Scheduled] += SynchronizeLocalIdList.Count()
else
RecordCounter[NoOf::Failed] += SynchronizeLocalIdList.Count();
SendCreateNewNotification(RecordCounter);
end;
[Scope('OnPrem')]
procedure CreateNewRecordsInCRM(var LocalIdListDictionary: Dictionary of [Code[20], List of [Guid]])
var
CRMIdListDictionary: Dictionary of [Code[20], List of [Guid]];
begin
CreateNewRecords(LocalIdListDictionary, CRMIdListDictionary);
end;
[Scope('Cloud')]
procedure CreateNewRecordsFromSelectedCRMOptions(RecVariant: Variant)
var
IntegrationTableMapping: Record "Integration Table Mapping";
CRMOptionMapping: Record "CRM Option Mapping";
RecRef: RecordRef;
RecordCounter: array[4] of Integer;
CRMOptionIds: List of [Integer];
CRMOptionId: Integer;
begin
if GetRecordRef(RecVariant, RecRef) = 0 then
exit;
GetIntegrationTableMappingFromCRMOption(IntegrationTableMapping, RecRef);
repeat
RecordCounter[NoOf::Total] += 1;
CRMOptionId := GetOptionIdFieldRef(RecRef).Value();
if CRMOptionMapping.FindRecordID(IntegrationTableMapping."Integration Table ID", IntegrationTableMapping."Integration Table UID Fld. No.", CRMOptionId) then
RecordCounter[NoOf::Skipped] += 1
else
CRMOptionIds.Add(CRMOptionId);
until RecRef.Next() = 0;
if EnqueueOptionSyncJobFromIntegrationTable(IntegrationTableMapping, CRMOptionIds, IntegrationTableMapping.Direction::FromIntegrationTable, false) then
RecordCounter[NoOf::Scheduled] += CRMOptionIds.Count()
else
RecordCounter[NoOf::Failed] += CRMOptionIds.Count();
SendCreateNewNotification(RecordCounter);
end;
procedure EnqueueOptionSyncJobFromIntegrationTable(IntegrationTableMapping: Record "Integration Table Mapping"; CRMOptionIds: List of [Integer]; Direction: Integer; SynchronizeOnlyCoupledRecords: Boolean): Boolean
var
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
begin
IntegrationTableMapping.Direction := Direction;
if Direction = IntegrationTableMapping.Direction::FromIntegrationTable then begin
IntegrationTableMapping.SetIntegrationTableFilter(GetTableViewForCRMOptionIds(IntegrationTableMapping."Integration Table ID", IntegrationTableMapping."Integration Table UID Fld. No.", CRMOptionIds));
AddIntegrationTableMapping(IntegrationTableMapping, SynchronizeOnlyCoupledRecords);
Commit();
exit(CRMSetupDefaults.CreateJobQueueEntry(IntegrationTableMapping));
end;
end;
procedure EnqueueOptionSyncJobToIntegrationTable(IntegrationTableMapping: Record "Integration Table Mapping"; LocalIdList: List of [Guid]; Direction: Integer; SynchronizeOnlyCoupledRecords: Boolean): Boolean
var
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
begin
IntegrationTableMapping.Direction := Direction;
if Direction = IntegrationTableMapping.Direction::ToIntegrationTable then begin
IntegrationTableMapping.SetTableFilter(IntegrationRecordSynch.GetTableViewForSystemIds(IntegrationTableMapping."Table ID", LocalIdList));
AddIntegrationTableMapping(IntegrationTableMapping, SynchronizeOnlyCoupledRecords);
Commit();
exit(CRMSetupDefaults.CreateJobQueueEntry(IntegrationTableMapping));
end;
end;
local procedure EnqueueOptionSyncJob(IntegrationTableMapping: Record "Integration Table Mapping"; RecordId: RecordId; CRMOptionId: Integer; Direction: Integer): Boolean
var
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
begin
IntegrationTableMapping.Direction := Direction;
IntegrationTableMapping.SetIntegrationTableFilter(GetTableViewForCRMOptionId(IntegrationTableMapping."Integration Table ID", IntegrationTableMapping."Integration Table UID Fld. No.", CRMOptionId));
IntegrationTableMapping.SetTableFilter(IntegrationRecordSynch.GetTableViewForRecordID(RecordId));
AddIntegrationTableMapping(IntegrationTableMapping);
Commit();
exit(CRMSetupDefaults.CreateJobQueueEntry(IntegrationTableMapping));
end;
procedure CreateNewRecordsFromCRM(RecVariant: Variant)
var
IntegrationTableMapping: Record "Integration Table Mapping";
RecordRef: RecordRef;
CRMIdListDictionary: Dictionary of [Code[20], List of [Guid]];
CRMIdList: List of [Guid];
CRMID: Guid;
begin
if GetRecordRef(RecVariant, RecordRef) = 0 then
exit;
GetIntegrationTableMappingFromCRMRecord(IntegrationTableMapping, RecordRef);
CRMIdListDictionary.Add(IntegrationTableMapping.Name, CRMIdList);
repeat
CRMID := RecordRef.Field(IntegrationTableMapping."Integration Table UID Fld. No.").Value();
CRMIdList.Add(CRMID);
until RecordRef.Next() = 0;
CreateNewRecordsFromCRM(CRMIdListDictionary);
end;
[Scope('OnPrem')]
procedure CreateNewRecordsFromCRM(var CRMIdListDictionary: Dictionary of [Code[20], List of [Guid]])
var
LocalIdListDictionary: Dictionary of [Code[20], List of [Guid]];
begin
CreateNewRecords(LocalIdListDictionary, CRMIdListDictionary);
end;
[Scope('OnPrem')]
procedure CreateNewRecords(var LocalIdListDictionary: Dictionary of [Code[20], List of [Guid]]; var CRMIdListDictionary: Dictionary of [Code[20], List of [Guid]])
var
IntegrationTableMapping: Record "Integration Table Mapping";
CRMIntegrationRecord: Record "CRM Integration Record";
CRMRecordRef: RecordRef;
RecordCounter: array[4] of Integer;
LocalId: Guid;
CRMId: Guid;
LocalIdCount: Integer;
CRMIdCount: Integer;
MappingDictionary: Dictionary of [Code[20], Boolean];
ToCRMMappingList: List of [Code[20]];
FromCRMMappingList: List of [Code[20]];
MappingList: List of [Code[20]];
LocalIdList: List of [Guid];
CRMIdList: List of [Guid];
MappingName: Code[20];
I: Integer;
J: Integer;
begin
ToCRMMappingList := LocalIdListDictionary.Keys();
FromCRMMappingList := CRMIdListDictionary.Keys();
MappingList.AddRange(ToCRMMappingList);
MappingList.AddRange(fromCRMMappingList);
foreach MappingName in MappingList do
if not MappingDictionary.ContainsKey(MappingName) then
MappingDictionary.Add(MappingName, true);
MappingList := MappingDictionary.Keys();
foreach MappingName in MappingList do begin
Clear(LocalIdList);
Clear(CRMIdList);
IntegrationTableMapping.Get(MappingName);
if ToCRMMappingList.Contains(MappingName) then begin
LocalIdList := LocalIdListDictionary.Get(MappingName);
LocalIdCount := LocalIdList.Count();
if LocalIdCount > 0 then begin
J := LocalIdCount + 1;
for I := 1 to LocalIdCount do begin
J -= 1;
LocalId := LocalIdList.Get(J);
RecordCounter[NoOf::Total] += 1;
CRMIntegrationRecord.SetCurrentKey("Integration ID");
CRMIntegrationRecord.SetFilter("Integration ID", LocalId);
if CRMIntegrationRecord.FindFirst() then begin
if CRMIntegrationRecord.GetCRMRecordRef(IntegrationTableMapping."Integration Table ID", CRMRecordRef) then begin
RecordCounter[NoOf::Skipped] += 1;
LocalIdList.RemoveAt(J);
end else
if not IsNullGuid(CRMIntegrationRecord."CRM ID") then // found the corrupt coupling
CRMIntegrationRecord.Delete();
CRMRecordRef.Close();
end;
end;
end;
end;
if FromCRMMappingList.Contains(MappingName) then begin
CRMIdList := CRMIdListDictionary.Get(MappingName);
CRMIdCount := CRMIdList.Count();
if CRMIdCount > 0 then begin
J := CRMIdCount + 1;
for I := 1 to CRMIdCount do begin
J -= 1;
CRMID := CRMIdList.Get(J);
RecordCounter[NoOf::Total] += 1;
if CRMIntegrationRecord.FindValidByCRMID(CRMID) then begin
RecordCounter[NoOf::Skipped] += 1;
CRMIdList.RemoveAt(J);
end else
if not IsNullGuid(CRMIntegrationRecord."CRM ID") then // found the corrupt coupling
CRMIntegrationRecord.Delete();
end;
end;
end;
EnqueueCreateNewJob(LocalIdList, CRMIdList, RecordCounter, IntegrationTableMapping);
end;
SendCreateNewNotification(RecordCounter);
end;
local procedure EnqueueCreateNewJob(var LocalIdList: List of [Guid]; CRMIdList: List of [Guid]; var RecordCounter: array[4] of Integer; var IntegrationTableMapping: Record "Integration Table Mapping")
var
LocalIdCount: Integer;
CRMIdCount: Integer;
Direction: Option;
begin
LocalIdCount := LocalIdList.Count();
CRMIdCount := CRMIdList.Count();
if (LocalIdCount > 0) or (CRMIdCount > 0) then begin
if CRMIdCount = 0 then
Direction := IntegrationTableMapping.Direction::ToIntegrationTable
else
if LocalIdCount = 0 then
Direction := IntegrationTableMapping.Direction::FromIntegrationTable
else
Direction := IntegrationTableMapping.Direction;
if EnqueueSyncJob(IntegrationTableMapping, LocalIdList, CRMIdList, Direction, false) then
RecordCounter[NoOf::Scheduled] += LocalIdCount + CRMIdCount
else
RecordCounter[NoOf::Failed] += LocalIdCount + CRMIdCount;
end;
end;
local procedure SendCreateNewNotification(var RecordCounter: array[4] of Integer)
begin
if RecordCounter[NoOf::Total] = RecordCounter[NoOf::Skipped] then begin
if RecordCounter[NoOf::Total] > 1 then
SendNotification(StrSubstNo(DetailedNotificationMessageTxt, SyncNowSkippedMsg, AllRecordsAlreadyCoupledTxt))
else
SendNotification(StrSubstNo(DetailedNotificationMessageTxt, SyncNowSkippedMsg, RecordAlreadyCoupledTxt));
exit;
end;
SendSyncNotification(RecordCounter);
end;
[Scope('OnPrem')]
procedure CreateNewRecordsFromSelectedCRMRecords(RecVariant: Variant)
begin
CreateNewRecordsFromCRM(RecVariant);
end;
local procedure PerformInitialSynchronization(RecordID: RecordID; CRMID: Guid; Direction: Option)
var
IntegrationTableMapping: Record "Integration Table Mapping";
RecordCounter: array[4] of Integer;
begin
RecordCounter[NoOf::Total] := 1;
GetIntegrationTableMapping(IntegrationTableMapping, RecordID);
if EnqueueSyncJob(IntegrationTableMapping, RecordID, CRMID, Direction) then
RecordCounter[NoOf::Scheduled] += 1
else
RecordCounter[NoOf::Failed] += 1;
SendSyncNotification(RecordCounter);
end;
local procedure PerformInitialOptionSynchronization(RecordId: RecordId; CRMOptionId: Integer; Direction: Option)
var
IntegrationTableMapping: Record "Integration Table Mapping";
RecordCounter: array[4] of Integer;
begin
RecordCounter[NoOf::Total] := 1;
GetIntegrationTableMapping(IntegrationTableMapping, RecordID.TableNo());
if EnqueueOptionSyncJob(IntegrationTableMapping, RecordId, CRMOptionId, Direction) then
RecordCounter[NoOf::Scheduled] += 1
else
RecordCounter[NoOf::Failed] += 1;
SendSyncNotification(RecordCounter);
end;
internal procedure MarkLocalDeletedAsSkipped()
var
IntegrationTableMapping: Record "Integration Table Mapping";
CRMIntegrationRecord: Record "CRM Integration Record";
RecordRef: RecordRef;
FieldRef: FieldRef;
TableFilter: Text;
Count: Integer;
TotalCount: Integer;
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetFilter(Direction, '<>%1', IntegrationTableMapping.Direction::Bidirectional);
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
IntegrationTableMapping.SetFilter("Table ID", '<>%1', Database::"Sales Header");
if not IntegrationTableMapping.FindSet() then
exit;
repeat
Count := 0;
TableFilter := IntegrationTableMapping.GetTableFilter();
CRMIntegrationRecord.SetRange("Table ID", IntegrationTableMapping."Table ID");
CRMIntegrationRecord.SetRange(Skipped, false);
if CRMIntegrationRecord.FindSet() then
repeat
RecordRef.Open(IntegrationTableMapping."Table ID");
RecordRef.SetView(TableFilter);
FieldRef := RecordRef.Field(RecordRef.SystemIdNo());
FieldRef.SetRange(CRMIntegrationRecord."Integration ID");
if RecordRef.IsEmpty() then begin
CRMIntegrationRecord.Skipped := true;
CRMIntegrationRecord.Modify();
Count += 1;
end;
RecordRef.Close();
until CRMIntegrationRecord.Next() = 0;
TotalCount += Count;
if Count > 0 then
Session.LogMessage('0000F26', StrSubstNo(BrokenCouplingsFoundAndMarkedAsSkippedForMappingTxt,
GetTableCaption(IntegrationTableMapping."Table ID"), GetTableCaption(IntegrationTableMapping."Integration Table ID"), IntegrationTableMapping.Direction, Count),
Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
until IntegrationTableMapping.Next() = 0;
if TotalCount > 0 then
Session.LogMessage('0000F27', StrSubstNo(BrokenCouplingsFoundAndMarkedAsSkippedTotalTxt, TotalCount), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok)
else
Session.LogMessage('0000F28', NoBrokenCouplingsFoundTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
procedure RepairBrokenCouplings()
begin
RepairBrokenCouplings(false);
end;
procedure RepairBrokenCouplings(UseLocalRecordsOnly: Boolean)
var
CRMIntegrationRecord: Record "CRM Integration Record";
BlankGuid: Guid;
begin
CRMIntegrationRecord.SetRange("Table ID", 0);
if CRMIntegrationRecord.IsEmpty() then
exit;
if CRMIntegrationRecord.FindSet() then
repeat
if CRMIntegrationRecord."Integration ID" <> BlankGuid then
if not CRMIntegrationRecord.RepairTableIdByLocalRecord() then
if not UseLocalRecordsOnly then
if not CRMIntegrationRecord.RepairTableIdByCRMRecord() then begin
CRMIntegrationRecord.Delete();
Session.LogMessage('0000DQD', StrSubstNo(DeletedRecordWithZeroTableIdTxt, CRMIntegrationRecord."Integration ID", CRMIntegrationRecord."CRM ID"), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
until CRMIntegrationRecord.Next() = 0;
end;
procedure RemoveCoupling(TableID: Integer; CRMTableID: Integer)
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
begin
if GetIntegrationTableMappingForUncoupling(IntegrationTableMapping, TableID, CRMTableID) then
ScheduleUncoupling(IntegrationTableMapping, '', '')
else begin
RepairBrokenCouplings();
CRMIntegrationRecord.SetRange("Table ID", TableID);
CRMIntegrationRecord.DeleteAll();
end;
end;
procedure MatchBasedCoupling(TableID: Integer): Boolean
begin
exit(MatchBasedCoupling(TableID, false, false, false));
end;
procedure MatchBasedCoupling(TableID: Integer; SkipSettingCriteria: Boolean; IsFullSync: Boolean; InForeground: Boolean): Boolean
var
IntegrationTableMapping: Record "Integration Table Mapping";
IntegrationFieldMapping: Record "Integration Field Mapping";
ScheduleJob: Boolean;
begin
if IsCRMTable(TableID) then begin
Session.LogMessage('0000EZO', StrSubstNo(NotLocalTableTxt, GetTableCaption(TableID)), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
exit(false);
end;
if GetIntegrationTableMappingForCoupling(IntegrationTableMapping, TableID) then begin
if SkipSettingCriteria then
ScheduleJob := true;
if not ScheduleJob then begin
IntegrationFieldMapping.SetMatchBasedCouplingFilters(IntegrationTableMapping);
ScheduleJob := (Page.RunModal(Page::"Match Based Coupling Criteria", IntegrationFieldMapping) = Action::LookupOK);
end;
if not ScheduleJob then
exit(false);
if InForeground then
exit(PerformCoupling(IntegrationTableMapping, '', IsFullSync))
else
exit(ScheduleCoupling(IntegrationTableMapping, '', IsFullSync));
end;
exit(false);
end;
procedure RemoveCoupling(var LocalRecordRef: RecordRef)
begin
RemoveCoupling(LocalRecordRef, true);
end;
procedure MatchBasedCoupling(var LocalRecordRef: RecordRef)
var
CouplingOption: Option None,Background,Foreground;
begin
MatchBasedCoupling(LocalRecordRef, CouplingOption::Background);
end;
procedure RemoveCoupling(LocalTableID: Integer; var LocalIdList: List of [Guid])
begin
RemoveCoupling(LocalTableID, LocalIdList, true);
end;
procedure RemoveCoupling(LocalTableID: Integer; var LocalIdList: List of [Guid]; Schedule: Boolean)
var
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
LocalRecordRef: RecordRef;
LocalIdFilter: Text;
begin
if LocalIdList.Count() = 0 then
exit;
LocalIdFilter := IntegrationRecordSynch.JoinIDs(LocalIdList, '|');
LocalRecordRef.Open(LocalTableId);
LocalRecordRef.Field(LocalRecordRef.SystemIdNo()).SetFilter(LocalIdFilter);
RemoveCoupling(LocalRecordRef, Schedule);
end;
procedure RemoveCoupling(LocalTableID: Integer; IntegrationTableID: Integer; var IntegrationIdList: List of [Guid])
begin
RemoveCoupling(LocalTableID, IntegrationTableID, IntegrationIdList, true);
end;
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;
OnRemoveCouplingOnBeforeGetIntegrationTableMappingForUncoupling(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;
procedure RemoveOptionMapping(var RecRef: RecordRef)
var
CRMOptionMapping: Record "CRM Option Mapping";
begin
if not RecRef.FindSet() then
exit;
repeat
CRMOptionMapping.SetRange("Record ID", RecRef.RecordId());
if CRMOptionMapping.FindFirst() then
CRMOptionMapping.Delete();
until RecRef.Next() = 0;
end;
local procedure RemoveOptionMapping(IntegrationTableId: Integer; IntegrationFieldId: Integer; OptionId: Integer): Boolean
var
CRMOptionMapping: Record "CRM Option Mapping";
begin
CRMOptionMapping.SetRange("Integration Table ID", IntegrationTableId);
CRMOptionMapping.SetRange("Integration Field ID", IntegrationFieldId);
CRMOptionMapping.SetRange("Option Value", OptionId);
if CRMOptionMapping.FindFirst() then
exit(CRMOptionMapping.Delete());
end;
local procedure RemoveOptionMappingFromRecRef(RecRef: RecordRef): Boolean
var
CRMOptionMapping: Record "CRM Option Mapping";
begin
CRMOptionMapping.SetRange("Record ID", RecRef.RecordId());
if not CRMOptionMapping.FindFirst() then
exit(false);
exit(CRMOptionMapping.Delete());
end;
procedure CreateOptionMapping(RecordID: RecordID; CRMOptionId: Integer; CRMOptionValue: Text[250])
var
CRMOptionMapping: Record "CRM Option Mapping";
begin
CRMOptionMapping.SetRange("Record ID", RecordId);
if not CRMOptionMapping.FindFirst() then begin
CRMOptionMapping.AssertCRMOptionIdCanBeMapped(RecordID, CRMOptionId);
CRMOptionMapping.InsertRecord(RecordID, CRMOptionId, CRMOptionValue);
end else
if CRMOptionMapping."Option Value" <> CRMOptionId then begin
CRMOptionMapping.AssertCRMOptionIdCanBeMapped(RecordID, CRMOptionId);
CRMOptionMapping."Option Value" := CRMOptionId;
CRMOptionMapping."Option Value Caption" := CRMOptionValue;
CRMOptionMapping.Modify(true);
end;
end;
procedure GetMappedCRMOptionId(RecordID: RecordID; var CRMOptionId: Integer): Boolean
var
CRMOptionMapping: Record "CRM Option Mapping";
Found: Boolean;
begin
CRMOptionMapping.SetRange("Record ID", RecordId);
if CRMOptionMapping.FindFirst() then begin
CRMOptionId := CRMOptionMapping."Option Value";
Found := true;
end;
exit(Found);
end;
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;
OnMatchBasedCouplingOnBeforeGetIntegrationTableMappingForMatchBasedCoupling(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;
internal procedure RemoveCoupling(LocalTableID: Integer; IntegrationTableID: Integer; var IntegrationIdList: List of [Guid]; Schedule: Boolean)
var
IntegrationTableMapping: Record "Integration Table Mapping";
begin
if IntegrationIdList.Count() = 0 then
exit;
if GetIntegrationTableMappingForUncoupling(IntegrationTableMapping, LocalTableID) then begin
if Schedule then
ScheduleUncoupling(IntegrationTableMapping, '', GetTableViewForCRMIDs(IntegrationTableID, IntegrationTableMapping."Integration Table UID Fld. No.", IntegrationIdList))
else
PerformUncoupling(IntegrationTableMapping, '', GetTableViewForCRMIDs(IntegrationTableID, IntegrationTableMapping."Integration Table UID Fld. No.", IntegrationIdList))
end else
RemoveCouplingToRecord(LocalTableID, IntegrationIdList);
end;
local procedure RemoveCouplingToRecord(var LocalRecordRef: RecordRef)
var
CRMIntegrationRecord: Record "CRM Integration Record";
begin
if LocalRecordRef.FindSet() then
repeat
CRMIntegrationRecord.RemoveCouplingToRecord(LocalRecordRef.RecordId());
until LocalRecordRef.Next() = 0;
end;
local procedure RemoveCouplingToRecord(LocalTableID: Integer; var CRMIDList: List of [Guid])
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMID: Guid;
begin
foreach CRMID in CRMIDList do
CRMIntegrationRecord.RemoveCouplingToCRMID(CRMID, LocalTableID);
end;
procedure RemoveCoupling(RecordID: RecordID)
begin
RemoveCoupling(RecordID, true);
end;
internal procedure RemoveCoupling(RecordID: RecordID; Schedule: Boolean): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
begin
if IsCRMTable(RecordID.TableNo()) then begin
Session.LogMessage('0000DHV', StrSubstNo(NotLocalTableTxt, GetTableCaption(RecordID.TableNo())), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
exit(false);
end;
if not GetIntegrationTableMappingForUncoupling(IntegrationTableMapping, RecordID.TableNo()) then
exit(CRMIntegrationRecord.RemoveCouplingToRecord(RecordID));
if Schedule then
exit(ScheduleUncoupling(IntegrationTableMapping, IntegrationRecordSynch.GetTableViewForRecordID(RecordID), ''));
exit(PerformUncoupling(IntegrationTableMapping, IntegrationRecordSynch.GetTableViewForRecordID(RecordID), ''));
end;
procedure RemoveCoupling(TableID: Integer; CRMTableID: Integer; CRMID: Guid)
begin
RemoveCoupling(TableID, CRMTableID, CRMID, true);
end;
internal procedure RemoveCoupling(TableID: Integer; CRMTableID: Integer; CRMID: Guid; Schedule: Boolean): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
begin
if not GetIntegrationTableMappingForUncoupling(IntegrationTableMapping, TableID, CRMTableID) then
exit(CRMIntegrationRecord.RemoveCouplingToCRMID(CRMID, TableID));
if Schedule then
exit(ScheduleUncoupling(IntegrationTableMapping, '', GetTableViewForGuid(CRMTableID, IntegrationTableMapping."Integration Table UID Fld. No.", CRMID)));
exit(PerformUncoupling(IntegrationTableMapping, '', GetTableViewForGuid(CRMTableID, IntegrationTableMapping."Integration Table UID Fld. No.", CRMID)));
end;
local procedure ScheduleUncoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; LocalTableFilter: Text; IntegrationTableFilter: Text): Boolean
var
RecordCounter: array[4] of Integer;
Scheduled: Boolean;
begin
RecordCounter[NoOf::Total] := 1;
Scheduled := EnqueueUncoupleJob(IntegrationTableMapping, LocalTableFilter, IntegrationTableFilter);
if Scheduled then
RecordCounter[NoOf::Scheduled] += 1
else
RecordCounter[NoOf::Failed] += 1;
SendUncoupleNotification(RecordCounter);
exit(Scheduled);
end;
local procedure ScheduleCoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; LocalTableFilter: Text; IsFullSync: Boolean): Boolean
var
RecordCounter: array[4] of Integer;
Scheduled: Boolean;
begin
IntegrationTableMapping.Find();
RecordCounter[NoOf::Total] := 1;
Scheduled := EnqueueCouplingJob(IntegrationTableMapping, LocalTableFilter, IsFullSync);
if Scheduled then
RecordCounter[NoOf::Scheduled] += 1
else
RecordCounter[NoOf::Failed] += 1;
SendCouplingNotification(RecordCounter);
exit(Scheduled);
end;
local procedure PerformCoupling(IntegrationTableMapping: Record "Integration Table Mapping"; LocalTableFilter: Text; IsFullSync: Boolean): Boolean
var
CDSIntTableCouple: Codeunit "CDS Int. Table Couple";
begin
IntegrationTableMapping.Find();
if LocalTableFilter <> '' then
IntegrationTableMapping.SetTableFilter(LocalTableFilter)
else
IntegrationTableMapping.CalcFields("Table Filter");
IntegrationTableMapping.CalcFields("Integration Table Filter");
IntegrationTableMapping."Full Sync is Running" := IsFullSync;
AddIntegrationTableMapping(IntegrationTableMapping, true);
CDSIntTableCouple.PerformScheduledCoupling(IntegrationTableMapping);
IntegrationTableMapping.Delete(true);
end;
local procedure PerformUncoupling(IntegrationTableMapping: Record "Integration Table Mapping"; LocalTableFilter: Text; IntegrationTableFilter: Text): Boolean
var
LocalRecordRef: RecordRef;
IntegrationRecordRef: RecordRef;
CountFailed: Integer;
begin
AddIntegrationTableMapping(IntegrationTableMapping);
IntegrationTableMapping.SetTableFilter(LocalTableFilter);
IntegrationTableMapping.SetIntegrationTableFilter(IntegrationTableFilter);
if LocalTableFilter <> '' then begin
LocalRecordRef.Open(IntegrationTableMapping."Table ID");
LocalRecordRef.SetView(LocalTableFilter);
if LocalRecordRef.FindSet() then
repeat
if not PerformUncoupling(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef) then
CountFailed += 1;
until LocalRecordRef.Next() = 0
end else begin
IntegrationRecordRef.Open(IntegrationTableMapping."Integration Table ID");
IntegrationRecordRef.SetView(IntegrationTableFilter);
if IntegrationRecordRef.FindSet() then
repeat
if not PerformUncoupling(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef) then
CountFailed += 1;
until IntegrationRecordRef.Next() = 0;
end;
IntegrationTableMapping.Delete(true);
exit(CountFailed = 0);
end;
local procedure PerformUncoupling(IntegrationTableMapping: Record "Integration Table Mapping"; LocalRecordRef: RecordRef; IntegrationRecordRef: RecordRef): Boolean
var
IntRecUncoupleInvoke: Codeunit "Int. Rec. Uncouple Invoke";
SynchAction: Option "None",Insert,Modify,ForceModify,IgnoreUnchanged,Fail,Skip,Delete,Uncouple,Couple;
LocalRecordModified: Boolean;
IntegrationRecordModified: Boolean;
JobId: Guid;
begin
SynchAction := SynchAction::Uncouple;
IntRecUncoupleInvoke.SetContext(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef, SynchAction, LocalRecordModified, IntegrationRecordModified, JobId, TableConnectionType::CRM);
IntRecUncoupleInvoke.Run();
IntRecUncoupleInvoke.GetContext(IntegrationTableMapping, LocalRecordRef, IntegrationRecordRef, SynchAction, LocalRecordModified, IntegrationRecordModified);
exit(SynchAction <> SynchAction::Fail);
end;
local procedure GetIntegrationTableMappingForUncoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; TableID: Integer; CRMTableID: Integer): Boolean
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Uncouple Codeunit ID", Codeunit::"CDS Int. Table Uncouple");
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
IntegrationTableMapping.SetRange("Table ID", TableID);
IntegrationTableMapping.SetRange("Integration Table ID", CRMTableID);
exit(IntegrationTableMapping.FindFirst());
end;
local procedure GetIntegrationTableMappingForUncoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; TableID: Integer): Boolean
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Uncouple Codeunit ID", Codeunit::"CDS Int. Table Uncouple");
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
IntegrationTableMapping.SetRange("Table ID", TableID);
exit(IntegrationTableMapping.FindFirst());
end;
local procedure GetIntegrationTableMappingForCoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; TableID: Integer): Boolean
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetFilter("Coupling Codeunit ID", '%1|%2', Codeunit::"CDS Int. Table Couple", Codeunit::"CDS Int. Option Couple");
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
IntegrationTableMapping.SetRange("Table ID", TableID);
exit(IntegrationTableMapping.FindFirst());
end;
procedure GetIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; RecId: RecordId)
var
TableId: Integer;
begin
TableId := RecId.TableNo();
OnBeforeGetIntegrationTableMappingWithRecordId(IntegrationTableMapping, RecId, TableId);
GetIntegrationTableMapping(IntegrationTableMapping, TableId);
end;
procedure GetIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; TableID: Integer)
begin
OnBeforeGetIntegrationTableMapping(IntegrationTableMapping, TableId);
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Synch. Codeunit ID", CODEUNIT::"CRM Integration Table Synch.");
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
if IsCRMTable(TableID) then
IntegrationTableMapping.SetRange("Integration Table ID", TableID)
else
IntegrationTableMapping.SetRange("Table ID", TableID);
if not IntegrationTableMapping.FindFirst() then
Error(IntegrationTableMappingNotFoundErr, IntegrationTableMapping.TableCaption(), GetTableCaption(TableID));
end;
local procedure GetIntegrationTableMappingFromCRMRecord(var IntegrationTableMapping: Record "Integration Table Mapping"; RecRef: RecordRef)
var
CRMAccount: Record "CRM Account";
CustomerTypeCodeFieldRef: FieldRef;
CustomerTypeCode: Text;
begin
OnBeforeGetIntegrationTableMappingFromCRMRecord(IntegrationTableMapping, RecRef);
if RecRef.Number <> Database::"CRM Account" then begin
GetIntegrationTableMapping(IntegrationTableMapping, RecRef.RecordId());
exit;
end;
CustomerTypeCodeFieldRef := RecRef.Field(CRMAccount.FieldNo(CustomerTypeCode));
Evaluate(CustomerTypeCode, Format(CustomerTypeCodeFieldRef.Value));
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Synch. Codeunit ID", CODEUNIT::"CRM Integration Table Synch.");
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
IntegrationTableMapping.SetRange("Integration Table ID", RecRef.Number);
if CustomerTypeCode = Format(CRMAccount.CustomerTypeCode::Customer) then
IntegrationTableMapping.SetRange("Table ID", Database::Customer)
else
if CustomerTypeCode = Format(CRMAccount.CustomerTypeCode::Vendor) then
IntegrationTableMapping.SetRange("Table ID", Database::Vendor);
OnAfterGetIntegrationTableMappingFromCRMRecordBeforeFindRecord(IntegrationTableMapping, RecRef);
if not IntegrationTableMapping.FindFirst() then
Error(IntegrationTableMappingNotFoundErr, IntegrationTableMapping.TableCaption(), GetTableCaption(RecRef.Number));
end;
local procedure GetIntegrationTableMappingFromCRMOption(var IntegrationTableMapping: Record "Integration Table Mapping"; RecRef: RecordRef)
var
TableId: Integer;
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Synch. Codeunit ID", Codeunit::"CRM Integration Table Synch.");
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
case RecRef.Number of
Database::"CRM Payment Terms":
TableId := Database::"Payment Terms";
Database::"CRM Freight Terms":
TableId := Database::"Shipment Method";
Database::"CRM Shipping Method":
TableId := Database::"Shipping Agent";
end;
OnGetTableIdFromCRMOption(RecRef, TableId);
IntegrationTableMapping.SetRange("Table ID", TableId);
if not IntegrationTableMapping.FindFirst() then
Error(IntegrationTableMappingNotFoundErr, IntegrationTableMapping.TableCaption(), GetTableCaption(TableID));
end;
local procedure GetTableCaption(TableID: Integer): Text
var
TableMetadata: Record "Table Metadata";
begin
if TableMetadata.Get(TableID) then
exit(TableMetadata.Caption);
exit('');
end;
procedure IsCRMTable(TableID: Integer): Boolean
var
TableMetadata: Record "Table Metadata";
CRMTable: Boolean;
Handled: Boolean;
begin
OnIsCRMTable(TableID, CRMTable, Handled);
if Handled then
exit(CRMTable);
if TableID in [Database::"CRM Payment Terms", Database::"CRM Freight Terms", Database::"CRM Shipping Method"] then
exit(true);
if TableMetadata.Get(TableID) then
exit(TableMetadata.TableType = TableMetadata.TableType::CRM);
end;
local procedure IsRecordSkipped(RecID: RecordID): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
begin
if CRMIntegrationRecord.FindByRecordID(RecID) then
exit(CRMIntegrationRecord.Skipped);
end;
local procedure JoinIntegers(var IdList: List of [Integer]; Delimiter: Text[1]): Text
var
IdValue: Integer;
IdFilter: Text;
begin
foreach IdValue in IdList do
IdFilter += Delimiter + Format(IdValue);
IdFilter := IdFilter.TrimStart(Delimiter);
exit(IdFilter);
end;
procedure EnqueueFullSyncJob(Name: Code[20]): Guid
var
IntegrationTableMapping: Record "Integration Table Mapping";
JobQueueEntry: Record "Job Queue Entry";
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
begin
IntegrationTableMapping.Get(Name);
IntegrationTableMapping."Full Sync is Running" := true;
IntegrationTableMapping.CalcFields("Table Filter", "Integration Table Filter");
AddIntegrationTableMapping(IntegrationTableMapping);
Commit();
if CRMSetupDefaults.CreateJobQueueEntry(IntegrationTableMapping) then begin
JobQueueEntry.SetRange("Record ID to Process", IntegrationTableMapping.RecordId());
if JobQueueEntry.FindFirst() then
exit(JobQueueEntry.ID);
end;
end;
procedure EnqueueSyncJob(IntegrationTableMapping: Record "Integration Table Mapping"; RecordID: RecordID; CRMID: Guid; Direction: Integer): Boolean
var
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
begin
IntegrationTableMapping.Direction := Direction;
if Direction in [IntegrationTableMapping.Direction::Bidirectional, IntegrationTableMapping.Direction::FromIntegrationTable] then
IntegrationTableMapping.SetIntegrationTableFilter(GetTableViewForGuid(IntegrationTableMapping."Integration Table ID", IntegrationTableMapping."Integration Table UID Fld. No.", CRMID));
if Direction in [IntegrationTableMapping.Direction::Bidirectional, IntegrationTableMapping.Direction::ToIntegrationTable] then
IntegrationTableMapping.SetTableFilter(IntegrationRecordSynch.GetTableViewForRecordIDAndFlowFilters(IntegrationTableMapping, RecordID));
AddIntegrationTableMapping(IntegrationTableMapping);
Commit();
exit(CRMSetupDefaults.CreateJobQueueEntry(IntegrationTableMapping));
end;
procedure EnqueueSyncJob(IntegrationTableMapping: Record "Integration Table Mapping"; SystemIds: List of [Guid]; CRMIDs: List of [Guid]; Direction: Integer; SynchronizeOnlyCoupledRecords: Boolean): Boolean
var
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
begin
IntegrationTableMapping.Direction := Direction;
if Direction in [IntegrationTableMapping.Direction::Bidirectional, IntegrationTableMapping.Direction::FromIntegrationTable] then
IntegrationTableMapping.SetIntegrationTableFilter(GetTableViewForCRMIds(IntegrationTableMapping."Integration Table ID", IntegrationTableMapping."Integration Table UID Fld. No.", CRMIDs));
if Direction in [IntegrationTableMapping.Direction::Bidirectional, IntegrationTableMapping.Direction::ToIntegrationTable] then
IntegrationTableMapping.SetTableFilter(IntegrationRecordSynch.GetTableViewForSystemIds(IntegrationTableMapping."Table ID", SystemIds));
AddIntegrationTableMapping(IntegrationTableMapping, SynchronizeOnlyCoupledRecords);
Commit();
exit(CRMSetupDefaults.CreateJobQueueEntry(IntegrationTableMapping));
end;
local procedure EnqueueUncoupleJob(IntegrationTableMapping: Record "Integration Table Mapping"; LocalTableFilter: Text; IntegrationTableFilter: Text): Boolean
var
CDSSetupDefaults: Codeunit "CDS Setup Defaults";
begin
IntegrationTableMapping.SetTableFilter(LocalTableFilter);
IntegrationTableMapping.SetIntegrationTableFilter(IntegrationTableFilter);
AddIntegrationTableMapping(IntegrationTableMapping);
Commit();
exit(CDSSetupDefaults.CreateUncoupleJobQueueEntry(IntegrationTableMapping));
end;
local procedure EnqueueCouplingJob(IntegrationTableMapping: Record "Integration Table Mapping"; LocalTableFilter: Text; IsFullSync: Boolean): Boolean
var
CDSSetupDefaults: Codeunit "CDS Setup Defaults";
begin
if LocalTableFilter <> '' then
IntegrationTableMapping.SetTableFilter(LocalTableFilter)
else
IntegrationTableMapping.CalcFields("Table Filter");
IntegrationTableMapping.CalcFields("Integration Table Filter");
IntegrationTableMapping."Full Sync is Running" := IsFullSync;
AddIntegrationTableMapping(IntegrationTableMapping, true);
Commit();
exit(CDSSetupDefaults.CreateCoupleJobQueueEntry(IntegrationTableMapping));
end;
procedure AddIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping")
begin
AddIntegrationTableMapping(IntegrationTableMapping, false);
end;
procedure AddIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; SynchOnlyCoupledRecords: Boolean)
var
SourceIntegrationTableMapping: Record "Integration Table Mapping";
SourceMappingName: Code[20];
begin
SourceMappingName := IntegrationTableMapping.GetName();
IntegrationTableMapping.Name := CopyStr(DelChr(Format(CreateGuid()), '=', '{}-'), 1, MaxStrLen(IntegrationTableMapping.Name));
IntegrationTableMapping."Synch. Only Coupled Records" := SynchOnlyCoupledRecords;
IntegrationTableMapping."Delete After Synchronization" := true;
IntegrationTableMapping."Parent Name" := SourceMappingName;
SourceIntegrationTableMapping.Get(IntegrationTableMapping."Parent Name");
IntegrationTableMapping."Update-Conflict Resolution" := SourceIntegrationTableMapping."Update-Conflict Resolution";
IntegrationTableMapping."Deletion-Conflict Resolution" := SourceIntegrationTableMapping."Deletion-Conflict Resolution";
Clear(IntegrationTableMapping."Synch. Modified On Filter");
Clear(IntegrationTableMapping."Synch. Int. Tbl. Mod. On Fltr.");
Clear(IntegrationTableMapping."Last Full Sync Start DateTime");
IntegrationTableMapping.Insert();
CloneIntegrationFieldMapping(SourceMappingName, IntegrationTableMapping.Name);
end;
procedure CloneIntegrationFieldMapping(SourceMappingName: Code[20]; DestinationMappingName: Code[20])
var
IntegrationFieldMapping: Record "Integration Field Mapping";
NewIntegrationFieldMapping: Record "Integration Field Mapping";
begin
IntegrationFieldMapping.SetRange("Integration Table Mapping Name", SourceMappingName);
if IntegrationFieldMapping.FindSet() then
repeat
NewIntegrationFieldMapping := IntegrationFieldMapping;
NewIntegrationFieldMapping."No." := 0; // Autoincrement
NewIntegrationFieldMapping."Integration Table Mapping Name" := DestinationMappingName;
NewIntegrationFieldMapping.Insert();
until IntegrationFieldMapping.Next() = 0;
end;
local procedure GetTableViewForGuid(TableNo: Integer; IdFiledNo: Integer; CRMId: Guid) View: Text
var
RecordRef: RecordRef;
FieldRef: FieldRef;
begin
RecordRef.Open(TableNo);
FieldRef := RecordRef.Field(IdFiledNo);
FieldRef.SetRange(CRMId);
View := RecordRef.GetView();
RecordRef.Close();
end;
local procedure GetTableViewForCRMIDs(TableNo: Integer; IdFiledNo: Integer; CRMIds: List of [Guid]) View: Text
var
IntegrationRecordSynch: Codeunit "Integration Record Synch.";
RecordRef: RecordRef;
FieldRef: FieldRef;
CRMIdFilter: Text;
begin
CRMIdFilter := IntegrationRecordSynch.JoinIDs(CRMIds, '|');
RecordRef.Open(TableNo);
FieldRef := RecordRef.Field(IdFiledNo);
FieldRef.SetFilter(CRMIdFilter);
View := RecordRef.GetView();
RecordRef.Close();
end;
local procedure GetTableViewForCRMOptionIds(TableNo: Integer; OptionIdFieldNo: Integer; CRMOptionIds: List of [Integer]) View: Text
var
RecordRef: RecordRef;
FieldRef: FieldRef;
CRMOptionIdFilter: Text;
begin
CRMOptionIdFilter := JoinIntegers(CRMOptionIds, '|');
RecordRef.Open(TableNo);
FieldRef := RecordRef.Field(OptionIdFieldNo);
FieldRef.SetFilter(CRMOptionIdFilter);
View := RecordRef.GetView(false);
RecordRef.Close();
end;
local procedure GetTableViewForCRMOptionId(TableNo: Integer; OptionIdFieldNo: Integer; CRMOptionId: Integer) View: Text
var
RecordRef: RecordRef;
FieldRef: FieldRef;
begin
RecordRef.Open(TableNo);
FieldRef := RecordRef.Field(OptionIdFieldNo);
FieldRef.SetRange(CRMOptionId);
View := RecordRef.GetView(false);
RecordRef.Close();
end;
procedure CreateOrUpdateCRMAccountStatistics(Customer: Record Customer)
var
CRMAccount: Record "CRM Account";
CRMStatisticsJob: Codeunit "CRM Statistics Job";
CRMID: Guid;
begin
if not GetCoupledCRMID(Customer.RecordId, CRMID) then
exit;
CRMAccount.Get(CRMID);
CRMStatisticsJob.CreateOrUpdateCRMAccountStatistics(Customer, CRMAccount);
CRMStatisticsJob.UpdateStatusOfPaidInvoices(Customer."No.");
Message(StrSubstNo(AccountStatisticsUpdatedMsg, CRMProductName.CDSServiceName()));
end;
procedure ShowCRMEntityFromRecordID(RecordID: RecordID)
var
CRMID: Guid;
begin
if not DefineCouplingIfNotCoupled(RecordID, CRMID) then
exit;
HyperLink(GetCRMEntityUrlFromRecordID(RecordID));
end;
procedure GetCRMEntityUrlFromRecordID(TargetRecordID: RecordID): Text
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
CRMIntegrationManagement: Codeunit "CRM Integration Management";
CRMId: Guid;
begin
if not CRMIntegrationRecord.FindIDFromRecordID(TargetRecordID, CRMId) then
Error(CouplingNotFoundErr, CRMProductName.CDSServiceName());
CRMIntegrationManagement.GetIntegrationTableMapping(IntegrationTableMapping, TargetRecordID);
exit(GetCRMEntityUrlFromCRMID(IntegrationTableMapping."Table ID", IntegrationTableMapping."Integration Table ID", CRMId));
end;
procedure GetCRMEntityUrlFromCRMID(TableId: Integer; CRMId: Guid): Text
var
CRMTableId: Integer;
begin
if IsCRMTable(TableId) then begin
CRMTableId := TableId;
TableId := 0;
end;
exit(GetCRMEntityUrlFromCRMID(TableId, CRMTableId, CRMId));
end;
procedure GetCRMEntityUrlFromCRMID(TableId: Integer; CRMTableId: Integer; CRMId: Guid): Text
var
CRMConnectionSetup: Record "CRM Connection Setup";
CRMEntityUrl: Text;
CDSServerAddress: Text;
IsHandled: Boolean;
begin
OnGetCDSServerAddress(CDSServerAddress, IsHandled);
if IsHandled then
CRMEntityUrl := StrSubstNo(CRMEntityUrlTemplateTxt, CDSServerAddress, GetCRMEntityTypeName(TableId, CRMTableId), CRMId)
else begin
CRMConnectionSetup.Get();
CRMEntityUrl := StrSubstNo(CRMEntityUrlTemplateTxt, CRMConnectionSetup."Server Address", GetCRMEntityTypeName(TableId, CRMTableId), CRMId);
if CRMConnectionSetup."Use Newest UI" and (CRMConnectionSetup."Newest UI AppModuleId" <> '') then
CRMEntityUrl += StrSubstNo(NewestUIAppIdParameterTxt, CRMConnectionSetup."Newest UI AppModuleId")
end;
OnAfterGetCRMEntityUrlFromCRMID(CRMEntityUrlTemplateTxt, NewestUIAppIdParameterTxt, TableId, CRMId, CRMEntityUrl, CRMTableId);
exit(CRMEntityUrl);
end;
procedure OpenCoupledNavRecordPage(CRMID: Guid; CRMEntityTypeName: Text): Boolean
var
IntegrationTableMapping: Record "Integration Table Mapping";
TempNameValueBuffer: Record "Name/Value Buffer" temporary;
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
RecordId: RecordId;
BCTableId: Integer;
IsHandled, Result : Boolean;
begin
IsHandled := false;
OnBeforeOpenCoupledNavRecordPage(CRMID, CRMEntityTypeName, Result, IsHandled);
if IsHandled then
exit(Result);
// Find the corresponding NAV record and type
CRMSetupDefaults.GetTableIDCRMEntityNameMapping(TempNameValueBuffer);
TempNameValueBuffer.SetCurrentKey(Name);
TempNameValueBuffer.SetRange(Name, LowerCase(CRMEntityTypeName));
if TempNameValueBuffer.IsEmpty() then
exit(false);
FindRecordFromNameValueBuffer(TempNameValueBuffer, CRMID, RecordId, BCTableId, CRMEntityTypeName);
if RecordId.TableNo = 0 then begin
GetIntegrationTableMappingFromCRMID(IntegrationTableMapping, BcTableId, CRMID);
if (IntegrationTableMapping.Direction in [IntegrationTableMapping.Direction::Bidirectional, IntegrationTableMapping.Direction::FromIntegrationTable])
and (IntegrationTableMapping."Synch. Only Coupled Records" = false) then
SynchFromIntegrationTable(IntegrationTableMapping, CRMID)
else
exit(false);
end;
FindRecordFromNameValueBuffer(TempNameValueBuffer, CRMID, RecordId, BCTableId, CRMEntityTypeName);
if RecordId.TableNo = 0 then
exit(false);
OpenRecordCardPage(RecordID);
exit(true);
end;
local procedure FindRecordFromNameValueBuffer(var TempNameValueBuffer: Record "Name/Value Buffer" temporary; CRMID: Guid; var RecordId: RecordId; var BCTableId: Integer; CRMEntityTypeName: Text)
var
CRMIntegrationRecord: Record "CRM Integration Record";
TableId: Integer;
begin
TempNameValueBuffer.SetCurrentKey(Name);
TempNameValueBuffer.SetRange(Name, LowerCase(CRMEntityTypeName));
if TempNameValueBuffer.FindSet() then
repeat
Evaluate(TableId, TempNameValueBuffer.Value);
if not IsCRMTable(TableId) then
BCTableId := TableId;
if CRMIntegrationRecord.FindRecordIDFromID(CRMID, TableId, RecordId) then
break;
until TempNameValueBuffer.Next() = 0;
end;
local procedure SynchFromIntegrationTable(IntegrationTableMapping: Record "Integration Table Mapping"; CRMID: Guid)
var
CRMRecordRef: RecordRef;
CRMFieldRef: FieldRef;
CRMTableView: Text;
begin
CRMRecordRef.Open(IntegrationTableMapping."Integration Table ID");
CRMRecordRef.SetView(IntegrationTableMapping.GetIntegrationTableFilter());
CRMFieldRef := CRMRecordRef.Field(IntegrationTableMapping."Integration Table UID Fld. No.");
CRMFieldRef.SetRange(CRMID);
CRMTableView := CRMRecordRef.GetView();
CRMRecordRef.Close();
IntegrationTableMapping.SetIntegrationTableFilter(CRMTableView);
IntegrationTableMapping.Direction := IntegrationTableMapping.Direction::FromIntegrationTable;
AddIntegrationTableMapping(IntegrationTableMapping);
Commit();
Codeunit.Run(IntegrationTableMapping."Synch. Codeunit ID", IntegrationTableMapping);
IntegrationTableMapping.Delete(true);
Commit();
end;
local procedure OpenRecordCardPage(RecordID: RecordID)
var
Customer: Record Customer;
Vendor: Record Vendor;
Contact: Record Contact;
Currency: Record Currency;
SalespersonPurchaser: Record "Salesperson/Purchaser";
UnitOfMeasure: Record "Unit of Measure";
Item: Record Item;
Resource: Record Resource;
SalesInvoiceHeader: Record "Sales Invoice Header";
CustomerPriceGroup: Record "Customer Price Group";
PageManagement: Codeunit "Page Management";
RecordRef: RecordRef;
IsHandled: Boolean;
begin
// Open the right kind of card page
OnBeforeOpenRecordCardPage(RecordID, IsHandled);
if IsHandled then
exit;
RecordRef := RecordID.GetRecord();
case RecordID.TableNo of
DATABASE::Contact:
begin
RecordRef.SetTable(Contact);
PAGE.Run(PAGE::"Contact Card", Contact);
end;
DATABASE::Currency:
begin
RecordRef.SetTable(Currency);
PAGE.Run(PAGE::"Currency Card", Currency);
end;
DATABASE::Customer:
begin
RecordRef.SetTable(Customer);
PAGE.Run(PAGE::"Customer Card", Customer);
end;
DATABASE::Vendor:
begin
RecordRef.SetTable(Vendor);
PAGE.Run(PAGE::"Vendor Card", Vendor);
end;
DATABASE::Item:
begin
RecordRef.SetTable(Item);
PAGE.Run(PAGE::"Item Card", Item);
end;
DATABASE::"Sales Invoice Header":
begin
RecordRef.SetTable(SalesInvoiceHeader);
PageManagement.PageRun(SalesInvoiceHeader);
end;
DATABASE::Resource:
begin
RecordRef.SetTable(Resource);
PAGE.Run(PAGE::"Resource Card", Resource);
end;
DATABASE::"Salesperson/Purchaser":
begin
RecordRef.SetTable(SalespersonPurchaser);
PAGE.Run(PAGE::"Salesperson/Purchaser Card", SalespersonPurchaser);
end;
DATABASE::"Unit of Measure":
begin
RecordRef.SetTable(UnitOfMeasure);
// There is no Unit of Measure card. Open the list, filtered down to this instance.
PAGE.Run(PAGE::"Units of Measure", UnitOfMeasure);
end;
DATABASE::"Customer Price Group":
begin
RecordRef.SetTable(CustomerPriceGroup);
// There is no Customer Price Group card. Open the list, filtered down to this instance.
PAGE.Run(PAGE::"Customer Price Groups", CustomerPriceGroup);
end;
else
Error(NoCardPageActionDefinedForTableIdErr, RecordID.TableNo);
end;
end;
procedure GetCRMEntityTypeName(TableId: Integer): Text
var
CRMTableId: Integer;
begin
if IsCRMTable(TableId) then begin
CRMTableId := TableId;
TableId := 0;
end;
exit(GetCRMEntityTypeName(TableId, CRMTableId));
end;
procedure GetCRMEntityTypeName(TableId: Integer; CRMTableId: Integer): Text
var
TempNameValueBuffer: Record "Name/Value Buffer" temporary;
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
begin
CRMSetupDefaults.GetTableIDCRMEntityNameMapping(TempNameValueBuffer);
if CRMTableId <> 0 then begin
TempNameValueBuffer.SetRange(Value, Format(CRMTableId));
if TempNameValueBuffer.FindFirst() then
exit(TempNameValueBuffer.Name);
end;
if TableId <> 0 then begin
TempNameValueBuffer.SetRange(Value, Format(TableId));
if TempNameValueBuffer.FindFirst() then
exit(TempNameValueBuffer.Name);
end;
Error(UnableToResolveCRMEntityNameFrmTableIDErr, TableId, CRMProductName.CDSServiceName());
end;
local procedure GetCoupledCRMID(RecordID: RecordID; var CRMID: Guid): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
begin
exit(CRMIntegrationRecord.FindIDFromRecordID(RecordID, CRMID))
end;
local procedure DefineCouplingIfNotCoupled(RecordID: RecordID; var CRMID: Guid): Boolean
var
RecordRef: RecordRef;
begin
if GetCoupledCRMID(RecordID, CRMID) then
exit(true);
RecordRef.Open(RecordID.TableNo);
if Confirm(StrSubstNo(ManageCouplingQst, RecordRef.Caption, CRMProductName.CDSServiceName()), false) then
if DefineCoupling(RecordID) then
exit(GetCoupledCRMID(RecordID, CRMID));
exit(false);
end;
procedure DefineCoupling(RecordID: RecordID): Boolean
var
CRMCouplingManagement: Codeunit "CRM Coupling Management";
CreateNew: Boolean;
Synchronize: Boolean;
Direction: Option;
CRMID: Guid;
begin
if CRMCouplingManagement.DefineCoupling(RecordID, CRMID, CreateNew, Synchronize, Direction) then begin
if CreateNew then
CreateNewRecordsInCRM(RecordID)
else
if Synchronize then
PerformInitialSynchronization(RecordID, CRMID, Direction);
exit(true);
end;
exit(false);
end;
local procedure DefineOptionMappingIfNotMapped(RecordID: RecordID; var CRMOptionId: Integer): Boolean
var
RecordRef: RecordRef;
begin
RecordRef.Open(RecordID.TableNo);
if Confirm(StrSubstNo(ManageCouplingQst, RecordRef.Caption, CRMProductName.CDSServiceName()), false) then
if DefineOptionMapping(RecordID) then
exit(GetMappedCRMOptionId(RecordID, CRMOptionId));
exit(false);
end;
procedure DefineOptionMapping(RecordID: RecordID): Boolean
var
CRMCouplingManagement: Codeunit "CRM Coupling Management";
CreateNew: Boolean;
Synchronize: Boolean;
Direction: Option;
CRMOptionId: Integer;
begin
if CRMCouplingManagement.DefineOptionMapping(RecordID, CRMOptionId, CreateNew, Synchronize, Direction) then begin
if CreateNew then
CreateNewOptionsInCRM(RecordID)
else
if Synchronize then
PerformInitialOptionSynchronization(RecordID, CRMOptionId, Direction);
exit(true);
end;
exit(false);
end;
procedure ManageCreateNewRecordFromCRM(TableID: Integer)
begin
// Extinct method. Kept for backward compatibility.
case TableID of
DATABASE::Contact:
CreateNewContactFromCRM();
DATABASE::Customer:
CreateNewCustomerFromCRM();
end;
end;
procedure CreateNewContactFromCRM()
var
IntegrationTableMapping: Record "Integration Table Mapping";
begin
GetIntegrationTableMapping(IntegrationTableMapping, DATABASE::Contact);
PAGE.RunModal(PAGE::"CRM Contact List");
end;
procedure CreateNewCustomerFromCRM()
var
IntegrationTableMapping: Record "Integration Table Mapping";
begin
GetIntegrationTableMapping(IntegrationTableMapping, DATABASE::Customer);
PAGE.RunModal(PAGE::"CRM Account List");
end;
procedure CreateNewVendorFromCRM()
var
IntegrationTableMapping: Record "Integration Table Mapping";
begin
GetIntegrationTableMapping(IntegrationTableMapping, DATABASE::Vendor);
PAGE.RunModal(PAGE::"CRM Account List");
end;
[Scope('OnPrem')]
procedure ShowCustomerCRMOpportunities(Customer: Record Customer)
var
CRMOpportunity: Record "CRM Opportunity";
CRMID: Guid;
begin
if not IsCRMIntegrationEnabled() then
exit;
if not DefineCouplingIfNotCoupled(Customer.RecordId, CRMID) then
exit;
CRMOpportunity.FilterGroup := 2;
CRMOpportunity.SetRange(ParentAccountId, CRMID);
CRMOpportunity.SetRange(StateCode, CRMOpportunity.StateCode::Open);
CRMOpportunity.FilterGroup := 0;
PAGE.Run(PAGE::"CRM Opportunity List", CRMOpportunity);
end;
[Scope('OnPrem')]
procedure ShowCustomerCRMQuotes(Customer: Record Customer)
var
CRMQuote: Record "CRM Quote";
CRMID: Guid;
begin
if not IsCRMIntegrationEnabled() then
exit;
if not DefineCouplingIfNotCoupled(Customer.RecordId, CRMID) then
exit;
CRMQuote.FilterGroup := 2;
CRMQuote.SetRange(CustomerId, CRMID);
CRMQuote.SetRange(StateCode, CRMQuote.StateCode::Active);
CRMQuote.FilterGroup := 0;
PAGE.Run(PAGE::"CRM Sales Quote List", CRMQuote);
end;
[Scope('OnPrem')]
procedure ShowCustomerCRMCases(Customer: Record Customer)
var
CRMIncident: Record "CRM Incident";
CRMID: Guid;
begin
if not IsCRMIntegrationEnabled() then
exit;
if not DefineCouplingIfNotCoupled(Customer.RecordId, CRMID) then
exit;
CRMIncident.FilterGroup := 2;
CRMIncident.SetRange(CustomerId, CRMID);
CRMIncident.SetRange(StateCode, CRMIncident.StateCode::Active);
CRMIncident.FilterGroup := 2;
PAGE.Run(PAGE::"CRM Case List", CRMIncident);
end;
[Scope('OnPrem')]
procedure ResetIntTableMappingDefaultConfiguration(var IntegrationTableMapping: Record "Integration Table Mapping")
var
CDSConnectionSetup: Record "CDS Connection Setup";
CRMConnectionSetup: Record "CRM Connection Setup";
CDSSetupDefaults: Codeunit "CDS Setup Defaults";
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
CDSIntegrationMgt: Codeunit "CDS Integration Mgt.";
EnqueueJobQueEntries: Boolean;
IsTeamOwnershipModel: Boolean;
IsHandled: Boolean;
begin
Codeunit.Run(Codeunit::"CRM Integration Management");
if CRMConnectionSetup.Get() then
EnqueueJobQueEntries := CRMConnectionSetup.DoReadCRMData() and CRMConnectionSetup.IsEnabled();
IsTeamOwnershipModel := CDSIntegrationMgt.IsTeamOwnershipModelSelected();
if IntegrationTableMapping.FindSet() then
repeat
case IntegrationTableMapping."Table ID" of
Database::"Salesperson/Purchaser":
CDSSetupDefaults.ResetSalesPeopleSystemUserMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel, true);
Database::Customer:
CDSSetupDefaults.ResetCustomerAccountMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel, true);
Database::Vendor:
CDSSetupDefaults.ResetVendorAccountMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel, true);
Database::Contact:
CDSSetupDefaults.ResetContactContactMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel, true);
Database::Currency:
CDSSetupDefaults.ResetCurrencyTransactionCurrencyMapping(IntegrationTableMapping.Name, true);
Database::"Payment Terms":
CDSSetupDefaults.ResetPaymentTermsMapping(IntegrationTableMapping.Name);
Database::"Shipment Method":
CDSSetupDefaults.ResetShipmentMethodMapping(IntegrationTableMapping.Name);
Database::"Shipping Agent":
CDSSetupDefaults.ResetShippingAgentMapping(IntegrationTableMapping.Name);
Database::"Unit of Measure":
CRMSetupDefaults.ResetUnitOfMeasureUoMScheduleMapping(IntegrationTableMapping.Name, EnqueueJobQueEntries);
Database::"Unit Group":
CRMSetupDefaults.ResetUnitGroupUoMScheduleMapping(IntegrationTableMapping.Name, EnqueueJobQueEntries);
Database::"Item Unit of Measure":
CRMSetupDefaults.ResetItemUnitOfMeasureUoMMapping(IntegrationTableMapping.Name, EnqueueJobQueEntries);
Database::"Resource Unit of Measure":
CRMSetupDefaults.ResetResourceUnitOfMeasureUoMMapping(IntegrationTableMapping.Name, EnqueueJobQueEntries);
Database::Item:
CRMSetupDefaults.ResetItemProductMapping(IntegrationTableMapping.Name, EnqueueJobQueEntries);
Database::Resource:
if IntegrationTableMapping."Integration Table ID" = Database::"CRM Product" then
CRMSetupDefaults.ResetResourceProductMapping(IntegrationTableMapping.Name, EnqueueJobQueEntries);
Database::"Customer Price Group":
CRMSetupDefaults.ResetCustomerPriceGroupPricelevelMapping(IntegrationTableMapping.Name, EnqueueJobQueEntries);
Database::"Sales Invoice Header":
CRMSetupDefaults.ResetSalesInvoiceHeaderInvoiceMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel, EnqueueJobQueEntries);
Database::"Sales Invoice Line":
CRMSetupDefaults.ResetSalesInvoiceLineInvoiceMapping(IntegrationTableMapping.Name);
Database::Opportunity:
CRMSetupDefaults.ResetOpportunityMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel);
Database::"Sales Header":
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then begin
CRMSetupDefaults.ResetBidirectionalSalesOrderMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel, EnqueueJobQueEntries);
CRMSetupDefaults.RecreateSalesOrderNotesJobQueueEntry(EnqueueJobQueEntries);
CRMSetupDefaults.RecreateArchivedSalesOrdersJobQueueEntry(EnqueueJobQueEntries);
end else begin
CRMSetupDefaults.ResetSalesOrderMapping(IntegrationTableMapping.Name, IsTeamOwnershipModel, EnqueueJobQueEntries);
CRMSetupDefaults.RecreateSalesOrderStatusJobQueueEntry(EnqueueJobQueEntries);
CRMSetupDefaults.RecreateSalesOrderNotesJobQueueEntry(EnqueueJobQueEntries);
end;
Database::"Sales Line":
CRMSetupDefaults.ResetBidirectionalSalesOrderLineMapping(IntegrationTableMapping.Name);
else begin
OnBeforeHandleCustomIntegrationTableMapping(IsHandled, IntegrationTableMapping.Name, IntegrationTableMapping, EnqueueJobQueEntries);
if not IsHandled then begin
if Confirm(ResetAllCustomIntegrationTableMappingsLbl) then begin
if CDSConnectionSetup.Get() then
CDSSetupDefaults.SetCustomIntegrationsTableMappings(CDSConnectionSetup);
if CRMConnectionSetup.Get() then
CRMSetupDefaults.SetCustomIntegrationsTableMappings(CRMConnectionSetup);
end;
IsHandled := true;
end;
end;
end;
AddExtraFieldMappings(IntegrationTableMapping);
until IntegrationTableMapping.Next() = 0;
end;
internal procedure AddExtraFieldMappings(IntegrationTableMapping: Record "Integration Table Mapping")
var
IntegrationFieldMapping: Record "Integration Field Mapping";
Customer: Record Customer;
Vendor: Record Vendor;
Contact: Record Contact;
SalespersonPurchaser: Record "Salesperson/Purchaser";
Opportunity: Record Opportunity;
TempCRMAccount: Record "CRM Account" temporary;
TempCRMContact: Record "CRM Contact" temporary;
TempCRMSystemUser: Record "CRM Systemuser" temporary;
TempCRMOpportunity: Record "CRM Opportunity" temporary;
begin
case IntegrationTableMapping."Table ID" of
Database::Customer:
begin
if IntegrationTableMapping."Integration Table ID" <> Database::"CRM Account" then
exit;
AddFieldMapping(IntegrationTableMapping, Customer.FieldNo("No."), TempCRMAccount.FieldNo(AccountNumber), IntegrationFieldMapping.Direction::ToIntegrationTable, false);
AddFieldMapping(IntegrationTableMapping, Customer.FieldNo("Mobile Phone No."), TempCRMAccount.FieldNo(Telephone2), IntegrationFieldMapping.Direction::Bidirectional, false);
end;
Database::Vendor:
begin
if IntegrationTableMapping."Integration Table ID" <> Database::"CRM Account" then
exit;
AddFieldMapping(IntegrationTableMapping, Vendor.FieldNo("No."), TempCRMAccount.FieldNo(AccountNumber), IntegrationFieldMapping.Direction::ToIntegrationTable, false);
AddFieldMapping(IntegrationTableMapping, Vendor.FieldNo("Mobile Phone No."), TempCRMAccount.FieldNo(Telephone2), IntegrationFieldMapping.Direction::Bidirectional, false);
end;
Database::Contact:
begin
if IntegrationTableMapping."Integration Table ID" <> Database::"CRM Contact" then
exit;
AddFieldMapping(IntegrationTableMapping, Contact.FieldNo("Salutation Code"), TempCRMContact.FieldNo(Salutation), IntegrationFieldMapping.Direction::Bidirectional, false);
end;
Database::"Salesperson/Purchaser":
begin
if IntegrationTableMapping."Integration Table ID" <> Database::"CRM Systemuser" then
exit;
AddFieldMapping(IntegrationTableMapping, SalespersonPurchaser.FieldNo("Job Title"), TempCRMSystemUser.FieldNo(JobTitle), IntegrationFieldMapping.Direction::Bidirectional, false);
AddFieldMapping(IntegrationTableMapping, SalespersonPurchaser.FieldNo("E-Mail 2"), TempCRMSystemUser.FieldNo(PersonalEMailAddress), IntegrationFieldMapping.Direction::Bidirectional, false);
end;
Database::Opportunity:
begin
if IntegrationTableMapping."Integration Table ID" <> Database::"CRM Opportunity" then
exit;
AddFieldMapping(IntegrationTableMapping, Opportunity.FieldNo("Date Closed"), TempCRMOpportunity.FieldNo(ActualCloseDate), IntegrationFieldMapping.Direction::ToIntegrationTable, false);
AddFieldMapping(IntegrationTableMapping, Opportunity.FieldNo("Probability %"), TempCRMOpportunity.FieldNo(CloseProbability), IntegrationFieldMapping.Direction::ToIntegrationTable, false);
AddFieldMapping(IntegrationTableMapping, Opportunity.FieldNo("Calcd. Current Value (LCY)"), TempCRMOpportunity.FieldNo(ActualValue), IntegrationFieldMapping.Direction::ToIntegrationTable, false);
end;
end;
OnAfterAddExtraFieldMappings(IntegrationTableMapping.Name);
end;
local procedure AddFieldMapping(IntegrationTableMapping: Record "Integration Table Mapping"; FieldNo: Integer; IntegrationTableFieldNo: Integer; Direction: Option; Enabled: Boolean);
var
IntegrationFieldMapping: Record "Integration Field Mapping";
begin
IntegrationFieldMapping.SetRange("Integration Table Mapping Name", IntegrationTableMapping.Name);
IntegrationFieldMapping.SetRange("Field No.", FieldNo);
if not IntegrationFieldMapping.IsEmpty() then
exit;
IntegrationFieldMapping.SetRange("Field No.");
IntegrationFieldMapping.SetRange("Integration Table Field No.", IntegrationTableFieldNo);
if not IntegrationFieldMapping.IsEmpty() then
exit;
IntegrationFieldMapping.CreateRecord(IntegrationTableMapping.Name, FieldNo, IntegrationTableFieldNo, Direction, '', true, false, Enabled, '', false);
end;
procedure GetNoOfCRMOpportunities(Customer: Record Customer): Integer
var
CRMOpportunity: Record "CRM Opportunity";
CRMID: Guid;
begin
if not IsCRMIntegrationEnabled() then
exit(0);
if not GetCoupledCRMID(Customer.RecordId, CRMID) then
exit(0);
CRMOpportunity.SetRange(ParentAccountId, CRMID);
CRMOpportunity.SetRange(StateCode, CRMOpportunity.StateCode::Open);
exit(CRMOpportunity.Count);
end;
procedure GetNoOfCRMQuotes(Customer: Record Customer): Integer
var
CRMQuote: Record "CRM Quote";
CRMID: Guid;
begin
if not IsCRMIntegrationEnabled() then
exit(0);
if not GetCoupledCRMID(Customer.RecordId, CRMID) then
exit(0);
CRMQuote.SetRange(CustomerId, CRMID);
CRMQuote.SetRange(StateCode, CRMQuote.StateCode::Active);
exit(CRMQuote.Count);
end;
procedure GetNoOfCRMCases(Customer: Record Customer): Integer
var
CRMIncident: Record "CRM Incident";
CRMID: Guid;
begin
if not IsCRMIntegrationEnabled() then
exit(0);
if not GetCoupledCRMID(Customer.RecordId, CRMID) then
exit(0);
CRMIncident.SetRange(StateCode, CRMIncident.StateCode::Active);
CRMIncident.SetRange(CustomerId, CRMID);
exit(CRMIncident.Count);
end;
local procedure GetSelectedMultipleSyncDirection(IntegrationTableMapping: Record "Integration Table Mapping"): Integer
var
SynchronizeNowQuestion: Text;
AllowedDirection: Integer;
RecommendedDirection: Integer;
SelectedDirection: Integer;
IsHandled: Boolean;
begin
AllowedDirection := IntegrationTableMapping.Direction;
RecommendedDirection := AllowedDirection;
case AllowedDirection of
IntegrationTableMapping.Direction::Bidirectional:
begin
SelectedDirection := StrMenu(UpdateNowUniDirectionQst, RecommendedDirection, UpdateMultipleNowTitleTxt);
if SelectedDirection = 0 then
SelectedDirection := -1;
exit(SelectedDirection);
end;
IntegrationTableMapping.Direction::FromIntegrationTable:
SynchronizeNowQuestion := StrSubstNo(UpdateMultipleNowFromCRMQst, CRMProductName.CDSServiceName());
else
SynchronizeNowQuestion := StrSubstNo(UpdateMultipleNowToCRMQst, CRMProductName.CDSServiceName());
end;
IsHandled := false;
OnBeforeSynchronyzeNowQuestion(AllowedDirection, IsHandled);
if IsHandled then
exit(AllowedDirection);
if Confirm(SynchronizeNowQuestion, true) then
exit(AllowedDirection);
exit(-1); // user canceled the process
end;
local procedure GetSelectedSingleSyncDirection(IntegrationTableMapping: Record "Integration Table Mapping"; RecordRef: RecordRef; CRMID: Guid; var RecommendedDirectionIgnored: Boolean): Integer
var
IntegrationRecSynchInvoke: Codeunit "Integration Rec. Synch. Invoke";
IntegrationTableSynch: Codeunit "Integration Table Synch.";
CRMRecordRef: RecordRef;
RecordIDDescr: Text;
SynchronizeNowQuestion: Text;
AllowedDirection: Integer;
RecommendedDirection: Integer;
SelectedDirection: Integer;
RecordModified: Boolean;
CRMRecordModified: Boolean;
BothModified: Boolean;
DefaultAnswer: Boolean;
FieldsModified: Boolean;
BidirectionalFieldsModified: Boolean;
begin
AllowedDirection := IntegrationTableMapping.Direction;
// Determine which sides were modified since last synch
IntegrationTableMapping.GetRecordRef(CRMID, CRMRecordRef);
RecordModified := IntegrationRecSynchInvoke.WasModifiedAfterLastSynch(IntegrationTableMapping, RecordRef);
CRMRecordModified := IntegrationRecSynchInvoke.WasModifiedAfterLastSynch(IntegrationTableMapping, CRMRecordRef);
BothModified := RecordModified and CRMRecordModified;
RecordIDDescr := Format(RecordRef.RecordId, 0, 1);
if BothModified then
// Changes on both sides. Bidirectional: warn user. Unidirectional: confirm and exit.
case AllowedDirection of
IntegrationTableMapping.Direction::Bidirectional:
begin
IntegrationTableSynch.CheckTransferFields(IntegrationTableMapping, RecordRef, CRMRecordRef, FieldsModified, BidirectionalFieldsModified);
if BidirectionalFieldsModified then
Message(BothRecordsModifiedBiDirectionalConflictMsg, RecordRef.Caption, CRMRecordRef.Caption, CRMProductName.CDSServiceName())
else begin
if not FieldsModified then
IntegrationTableSynch.CheckTransferFields(IntegrationTableMapping, CRMRecordRef, RecordRef, FieldsModified, BidirectionalFieldsModified);
if FieldsModified then
Message(BothRecordsModifiedBiDirectionalNoConflictMsg, RecordRef.Caption, CRMRecordRef.Caption, CRMProductName.CDSServiceName());
end;
end;
IntegrationTableMapping.Direction::ToIntegrationTable:
begin
IntegrationTableSynch.CheckTransferFields(IntegrationTableMapping, RecordRef, CRMRecordRef, FieldsModified, BidirectionalFieldsModified);
if not FieldsModified then
exit(AllowedDirection);
if Confirm(BothRecordsModifiedToCRMQst, false, RecordIDDescr, CRMRecordRef.Caption, PRODUCTNAME.Short(), CRMProductName.CDSServiceName()) then
exit(AllowedDirection);
exit(-1);
end;
IntegrationTableMapping.Direction::FromIntegrationTable:
begin
IntegrationTableSynch.CheckTransferFields(IntegrationTableMapping, CRMRecordRef, RecordRef, FieldsModified, BidirectionalFieldsModified);
if not FieldsModified then
exit(AllowedDirection);
if Confirm(BothRecordsModifiedToNAVQst, false, RecordIDDescr, CRMRecordRef.Caption, PRODUCTNAME.Short(), CRMProductName.CDSServiceName()) then
exit(AllowedDirection);
exit(-1);
end;
end;
// Zero or one side changed. Synch for zero too because dependent objects could have changed.
case AllowedDirection of
IntegrationTableMapping.Direction::Bidirectional:
begin
if BothModified and BidirectionalFieldsModified then begin
RecommendedDirection := IntegrationTableMapping.Direction::ToIntegrationTable;
SelectedDirection := StrMenu(UpdateNowUniDirectionQst, RecommendedDirection, StrSubstNo(UpdateOneNowTitleTxt, RecordIDDescr));
if SelectedDirection = 0 then
SelectedDirection := -1;
end else begin
if RecordModified = CRMRecordModified then
RecommendedDirection := IntegrationTableMapping.Direction::Bidirectional
else
if CRMRecordModified then
RecommendedDirection := IntegrationTableMapping.Direction::FromIntegrationTable
else
RecommendedDirection := IntegrationTableMapping.Direction::ToIntegrationTable;
SelectedDirection := StrMenu(UpdateNowBiDirectionQst, RecommendedDirection, StrSubstNo(UpdateOneNowTitleTxt, RecordIDDescr));
case SelectedDirection of
0:
SelectedDirection := -1;
3:
SelectedDirection := IntegrationTableMapping.Direction::Bidirectional;
end;
end;
RecommendedDirectionIgnored := SelectedDirection <> RecommendedDirection;
exit(SelectedDirection);
end;
IntegrationTableMapping.Direction::FromIntegrationTable:
if RecordModified then
SynchronizeNowQuestion := StrSubstNo(UpdateOneNowFromOldCRMQst, RecordIDDescr, PRODUCTNAME.Short(), CRMProductName.CDSServiceName())
else begin
SynchronizeNowQuestion := StrSubstNo(UpdateOneNowFromCRMQst, RecordIDDescr, CRMProductName.CDSServiceName());
DefaultAnswer := true;
end;
else
if CRMRecordModified then
SynchronizeNowQuestion := StrSubstNo(UpdateOneNowToModifiedCRMQst, RecordIDDescr, PRODUCTNAME.Short(), CRMProductName.CDSServiceName())
else begin
SynchronizeNowQuestion := StrSubstNo(UpdateOneNowToCRMQst, RecordIDDescr, CRMProductName.CDSServiceName());
DefaultAnswer := true;
end;
end;
if Confirm(SynchronizeNowQuestion, DefaultAnswer) then
exit(AllowedDirection);
exit(-1); // user canceled the process
end;
local procedure DeleteIntegrationRecordByBCID(var BCRecordRef: RecordRef)
var
CRMIntegrationRecord: Record "CRM Integration Record";
begin
if CRMIntegrationRecord.FindByRecordID(BCRecordRef.RecordId()) then begin
CRMIntegrationRecord.Delete();
Commit();
end;
end;
local procedure DeleteIntegrationRecordByCRMID(var CRMRecordRef: RecordRef; var IntegrationTableMapping: Record "Integration Table Mapping")
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMID: Guid;
begin
CRMID := CRMRecordRef.Field(IntegrationTableMapping."Integration Table UID Fld. No.").Value();
if CRMIntegrationRecord.FindByCRMID(CRMID) then begin
CRMIntegrationRecord.Delete();
Commit();
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnUpdateConflictDetected', '', false, false)]
local procedure HandleOnUpdateConflictDetected(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; var UpdateConflictHandled: Boolean; var SkipRecord: Boolean)
begin
if UpdateConflictHandled then
exit;
if not (IsCDSIntegrationEnabled() or IsCRMIntegrationEnabled()) then
exit;
case IntegrationTableMapping."Update-Conflict Resolution" of
IntegrationTableMapping."Update-Conflict Resolution"::"Get Update from Integration":
begin
UpdateConflictHandled := true;
if SourceRecordRef.Number() = IntegrationTableMapping."Integration Table ID" then begin
SkipRecord := false;
Session.LogMessage('0000CUC', UpdateConflictHandledFromIntTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end else begin
SkipRecord := true;
Session.LogMessage('0000D3O', UpdateConflictHandledSkipTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
end;
IntegrationTableMapping."Update-Conflict Resolution"::"Send Update to Integration":
begin
UpdateConflictHandled := true;
if SourceRecordRef.Number() = IntegrationTableMapping."Table ID" then begin
SkipRecord := false;
Session.LogMessage('0000CUD', UpdateConflictHandledToIntTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end else begin
SkipRecord := true;
Session.LogMessage('0000D3P', UpdateConflictHandledSkipTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
end;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Integration Rec. Synch. Invoke", 'OnDeletionConflictDetected', '', false, false)]
local procedure HandleOnDeletionConflictDetected(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DeletionConflictHandled: Boolean)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CRMID: Guid;
begin
if DeletionConflictHandled then
exit;
if not (IsCDSIntegrationEnabled() or IsCRMIntegrationEnabled()) then
exit;
case IntegrationTableMapping."Deletion-Conflict Resolution" of
IntegrationTableMapping."Deletion-Conflict Resolution"::"Remove Coupling":
begin
if SourceRecordRef.Number = IntegrationTableMapping."Table ID" then
DeletionConflictHandled := RemoveCoupling(SourceRecordRef.RecordId(), false)
else begin
CRMID := SourceRecordRef.Field(IntegrationTableMapping."Integration Table UID Fld. No.").Value();
DeletionConflictHandled := RemoveCoupling(IntegrationTableMapping."Table ID", IntegrationTableMapping."Integration Table ID", CRMID, false);
end;
if IntegrationTableMapping."Table ID" = Database::"Sales Line" then
if CRMConnectionSetup.IsBidirectionalSalesOrderIntEnabled() then
if SourceRecordRef.Delete() then;
if DeletionConflictHandled then
Session.LogMessage('0000CUE', DeletionConflictHandledRemoveCouplingTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
IntegrationTableMapping."Deletion-Conflict Resolution"::"Restore Records":
begin
if SourceRecordRef.Number = IntegrationTableMapping."Table ID" then
DeleteIntegrationRecordByBCID(SourceRecordRef)
else
DeleteIntegrationRecordByCRMID(SourceRecordRef, IntegrationTableMapping);
DeletionConflictHandled := true;
Session.LogMessage('0000CUF', DeletionConflictHandledRestoreRecordTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
end;
end;
[EventSubscriber(ObjectType::Table, Database::"Service Connection", 'OnRegisterServiceConnection', '', false, false)]
procedure HandleCRMRegisterServiceConnection(var ServiceConnection: Record "Service Connection")
var
CRMConnectionSetup: Record "CRM Connection Setup";
RecRef: RecordRef;
begin
if not CRMConnectionSetup.Get() then begin
if not CRMConnectionSetup.WritePermission then begin
Session.LogMessage('0000CLV', NoPermissionsTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
exit;
end;
CRMConnectionSetup.Init();
CRMConnectionSetup.Insert();
end;
RecRef.GetTable(CRMConnectionSetup);
ServiceConnection.Status := ServiceConnection.Status::Enabled;
if not CRMConnectionSetup."Is Enabled" then
ServiceConnection.Status := ServiceConnection.Status::Disabled
else
if CRMConnectionSetup.TestConnection() then
ServiceConnection.Status := ServiceConnection.Status::Connected
else
ServiceConnection.Status := ServiceConnection.Status::Error;
ServiceConnection.InsertServiceConnectionExtended(
ServiceConnection, RecRef.RecordId, CRMConnectionSetup.TableCaption(), CRMConnectionSetup."Server Address", PAGE::"CRM Connection Setup",
PAGE::"CRM Connection Setup Wizard");
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Integration Mgt.", 'OnGetIntegrationSolutions', '', false, false)]
local procedure HandleOnGetIntegrationSolutions(var SolutionUniqueNameList: List of [Text])
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if CRMConnectionSetup.Get() then
if CRMConnectionSetup."Is Enabled" then
SolutionUniqueNameList.Add(CRMProductName.UNIQUE());
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"CDS Integration Mgt.", 'OnGetIntegrationRequiredRoles', '', false, false)]
local procedure HandleOnGetIntegrationRequiredRoles(var RequiredRoleIdList: List of [Guid])
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if CRMConnectionSetup.Get() then
if CRMConnectionSetup."Is Enabled" then begin
RequiredRoleIdList.Add(GetIntegrationAdminRoleID());
RequiredRoleIdList.Add(GetIntegrationUserRoleID());
end;
end;
procedure ClearState()
begin
CRMIntegrationEnabledState := CRMIntegrationEnabledState::" "
end;
[Scope('OnPrem')]
procedure GetLastErrorMessage(): Text
var
ErrorObject: DotNet Exception;
begin
ErrorObject := GetLastErrorObject();
if IsNull(ErrorObject) then
exit('');
if StrPos(ErrorObject.GetType().Name, 'NavCrmException') > 0 then
if not IsNull(ErrorObject.InnerException) then
exit(ErrorObject.InnerException.Message);
exit(GetLastErrorText);
end;
[TryFunction]
[Scope('OnPrem')]
procedure ImportCRMSolution(ServerAddress: Text; IntegrationUserEmail: Text; AdminUserEmail: Text; AdminUserPassword: SecretText; AccessToken: SecretText; AdminADDomain: Text; ProxyVersion: Integer; ForceRedeploy: Boolean; var SolutionImportFailed: Boolean)
var
CDSConnectionSetup: Record "CDS Connection Setup";
CRMRole: Record "CRM Role";
CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
PageCDSConnectionSetup: Page "CDS Connection Setup";
CRMHelper: DotNet CrmHelper;
UserGUID: Guid;
IntegrationAdminRoleGUID: Guid;
IntegrationUserRoleGUID: Guid;
SalesProIntegrationRoleGUID: Guid;
EmptyGuid: Guid;
DefaultOwningTeamGUID: Guid;
TempConnectionString: SecretText;
TempConnectionStringWithPlaceholders: Text;
SolutionInstalled: Boolean;
SolutionOutdated: Boolean;
ImportSolution: Boolean;
begin
CheckConnectRequiredFields(ServerAddress, IntegrationUserEmail);
CDSConnectionSetup.Get();
if not AccessToken.IsEmpty() then begin
TempConnectionStringWithPlaceholders :=
StrSubstNo(OAuthConnectionStringFormatTok, ServerAddress, '%1', ProxyVersion, CDSIntegrationImpl.GetAuthenticationTypeToken(CDSConnectionSetup));
TempConnectionString := SecretStrSubstNo(TempConnectionStringWithPlaceholders, AccessToken);
end
else
if AdminADDomain <> '' then begin
TempConnectionStringWithPlaceholders := StrSubstNo(
ConnectionStringFormatTok, ServerAddress, AdminUserEmail, '%1', ProxyVersion, CDSIntegrationImpl.GetAuthenticationTypeToken(CDSConnectionSetup, AdminADDomain));
TempConnectionString := SecretStrSubstNo(TempConnectionStringWithPlaceholders, AdminUserPassword);
end
else begin
TempConnectionStringWithPlaceholders := StrSubstNo(
ConnectionStringFormatTok, ServerAddress, AdminUserEmail, '%1', ProxyVersion, CDSIntegrationImpl.GetAuthenticationTypeToken(CDSConnectionSetup));
TempConnectionString := SecretStrSubstNo(TempConnectionStringWithPlaceholders, AdminUserPassword)
end;
if CDSConnectionSetup."Authentication Type" = CDSConnectionSetup."Authentication Type"::OAuth then
TempConnectionString := CDSIntegrationImpl.ReplaceUserNamePasswordInConnectionString(CDSConnectionSetup, AdminUserEmail, AdminUserPassword);
if not InitializeCRMConnection(CRMHelper, TempConnectionString) then
ProcessConnectionFailures();
UserGUID := CRMHelper.GetUserId(IntegrationUserEmail);
if IsNullGuid(UserGUID) then
Error(UserDoesNotExistCRMErr, IntegrationUserEmail, CRMProductName.CDSServiceName());
SolutionInstalled := CRMHelper.CheckSolutionPresence(MicrosoftDynamicsNavIntegrationTxt);
if SolutionInstalled then
SolutionOutdated := IsSolutionOutdated(TempConnectionString);
if ForceRedeploy then
ImportSolution := (not SolutionInstalled) or SolutionOutdated
else
ImportSolution := not SolutionInstalled;
if ImportSolution then begin
if not ImportDefaultCRMSolution(CRMHelper) then begin
SolutionImportFailed := true;
ProcessConnectionFailures();
end;
if CrmHelper.GetPrivilegeId(SalesProDefaultSettingsPrivilegeNameTxt) <> EmptyGuid then
if not ImportDefaultSalesProSolution(CRMHelper) then begin
SolutionImportFailed := true;
ProcessConnectionFailures();
end else
Session.LogMessage('0000JFP', SalesProIntegrationSolutionImportedTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
IntegrationAdminRoleGUID := CRMHelper.GetRoleId(GetIntegrationAdminRoleID());
IntegrationUserRoleGUID := CRMHelper.GetRoleId(GetIntegrationUserRoleID());
SalesProIntegrationRoleGUID := CRMHelper.GetRoleId(GetSalesProIntegrationRoleId());
if not CRMHelper.CheckRoleAssignedToUser(UserGUID, IntegrationAdminRoleGUID) then
CRMHelper.AssociateUserWithRole(UserGUID, IntegrationAdminRoleGUID);
if not CRMHelper.CheckRoleAssignedToUser(UserGUID, IntegrationUserRoleGUID) then
CRMHelper.AssociateUserWithRole(UserGUID, IntegrationUserRoleGUID);
if SalesProIntegrationRoleGUID <> EmptyGuid then
if not CRMHelper.CheckRoleAssignedToUser(UserGUID, SalesProIntegrationRoleGUID) then
CRMHelper.AssociateUserWithRole(UserGUID, SalesProIntegrationRoleGUID);
if CDSIntegrationImpl.IsIntegrationEnabled() then begin
CDSIntegrationImpl.RegisterConnection();
CDSIntegrationImpl.ActivateConnection();
CDSConnectionSetup.Get();
DefaultOwningTeamGUID := CDSIntegrationImpl.GetOwningTeamId(CDSConnectionSetup);
if IsNullGuid(DefaultOwningTeamGUID) then begin
Session.LogMessage('0000GDK', TeamNotFoundTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(TeamNotFoundErr, CDSIntegrationImpl.GetDefaultBusinessUnitName(), PageCDSConnectionSetup.Caption);
end;
CRMRole.SetRange(ParentRoleId, IntegrationAdminRoleGUID);
CRMRole.SetRange(BusinessUnitId, CDSIntegrationImpl.GetCoupledBusinessUnitId());
if not CRMRole.FindFirst() then begin
Session.LogMessage('0000BKQ', RoleNotFoundForBusinessUnitTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(IntegrationRoleNotFoundErr, GetIntegrationAdminRoleID(), CDSIntegrationImpl.GetDefaultBusinessUnitName());
end;
if not CDSIntegrationImpl.AssignTeamRole(CrmHelper, DefaultOwningTeamGUID, CRMRole.RoleId) then begin
Session.LogMessage('0000BKR', CannotAssignRoleToTeamTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(CannotAssignRoleToTeamErr, DefaultOwningTeamGUID, CDSIntegrationImpl.GetDefaultBusinessUnitName(), CRMRole.Name);
end;
CRMRole.SetRange(ParentRoleId, IntegrationUserRoleGUID);
CRMRole.SetRange(BusinessUnitId, CDSIntegrationImpl.GetCoupledBusinessUnitId());
if not CRMRole.FindFirst() then begin
Session.LogMessage('0000BKS', RoleNotFoundForBusinessUnitTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(IntegrationRoleNotFoundErr, GetIntegrationUserRoleID(), CDSIntegrationImpl.GetDefaultBusinessUnitName());
end;
if not CDSIntegrationImpl.AssignTeamRole(CrmHelper, DefaultOwningTeamGUID, CRMRole.RoleId) then begin
Session.LogMessage('0000BKT', CannotAssignRoleToTeamTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(CannotAssignRoleToTeamErr, DefaultOwningTeamGUID, CDSIntegrationImpl.GetDefaultBusinessUnitName(), CRMRole.Name);
end;
if SalesProIntegrationRoleGUID <> EmptyGuid then begin
CRMRole.SetRange(ParentRoleId, SalesProIntegrationRoleGUID);
CRMRole.SetRange(BusinessUnitId, CDSIntegrationImpl.GetCoupledBusinessUnitId());
if not CRMRole.FindFirst() then begin
Session.LogMessage('0000JFN', RoleNotFoundForBusinessUnitTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(IntegrationRoleNotFoundErr, SalesProIntegrationRoleGUID, CDSIntegrationImpl.GetDefaultBusinessUnitName());
end;
if not CDSIntegrationImpl.AssignTeamRole(CrmHelper, DefaultOwningTeamGUID, CRMRole.RoleId) then begin
Session.LogMessage('0000JFO', CannotAssignRoleToTeamTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(CannotAssignRoleToTeamErr, DefaultOwningTeamGUID, CDSIntegrationImpl.GetDefaultBusinessUnitName(), CRMRole.Name);
end;
end;
end;
end;
[NonDebuggable]
local procedure IsSolutionOutdated(TempConnectionString: SecretText): Boolean
begin
exit(IsSolutionOutdated(TempConnectionString, MicrosoftDynamicsNavIntegrationTxt));
end;
[NonDebuggable]
[Scope('OnPrem')]
procedure IsSolutionOutdated(TempConnectionString: SecretText; SolutionUniqueName: Text[65]): Boolean
var
CDSSolution: Record "CDS Solution";
CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
NavTenantSettingsHelper: DotNet NavTenantSettingsHelper;
Version: DotNet Version;
TempConnectionName: Text;
SolutionOutdated: Boolean;
begin
TempConnectionName := CDSIntegrationImpl.GetTempConnectionName();
if HasTableConnection(TableConnectionType::CRM, TempConnectionName) then
UnregisterTableConnection(TableConnectionType::CRM, TempConnectionName);
RegisterTableConnection(TableConnectionType::CRM, TempConnectionName, TempConnectionString.Unwrap());
SetDefaultTableConnection(TableConnectionType::CRM, TempConnectionName, true);
SolutionOutdated := true;
CDSSolution.SetRange(UniqueName, SolutionUniqueName);
if CDSSolution.FindFirst() then
if Version.TryParse(CDSSolution.Version, Version) then
SolutionOutdated := Version.CompareTo(NavTenantSettingsHelper.GetPlatformVersion()) < 0;
UnregisterTableConnection(TableConnectionType::CRM, TempConnectionName);
exit(SolutionOutdated);
end;
[TryFunction]
local procedure ImportDefaultCRMSolution(var CRMHelper: DotNet CrmHelper)
begin
CRMHelper.ImportDefaultCrmSolution();
end;
[TryFunction]
local procedure ImportDefaultSalesProSolution(var CRMHelper: DotNet CrmHelper)
begin
CRMHelper.ImportDefaultSalesProSolution();
end;
procedure CheckConnectRequiredFields(ServerAddress: Text; IntegrationUserEmail: Text)
begin
if (IntegrationUserEmail = '') or (ServerAddress = '') then
Error(EmailAndServerAddressEmptyErr);
end;
procedure CheckModifyCRMConnectionURL(var ServerAddress: Text[250])
var
UriHelper: DotNet Uri;
UriHelper2: DotNet Uri;
UriPartialHelper: DotNet UriPartial;
ProposedUri: Text[250];
begin
FirstCheckCRMConnectionURL(ServerAddress, UriHelper, UriHelper2);
if IsNull(UriHelper2) then
exit;
ProposedUri := UriHelper2.GetLeftPart(UriPartialHelper.Authority);
// Test that a specific port number is given
if ((UriHelper2.Port = 443) or (UriHelper2.Port = 80)) and (LowerCase(ServerAddress) <> LowerCase(ProposedUri)) then
if Confirm(StrSubstNo(ReplaceServerAddressQst, ServerAddress, ProposedUri)) then
ServerAddress := ProposedUri;
end;
local procedure FirstCheckCRMConnectionURL(var ServerAddress: Text[250]; var UriHelper: DotNet Uri; var UriHelper2: DotNet Uri)
var
CRMSetupDefaults: Codeunit "CRM Setup Defaults";
UriKindHelper: DotNet UriKind;
begin
if (ServerAddress = '') or (ServerAddress = '@@test@@') then
exit;
ServerAddress := DelChr(ServerAddress, '<>');
if not UriHelper.TryCreate(ServerAddress, UriKindHelper.Absolute, UriHelper2) then
if not UriHelper.TryCreate('https://' + ServerAddress, UriKindHelper.Absolute, UriHelper2) then
Error(InvalidUriErr);
if UriHelper2.Scheme <> 'https' then begin
if not CRMSetupDefaults.GetAllowNonSecureConnections() then
Error(MustUseHttpsErr, CRMProductName.SHORT());
if UriHelper2.Scheme <> 'http' then
Error(MustUseHttpOrHttpsErr, UriHelper2.Scheme, CRMProductName.SHORT());
end;
end;
procedure CheckCRMConnectionURL(ServerAddress: Text[250])
var
UriHelper: DotNet Uri;
UriHelper2: DotNet Uri;
CheckedServerAddress: Text[250];
begin
CheckedServerAddress := ServerAddress;
FirstCheckCRMConnectionURL(CheckedServerAddress, UriHelper, UriHelper2);
if CheckedServerAddress <> ServerAddress then
Error(InvalidUriErr);
end;
procedure GetOrganizationFromUrl(ServerAddress: Text[250]) orgName: Text
var
UriHelper: DotNet Uri;
UriHelper2: DotNet Uri;
UriKindHelper: DotNet UriKind;
begin
// Return the organization name from an OnPremise URL which is in the form
// http://crm-server:port/organization-name
// Notice that TryCreate will fail if the port is not a number
if (ServerAddress = '') or (ServerAddress = '@@test@@') then
exit('');
ServerAddress := DelChr(ServerAddress, '<>');
if not UriHelper.TryCreate(ServerAddress, UriKindHelper.Absolute, UriHelper2) then
if not UriHelper.TryCreate('https://' + ServerAddress, UriKindHelper.Absolute, UriHelper2) then
Error(InvalidUriErr);
orgName := UriHelper2.AbsolutePath;
if orgName = '/' then
exit('');
if (orgName <> '') and (StrLen(orgName) > 1) then
orgName := CopyStr(orgName, 2);
exit(orgName);
end;
procedure ConstructConnectionStringForSolutionImport(ServerAddress: Text): Text
var
FirstPart: Text;
SecondPart: Text;
FirstLevel: Integer;
begin
FirstLevel := StrPos(ServerAddress, '.');
if FirstLevel = 0 then
Error(CRMConnectionURLWrongErr, CRMProductName.SHORT());
FirstPart := CopyStr(ServerAddress, 1, FirstLevel);
SecondPart := CopyStr(ServerAddress, FirstLevel);
exit(StrSubstNo(ImportSolutionConnectStringTok, FirstPart, SecondPart));
end;
[TryFunction]
[NonDebuggable]
local procedure InitializeCRMConnection(var CRMHelper: DotNet CrmHelper; ConnectionString: SecretText)
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if ConnectionString.IsEmpty() then begin
CRMConnectionSetup.Get();
CRMHelper := CRMHelper.CrmHelper(CRMConnectionSetup.GetSecretConnectionStringWithCredentials().Unwrap());
end else
CRMHelper := CRMHelper.CrmHelper(ConnectionString.Unwrap());
if not TestCRMConnection(CRMHelper) then
ProcessConnectionFailures();
end;
[Scope('OnPrem')]
procedure ProcessConnectionFailures()
var
DotNetExceptionHandler: Codeunit "DotNet Exception Handler";
FaultException: DotNet FaultException;
FileNotFoundException: DotNet FileNotFoundException;
ArgumentNullException: DotNet ArgumentNullException;
CRMHelper: DotNet CrmHelper;
ErrorMessage: Text;
begin
DotNetExceptionHandler.Collect();
if DotNetExceptionHandler.TryCastToType(GetDotNetType(FaultException)) then begin
Session.LogMessage('0000CLW', ConnectionFailureTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(AdminEmailPasswordWrongErr, CRMProductName.SHORT());
end;
if DotNetExceptionHandler.TryCastToType(GetDotNetType(FileNotFoundException)) then begin
Session.LogMessage('0000CLX', ConnectionFailureTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(CRMSolutionFileNotFoundErr);
end;
if DotNetExceptionHandler.TryCastToType(CRMHelper.OrganizationServiceFaultExceptionType()) then begin
Session.LogMessage('0000CLY', ConnectionFailureTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(OrganizationServiceFailureErr)
end;
if DotNetExceptionHandler.TryCastToType(CRMHelper.SystemNetWebException()) then begin
Session.LogMessage('0000CLZ', ConnectionFailureTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(CRMConnectionURLWrongErr, CRMProductName.SHORT());
end;
if DotNetExceptionHandler.CastToType(ArgumentNullException, GetDotNetType(ArgumentNullException)) then
case ArgumentNullException.ParamName of
'cred':
begin
Session.LogMessage('0000CM0', ConnectionFailureTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(AdminEmailPasswordWrongErr, CRMProductName.SHORT());
end;
'Organization Name':
begin
Session.LogMessage('0000CM1', ConnectionFailureTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(CRMConnectionURLWrongErr, CRMProductName.SHORT());
end;
end;
ErrorMessage := DotNetExceptionHandler.GetMessage();
if ErrorMessage <> '' then
if ErrorMessage.ToLower().Contains(TimeoutTxt) then begin
Session.LogMessage('0000EJ7', ConnectionFailureTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Error(RetryAfterTimeoutErr, ErrorMessage);
end;
DotNetExceptionHandler.Rethrow();
end;
local procedure GetIntegrationTableMappingFromCRMID(var IntegrationTableMapping: Record "Integration Table Mapping"; TableID: Integer; CRMID: Guid)
var
CRMAccount: Record "CRM Account";
CRMProduct: Record "CRM Product";
IsHandled: Boolean;
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Synch. Codeunit ID", CODEUNIT::"CRM Integration Table Synch.");
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
if IsCRMTable(TableID) then
IntegrationTableMapping.SetRange("Integration Table ID", TableID)
else begin
IsHandled := false;
OnGetIntegrationTableMappingFromCRMIDOnBeforeFindTableID(IntegrationTableMapping, TableID, CRMID, IsHandled);
if not IsHandled then begin
if (TableID = Database::Vendor) or (TableID = Database::Customer) then begin
CRMAccount.SetRange(AccountId, CRMID);
if CRMAccount.FindFirst() then
case CRMAccount.CustomerTypeCode of
CRMAccount.CustomerTypeCode::Customer:
TableID := Database::Customer;
CRMAccount.CustomerTypeCode::Vendor:
TableID := Database::Vendor;
else
Error(AccountRelationshipTypeNotSupportedErr);
end;
end;
if (TableID = Database::Item) or (TableID = Database::Resource) then begin
CRMProduct.SetRange(ProductId, CRMID);
if CRMProduct.FindFirst() then
case CRMProduct.ProductTypeCode of
CRMProduct.ProductTypeCode::Services:
TableID := Database::Resource;
CRMProduct.ProductTypeCode::SalesInventory:
TableID := Database::Item;
else
Error(ProductTypeNotSupportedErr);
end;
end;
end;
IntegrationTableMapping.SetRange("Table ID", TableID);
end;
if not IntegrationTableMapping.FindFirst() then
Error(IntegrationTableMappingNotFoundErr, IntegrationTableMapping.TableCaption(), GetTableCaption(TableID));
end;
[EventSubscriber(ObjectType::Page, Page::"My Notifications", 'OnInitializingNotificationWithDefaultState', '', false, false)]
local procedure OnInitializingNotificationWithDefaultState();
var
MyNotifications: Record "My Notifications";
begin
MyNotifications.InsertDefault(GetCommonNotificationID(), CommonNotificationNameTxt, CommonNotificationDescriptionTxt, true);
MyNotifications.InsertDefault(GetSkippedNotificationID(), SkippedRecordsNotificationNameTxt, SkippedRecordsNotificationDescriptionTxt, true);
end;
[Scope('OnPrem')]
procedure DisableNotification(HostNotification: Notification)
var
MyNotifications: Record "My Notifications";
NotificationId: Text;
begin
NotificationId := HostNotification.GetData('NotificationId');
if not MyNotifications.Disable(NotificationId) then
MyNotifications.InsertDefault(NotificationId, GetNotificationName(NotificationId), GetNotificationDescription(NotificationId), false);
Session.LogMessage('0000F0J', StrSubstNo(UserDisabledNotificationTxt, GetNotificationName(NotificationId)), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
local procedure GetNotificationName(NotificationId: Guid): Text[128];
begin
case NotificationId of
GetCommonNotificationID():
exit(CommonNotificationNameTxt);
GetSkippedNotificationID():
exit(SkippedRecordsNotificationNameTxt);
end;
exit('');
end;
local procedure GetNotificationDescription(NotificationId: Guid): Text;
begin
case NotificationId of
GetCommonNotificationID():
exit(CommonNotificationDescriptionTxt);
GetSkippedNotificationID():
exit(SkippedRecordsNotificationDescriptionTxt);
end;
exit('');
end;
local procedure GetIntegrationAdminRoleID(): Text
begin
exit('8c8d4f51-a72b-e511-80d9-3863bb349780');
end;
local procedure GetIntegrationUserRoleID(): Text
begin
exit('6f960e32-a72b-e511-80d9-3863bb349780');
end;
local procedure GetSalesProIntegrationRoleId(): Text
begin
exit('2f92fbbd-2d13-461a-895f-27c13f789fdb');
end;
procedure GetCommonNotificationID(): Guid
begin
exit('63428E33-54E4-42A6-82EE-3EEF268340BA');
end;
procedure GetSkippedNotificationID(): Guid
begin
exit('B523E8EA-56B3-4E79-837E-F812CFB74DD4');
end;
local procedure SendRestoredSyncNotification(Counter: Integer)
var
Msg: Text;
begin
if Counter = 1 then
Msg := SyncRestoredMsg
else
Msg := StrSubstNo(SyncMultipleRestoredMsg, Counter);
SendNotification(Msg);
end;
procedure SendResultNotification(RecVariant: Variant): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationSynchJob: Record "Integration Synch. Job";
IntegrationSynchJobErrors: Record "Integration Synch. Job Errors";
RecordRef: RecordRef;
NotificationMessage: Text;
FailureDatetime: DateTime;
SuccessDateTime: DateTime;
begin
RecordRef.GetTable(RecVariant);
if CRMIntegrationRecord.FindByRecordID(RecordRef.RecordId()) then begin
if CRMIntegrationRecord.Skipped then
exit(SendSkippedSyncNotification(CRMIntegrationRecord."Integration ID"));
if CRMIntegrationRecord."Last Synch. CRM Result" = CRMIntegrationRecord."Last Synch. CRM Result"::Failure then
GetNotificationDetailsFromIntegrationSyncJobEntry(
CRMIntegrationRecord."Last Synch. CRM Job ID", RecordRef.RecordId, NotificationMessage, FailureDatetime)
else
SuccessDateTime := CRMIntegrationRecord."Last Synch. CRM Modified On";
if CRMIntegrationRecord."Last Synch. Result" = CRMIntegrationRecord."Last Synch. Result"::Failure then
GetNotificationDetailsFromIntegrationSyncJobEntry(
CRMIntegrationRecord."Last Synch. Job ID", RecordRef.RecordId, NotificationMessage, FailureDatetime)
else
SuccessDateTime := CRMIntegrationRecord."Last Synch. Modified On";
if SuccessDateTime > FailureDatetime then
NotificationMessage := '';
end else begin
Clear(IntegrationSynchJob);
IntegrationSynchJob."Synch. Direction" := IntegrationSynchJob."Synch. Direction"::ToIntegrationTable;
if IntegrationSynchJob.GetErrorForRecordID(RecordRef.RecordId, IntegrationSynchJobErrors) then
NotificationMessage := IntegrationSynchJobErrors.Message;
end;
if NotificationMessage <> '' then
exit(SendNotification(NotificationMessage));
end;
local procedure SendSkippedSyncNotification(IntegrationID: Guid): Boolean
var
MyNotifications: Record "My Notifications";
SyncNotification: Notification;
begin
if not MyNotifications.IsEnabled(GetSkippedNotificationID()) then
exit;
SyncNotification.Id := GetSkippedNotificationID();
SyncNotification.Recall();
SyncNotification.Message(SyncSkippedMsg);
SyncNotification.Scope(NOTIFICATIONSCOPE::LocalScope);
SyncNotification.SetData('IntegrationID', IntegrationID);
SyncNotification.SetData('NotificationId', GetSkippedNotificationID());
SyncNotification.AddAction(DetailsTxt, CODEUNIT::"CRM Integration Management", 'ShowSkippedRecords');
SyncNotification.AddAction(DisableNotificationTxt, Codeunit::"CRM Integration Management", 'DisableNotification');
SyncNotification.Send();
exit(true);
end;
local procedure SendSyncNotification(RecordCounter: array[4] of Integer): Boolean
begin
if RecordCounter[NoOf::Total] = 1 then begin
if RecordCounter[NoOf::Scheduled] = 1 then
exit(SendNotification(SyncNowScheduledMsg));
if RecordCounter[NoOf::Skipped] = 1 then
exit(SendNotification(SyncNowSkippedMsg));
exit(SendNotification(SyncNowFailedMsg));
end;
exit(SendMultipleSyncNotification(RecordCounter));
end;
local procedure SendMultipleSyncNotification(RecordCounter: array[4] of Integer): Boolean
begin
exit(
SendNotification(
StrSubstNo(
SyncMultipleMsg,
RecordCounter[NoOf::Scheduled], RecordCounter[NoOf::Failed],
RecordCounter[NoOf::Skipped], RecordCounter[NoOf::Total])));
end;
local procedure SendUncoupleNotification(RecordCounter: array[4] of Integer): Boolean
begin
if RecordCounter[NoOf::Total] = 1 then begin
if RecordCounter[NoOf::Scheduled] = 1 then
exit(SendNotification(UncoupleScheduledMsg));
if RecordCounter[NoOf::Skipped] = 1 then
exit(SendNotification(UncoupleSkippedMsg));
exit(SendNotification(UncoupleFailedMsg));
end;
exit(SendMultipleUncoupleNotification(RecordCounter));
end;
local procedure SendCouplingNotification(RecordCounter: array[4] of Integer): Boolean
begin
if RecordCounter[NoOf::Total] = 1 then begin
if RecordCounter[NoOf::Scheduled] = 1 then
exit(SendNotification(CouplingScheduledMsg));
if RecordCounter[NoOf::Skipped] = 1 then
exit(SendNotification(CouplingSkippedMsg));
exit(SendNotification(CouplingFailedMsg));
end;
exit(SendMultipleCouplingNotification(RecordCounter));
end;
local procedure SendMultipleUncoupleNotification(RecordCounter: array[4] of Integer): Boolean
begin
exit(
SendNotification(
StrSubstNo(
UncoupleMultipleMsg,
RecordCounter[NoOf::Scheduled], RecordCounter[NoOf::Failed],
RecordCounter[NoOf::Skipped], RecordCounter[NoOf::Total])));
end;
local procedure SendMultipleCouplingNotification(RecordCounter: array[4] of Integer): Boolean
begin
exit(
SendNotification(
StrSubstNo(
CouplingMultipleMsg,
RecordCounter[NoOf::Scheduled], RecordCounter[NoOf::Failed],
RecordCounter[NoOf::Skipped], RecordCounter[NoOf::Total])));
end;
local procedure SendNotification(Msg: Text): Boolean
var
MyNotifications: Record "My Notifications";
SyncNotification: Notification;
begin
if not MyNotifications.IsEnabled(GetCommonNotificationID()) then
exit;
SyncNotification.Id := GetCommonNotificationID();
SyncNotification.Recall();
SyncNotification.Message(Msg);
SyncNotification.Scope(NOTIFICATIONSCOPE::LocalScope);
SyncNotification.AddAction(DetailsTxt, Codeunit::"CRM Integration Management", 'OpenIntegrationSynchronizationJobsFromNotification');
SyncNotification.SetData('NotificationId', GetCommonNotificationID());
SyncNotification.AddAction(DisableNotificationTxt, Codeunit::"CRM Integration Management", 'DisableNotification');
SyncNotification.Send();
exit(true);
end;
[Scope('OnPrem')]
procedure OpenIntegrationSynchronizationJobsFromNotification(HostNotification: Notification)
var
IntegrationSynchJobList: Page "Integration Synch. Job List";
begin
IntegrationSynchJobList.Run();
Session.LogMessage('0000F0K', UserOpenedIntegrationSynchJobListViaNotificationTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
procedure ShowLog(RecId: RecordID)
var
CRMIntegrationRecord: Record "CRM Integration Record";
IntegrationTableMapping: Record "Integration Table Mapping";
begin
GetIntegrationTableMapping(IntegrationTableMapping, RecId);
CRMIntegrationRecord.FindByRecordID(RecId);
IntegrationTableMapping.ShowLog(CRMIntegrationRecord.GetLatestJobIDFilter());
end;
procedure ShowOptionLog(RecId: RecordId)
var
CRMOptionMapping: Record "CRM Option Mapping";
IntegrationTableMapping: Record "Integration Table Mapping";
begin
GetIntegrationTableMapping(IntegrationTableMapping, RecId);
CRMOptionMapping.SetRange("Record ID", RecId);
if CRMOptionMapping.FindFirst() then
IntegrationTableMapping.ShowLog(CRMOptionMapping.GetLatestJobIDFilter())
else
IntegrationTableMapping.ShowLog('');
end;
procedure ShowSkippedRecords(SkippedSyncNotification: Notification)
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMSkippedRecords: Page "CRM Skipped Records";
IntegrationID: Guid;
begin
if Evaluate(IntegrationID, SkippedSyncNotification.GetData('IntegrationID')) then begin
CRMIntegrationRecord.SetRange("Integration ID", IntegrationID);
if CRMIntegrationRecord.FindFirst() then
CRMSkippedRecords.SetRecords(CRMIntegrationRecord);
end;
CRMSkippedRecords.Run();
end;
procedure CoupleCRMEntity(RecordID: RecordID; CRMID: Guid; var Synchronize: Boolean; var Direction: Option): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
CouplingRecordBuffer: Record "Coupling Record Buffer";
begin
CouplingRecordBuffer.Initialize(RecordID);
CouplingRecordBuffer."CRM ID" := CRMID;
if not CouplingRecordBuffer.Insert() then
CouplingRecordBuffer.Modify();
if not IsNullGuid(CouplingRecordBuffer."CRM ID") then begin
CRMIntegrationRecord.CoupleRecordIdToCRMID(CouplingRecordBuffer."NAV Record ID", CouplingRecordBuffer."CRM ID");
if CouplingRecordBuffer.GetPerformInitialSynchronization() then begin
Synchronize := true;
Direction := CouplingRecordBuffer.GetInitialSynchronizationDirection();
PerformInitialSynchronization(CouplingRecordBuffer."NAV Record ID", CouplingRecordBuffer."CRM ID", Direction);
end;
end else
exit(false);
exit(true);
end;
[TryFunction]
[Scope('OnPrem')]
procedure TestCRMConnection(var CRMHelper: DotNet CrmHelper)
begin
CRMHelper.CheckCredentials();
CRMHelper.GetConnectedCrmVersion();
end;
[Scope('OnPrem')]
procedure InitializeProxyVersionList(var TempStack: Record TempStack temporary)
var
CRMHelper: DotNet CrmHelper;
IList: DotNet GenericList1;
i: Integer;
ProxyCount: Integer;
begin
IList := CRMHelper.GetProxyIdList();
ProxyCount := IList.Count();
for i := 0 to ProxyCount - 1 do begin
TempStack.StackOrder := IList.Item(i);
TempStack.Insert();
end;
end;
[Scope('OnPrem')]
procedure GetLastProxyVersionItem(): Integer
var
TempStack: Record TempStack temporary;
begin
InitializeProxyVersionList(TempStack);
TempStack.FindLast();
exit(TempStack.StackOrder);
end;
local procedure GetNotificationDetailsFromIntegrationSyncJobEntry(JobId: Guid; RecRefRecId: RecordID; var NotificationMessage: Text; var FailureDatetime: DateTime)
var
IntegrationSynchJob: Record "Integration Synch. Job";
IntegrationSynchJobErrors: Record "Integration Synch. Job Errors";
begin
if IntegrationSynchJob.Get(JobId) then
if IntegrationSynchJob.GetErrorForRecordID(RecRefRecId, IntegrationSynchJobErrors) then begin
NotificationMessage := IntegrationSynchJobErrors.Message;
FailureDatetime := IntegrationSynchJobErrors."Date/Time";
end
end;
procedure ClearConnectionDisableReason(var CRMConnectionSetup: Record "CRM Connection Setup")
var
Notification: Notification;
begin
Session.LogMessage('0000CM2', ClearDisabledReasonTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
Notification.Id := GetCRMDisabledErrorReasonNotificationId();
Notification.Recall();
Clear(CRMConnectionSetup."Disable Reason");
CRMConnectionSetup.Modify();
end;
procedure GetCRMDisabledErrorReasonNotificationId() CRMDisabledErrorReasonNotificationId: Guid
begin
Evaluate(CRMDisabledErrorReasonNotificationId, CRMDisabledErrorReasonNotificationIdTxt);
exit(CRMDisabledErrorReasonNotificationId);
end;
procedure IsWorkingConnection(): Boolean
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
exit(CRMConnectionSetup.TryReadSystemUsers());
end;
local procedure DisableConnection()
var
CRMConnectionSetup: Record "CRM Connection Setup";
LastError: Text;
begin
Session.LogMessage('0000CM3', DisableIntegrationTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
LastError := GetLastErrorText;
LastError := CopyStr(LastError, StrPos(Format(LastError), ':') + 1, StrLen(LastError));
Message(StrSubstNo(ConnectionBrokenMsg, LastError));
if CRMConnectionSetup.Get() then begin
CRMConnectionSetup.Validate("Is Enabled", false);
CRMConnectionSetup.Validate(
"Disable Reason",
CopyStr(LastError, 1, MaxStrLen(CRMConnectionSetup."Disable Reason")));
CRMConnectionSetup.Modify();
Session.LogMessage('0000CM4', IntegrationDisabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end else
Session.LogMessage('0000CM5', IntegrationNotConfiguredTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
procedure SendConnectionDisabledNotification(DisableReason: Text[250])
var
Notification: Notification;
begin
Notification.Id := GetCRMDisabledErrorReasonNotificationId();
Notification.Message := StrSubstNo(ConnectionDisabledNotificationMsg, DisableReason);
Notification.Scope := NOTIFICATIONSCOPE::LocalScope;
Notification.Send();
end;
[Scope('OnPrem')]
procedure IsItemAvailabilityEnabled(): Boolean
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if CRMConnectionSetup.Get() then
exit(CRMConnectionSetup."Item Availability Enabled");
end;
procedure InitializeCRMSynchStatus()
var
CRMSynchStatus: Record "CRM Synch Status";
begin
if CRMSynchStatus.IsEmpty() then begin
CRMSynchStatus."Last Update Invoice Entry No." := 0;
CRMSynchStatus.Insert();
end;
end;
internal procedure InitializeCRMSynchStatus(var CRMSynchStatus: Record "CRM Synch Status")
begin
if CRMSynchStatus.IsEmpty() then begin
CRMSynchStatus."Last Update Invoice Entry No." := 0;
CRMSynchStatus.Insert();
end;
CRMSynchStatus.Get();
end;
procedure HasUncoupledSelectedUsers(var SelectedCRMSystemuser: Record "CRM Systemuser"): Boolean
var
CRMIntegrationRecord: Record "CRM Integration Record";
RecordID: RecordID;
begin
if SelectedCRMSystemuser.FindSet() then
repeat
if not CRMIntegrationRecord.FindRecordIDFromID(SelectedCRMSystemuser.SystemUserId, Database::"Salesperson/Purchaser", RecordID) then
exit(true);
until SelectedCRMSystemuser.Next() = 0;
exit(false);
end;
[EventSubscriber(ObjectType::Table, Database::"Integration Synch. Job Errors", 'OnIsDataIntegrationEnabled', '', false, false)]
local procedure IsDataIntegrationEnabled(var IsIntegrationEnabled: Boolean)
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if not IsIntegrationEnabled then
IsIntegrationEnabled := CRMConnectionSetup.IsEnabled();
end;
[EventSubscriber(ObjectType::Table, Database::"Integration Synch. Job Errors", 'OnAfterLogSynchError', '', false, false)]
local procedure DisableConnectionOnAfterLongSynchError(IntegrationSynchJobErrors: Record "Integration Synch. Job Errors")
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if not IntegrationSynchJobErrors.Message.Contains('CrmCreate') then
exit;
if not IntegrationSynchJobErrors.Message.Contains('prvCreate') then
exit;
DisableConnection();
if CRMConnectionSetup.Get() then begin
CRMConnectionSetup."Disable Reason" := StrSubstNo(ConnectionDisabledReasonTxt, CRMConnectionSetup."Server Address", CRMConnectionSetup."User Name");
CRMConnectionSetup.Modify();
end;
end;
[EventSubscriber(ObjectType::Table, Database::"Integration Synch. Job Errors", 'OnForceSynchronizeDataIntegration', '', false, false)]
local procedure ForceSynchronizeDataIntegration(LocalRecordID: RecordID; var SynchronizeHandled: Boolean)
begin
if SynchronizeHandled then
exit;
if not (IsCDSIntegrationEnabled() or IsCRMIntegrationEnabled()) then
exit;
UpdateOneNow(LocalRecordID);
SynchronizeHandled := true;
end;
[EventSubscriber(ObjectType::Table, Database::"Integration Synch. Job Errors", 'OnForceSynchronizeRecords', '', false, false)]
local procedure ForceSynchronizeRecords(var LocalRecordIdList: List of [RecordId]; var SynchronizeHandled: Boolean)
var
SelectedCRMIntegrationRecord: Record "CRM Integration Record";
CRMIntegrationRecord: Record "CRM Integration Record";
LocalRecordId: RecordId;
begin
if SynchronizeHandled then
exit;
if not (IsCDSIntegrationEnabled() or IsCRMIntegrationEnabled()) then
exit;
foreach LocalRecordId in LocalRecordIdList do
if CRMIntegrationRecord.FindByRecordID(LocalRecordId) then begin
SelectedCRMIntegrationRecord."CRM ID" := CRMIntegrationRecord."CRM ID";
SelectedCRMIntegrationRecord."Integration ID" := CRMIntegrationRecord."Integration ID";
if SelectedCRMIntegrationRecord.Find() then
SelectedCRMIntegrationRecord.Mark(true);
end;
SelectedCRMIntegrationRecord.MarkedOnly(true);
UpdateMultipleNow(SelectedCRMIntegrationRecord);
SynchronizeHandled := true;
end;
[Scope('OnPrem')]
procedure RegisterAssistedSetup()
var
GuidedExperience: Codeunit "Guided Experience";
GuidedExperienceType: Enum "Guided Experience Type";
Info: ModuleInfo;
AssistedSetupGroup: Enum "Assisted Setup Group";
VideoCategory: Enum "Video Category";
begin
NavApp.GetCurrentModuleInfo(Info);
if not GuidedExperience.Exists(GuidedExperienceType::"Assisted Setup", ObjectType::Page, PAGE::"CRM Connection Setup Wizard") then
GuidedExperience.InsertAssistedSetup(
StrSubstNo(CRMConnectionSetupTxt, CRMProductName.SHORT()), CopyStr(StrSubstNo(CRMConnectionSetupTxt, CRMProductName.SHORT()), 1, 50), '',
0, ObjectType::Page, PAGE::"CRM Connection Setup Wizard", AssistedSetupGroup::Customize, VideoUrlSetupCRMConnectionTxt, VideoCategory::Customize, '');
end;
[Scope('OnPrem')]
procedure IsCRMIntegrationRecord(TableID: Integer): Boolean
var
IntegrationTableMapping: Record "Integration Table Mapping";
isIntegrationRecord: Boolean;
begin
// this is the new event that partners who have integration to custom entities should subscribe to
OnIsCRMIntegrationRecord(TableID, isIntegrationRecord);
if isIntegrationRecord then
exit(true);
IntegrationTableMapping.ReadIsolation := IsolationLevel::ReadUncommitted;
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
exit(IntegrationTableMapping.DoesExistForTable(TableID));
end;
[Scope('OnPrem')]
procedure GetDatabaseTableTriggerSetup(TableID: Integer; var Insert: Boolean; var Modify: Boolean; var Delete: Boolean; var Rename: Boolean)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CDSConnectionSetup: Record "CDS Connection Setup";
Enabled: Boolean;
begin
if (Insert and Modify and Rename) then
exit;
if CompanyName = '' then
exit;
OnEnabledDatabaseTriggersSetup(TableID, Enabled);
if not Enabled then begin
if CDSConnectionSetup.ReadPermission() then
if CDSConnectionSetup.Get() then
if CDSConnectionSetup."Is Enabled" then
Enabled := IsCRMIntegrationRecord(TableID);
if not Enabled then
if CRMConnectionSetup.ReadPermission() then begin
CRMConnectionSetup.SetLoadFields("Is Enabled");
if CRMConnectionSetup.IsEnabled() then
Enabled := IsCRMIntegrationRecord(TableID);
end;
end;
if Enabled then begin
Insert := true;
Modify := true;
Rename := true;
if not Delete then
Delete := false;
end;
end;
[Scope('OnPrem')]
procedure OnDatabaseInsert(RecRef: RecordRef)
begin
ReactivateJobForTable(RecRef.Number);
end;
[Scope('OnPrem')]
procedure OnDatabaseModify(RecRef: RecordRef)
begin
ReactivateJobForTable(RecRef.Number);
end;
[Scope('OnPrem')]
procedure OnDatabaseRename(RecRef: RecordRef; XRecRef: RecordRef)
begin
ReactivateJobForTable(RecRef.Number);
end;
internal procedure ReactivateJobForTable(TableNo: Integer)
var
CRMConnectionSetup: Record "CRM Connection Setup";
CDSConnectionSetup: Record "CDS Connection Setup";
JobQueueEntry: Record "Job Queue Entry";
JobQueueEntryUpdate: Record "Job Queue Entry";
ScheduledTask: Record "Scheduled Task";
IntegrationTableMapping: Record "Integration Table Mapping";
DataUpgradeMgt: Codeunit "Data Upgrade Mgt.";
NewEarliestStartDateTime: DateTime;
Enabled: Boolean;
IsCRMIntRec: Boolean;
RescheduleOffsetInMs: Integer;
begin
if CDSConnectionSetup.Get() then
Enabled := CDSConnectionSetup."Is Enabled";
if not Enabled then
Enabled := CRMConnectionSetup.IsEnabled();
if not Enabled then
exit;
if not CachedIsCRMIntegrationRecord.ContainsKey(TableNo) then begin
IsCRMIntRec := IsCRMIntegrationRecord(TableNo);
CachedIsCRMIntegrationRecord.Add(TableNo, IsCRMIntRec);
if not IsCRMIntRec then
exit;
end;
if not CachedIsCRMIntegrationRecord.Get(TableNo) then
exit;
if not CachedDisableEventDrivenSynchJobReschedule.ContainsKey(TableNo) then begin
IntegrationTableMapping.ReadIsolation := IsolationLevel::ReadUncommitted;
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
if IntegrationTableMapping.FindMappingForTable(TableNo) then begin
CachedDisableEventDrivenSynchJobReschedule.Add(TableNo, IntegrationTableMapping."Disable Event Job Resch.");
if IntegrationTableMapping."Disable Event Job Resch." then
exit;
end;
end;
if CachedDisableEventDrivenSynchJobReschedule.ContainsKey(TableNo) then
if CachedDisableEventDrivenSynchJobReschedule.Get(TableNo) then
exit;
if DataUpgradeMgt.IsUpgradeInProgress() then
exit;
if not UserCanRescheduleJob() then
exit;
JobQueueEntryUpdate.ReadIsolation := IsolationLevel::UpdLock;
JobQueueEntry.Reset();
JobQueueEntry.ReadIsolation := IsolationLevel::ReadUncommitted;
JobQueueEntry.SetLoadFields(Status, "System Task ID", "Object Type to Run", "Object ID to Run", "Record ID to Process", Description);
JobQueueEntry.SetFilter(Status, Format(JobQueueEntry.Status::Ready) + '|' + Format(JobQueueEntry.Status::"On Hold with Inactivity Timeout"));
JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit);
JobQueueEntry.SetFilter("Object ID to Run", '%1|%2|%3|%4|%5', Codeunit::"Integration Synch. Job Runner", Codeunit::"CRM Statistics Job", Codeunit::"Int. Coupling Job Runner",
Codeunit::"Int. Uncouple Job Runner", Codeunit::"CRM Archived Sales Orders Job");
JobQueueEntry.SetRange("Recurring Job", true);
JobQueueEntry.SetFilter("Parameter String", '%1|%2', '', Format(TableNo));
JobQueueEntry.SetRange(Scheduled, true);
if JobQueueEntry.IsEmpty() then
exit;
if not JobQueueEntry.FindSet() then
exit;
// reschedule the synch job in 30 seconds from now, to give time to the user to make further changes
RescheduleOffSetInMs := 30000;
OnBeforeRescheduleJobQueueEntries(TableNo, RescheduleOffSetInMs);
if RescheduleOffSetInMs < 5000 then
RescheduleOffSetInMs := 5000;
ScheduledTask.ReadIsolation := IsolationLevel::ReadUncommitted;
repeat
// The rescheduled task might start while the current transaction is not committed yet.
// Therefore the task will restart with a delay to lower a risk of use of "old" data.
// If the task is scheduled to run soon (in 60 seconds from now) we don't reschedule
NewEarliestStartDateTime := CurrentDateTime() + RescheduleOffsetInMs;
if DoesJobActOnTable(JobQueueEntry, TableNo) then
if ScheduledTask.Get(JobQueueEntry."System Task ID") then
if (NewEarliestStartDateTime + RescheduleOffSetInMs) < ScheduledTask."Not Before" then
if TaskScheduler.SetTaskReady(JobQueueEntry."System Task ID", NewEarliestStartDateTime) then begin
JobQueueEntryUpdate.ID := JobQueueEntry.ID;
if JobQueueEntryUpdate.GetRecLockedExtendedTimeout() then
if JobQueueEntryUpdate.Status in [JobQueueEntry.Status::Ready, JobQueueEntry.Status::"On Hold with Inactivity Timeout"] then begin
JobQueueEntryUpdate.Status := JobQueueEntry.Status::Ready;
JobQueueEntryUpdate."Earliest Start Date/Time" := NewEarliestStartDateTime;
JobQueueEntryUpdate."Parameter String" := Format(TableNo);
JobQueueEntryUpdate.Modify();
Session.LogMessage('0000JAV', StrSubstNo(RescheduledTaskTxt, Format(ScheduledTask.ID), Format(JobQueueEntry.ID), JobQueueEntry.Description, Format(ScheduledTask."Not Before")), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
end;
end;
until JobQueueEntry.Next() = 0;
end;
local procedure DoesJobActOnTable(JobQueueEntry: Record "Job Queue Entry"; TableNo: Integer): Boolean
var
IntegrationTableMapping: Record "Integration Table Mapping";
RecRef: RecordRef;
CacheKey: Text;
ActsOnTable: Boolean;
begin
if (JobQueueEntry."Object Type to Run" = JobQueueEntry."Object Type to Run"::Codeunit) and (JobQueueEntry."Object ID to Run" = Codeunit::"CRM Archived Sales Orders Job") then
exit(TableNo = Database::"Sales Header Archive");
CacheKey := CompanyName + '$' + Format(JobQueueEntry.ID) + '$' + Format(TableNo);
if CachedDoesJobActOnTable.ContainsKey(CacheKey) then
exit(CachedDoesJobActOnTable.Get(CacheKey));
if JobQueueEntry."Record ID to Process".TableNo = DATABASE::"Integration Table Mapping" then
if RecRef.Get(JobQueueEntry."Record ID to Process") then begin
RecRef.SetTable(IntegrationTableMapping);
if IntegrationTableMapping."Table ID" = Database::"Sales Header" then
ActsOnTable := TableNo = Database::"Sales Line"
else
ActsOnTable := IntegrationTableMapping."Table ID" = TableNo;
end;
CachedDoesJobActOnTable.Add(CacheKey, ActsOnTable);
exit(ActsOnTable);
end;
[Scope('OnPrem')]
procedure UserCanRescheduleJob(): Boolean
var
JobQueueEntry: Record "Job Queue Entry";
DummyErrorMessageRegister: Record "Error Message Register";
DummyErrorMessage: Record "Error Message";
begin
if not JobQueueEntry.ReadPermission then
exit(false);
if not JobQueueEntry.WritePermission then
exit(false);
if not DummyErrorMessageRegister.WritePermission then
exit(false);
if not DummyErrorMessage.WritePermission then
exit(false);
if not TaskScheduler.CanCreateTask() then
exit(false);
exit(true);
end;
internal procedure FindCoupledToCRMField(var RecRef: RecordRef; var CoupledToCRMFldRef: FieldRef): Boolean
var
Field: Record "Field";
Customer: Record Customer;
TableNo: Integer;
FieldNo: Integer;
CoupledFieldNo: Integer;
IsHandled: Boolean;
FieldFilterSearchTok: Label '*%1*', Locked = true;
begin
TableNo := RecRef.Number();
IsHandled := false;
CoupledFieldNo := 0;
OnBeforeFindCoupledToCRMField(TableNo, IsHandled, CoupledFieldNo);
if IsHandled then begin
if CoupledFieldNo = 0 then
exit(false);
CoupledToCRMFldRef := RecRef.Field(CoupledFieldNo);
exit(true);
end;
if CachedCoupledToCRMFieldNo.ContainsKey(TableNo) then
FieldNo := CachedCoupledToCRMFieldNo.Get(TableNo)
else begin
Field.SetRange(TableNo, TableNo);
Field.SetRange(Type, Field.Type::Boolean);
Field.SetRange(FieldName, Customer.FieldName("Coupled to Dataverse"));
if Field.FindFirst() then
FieldNo := Field."No."
else begin
Field.SetFilter(FieldName, StrSubstNo(FieldFilterSearchTok, Customer.FieldName("Coupled to Dataverse")));
if Field.FindFirst() then
FieldNo := Field."No."
else
FieldNo := 0;
end;
CachedCoupledToCRMFieldNo.Add(TableNo, FieldNo);
end;
if FieldNo = 0 then
exit(false);
CoupledToCRMFldRef := RecRef.Field(FieldNo);
exit(true);
end;
procedure EnableUnitGroupMapping(): Guid
var
JobQueueEntry: Record "Job Queue Entry";
begin
AdjustUnitGroupCRMConnectionSetup();
if UserCanRescheduleJob() then begin
JobQueueEntry.Init();
JobQueueEntry."Earliest Start Date/Time" := CurrentDateTime() + 1000;
JobQueueEntry."Object Type to Run" := JobQueueEntry."Object Type to Run"::Codeunit;
JobQueueEntry."Object ID to Run" := Codeunit::"CRM Update Unit Group Mapping";
JobQueueEntry."Maximum No. of Attempts to Run" := 3;
JobQueueEntry."Rerun Delay (sec.)" := 60;
JobQueueEntry."Run in User Session" := false;
JobQueueEntry.Description := UpdateUnitGroupMappingJQEDescriptionTxt;
Codeunit.Run(Codeunit::"Job Queue - Enqueue", JobQueueEntry);
exit(JobQueueEntry.ID);
end;
end;
procedure DisableUnitGroupMapping()
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMFullSyncReviewLine: Record "CRM Full Synch. Review Line";
begin
CRMIntegrationRecord.SetFilter("Table ID", '%1', Database::"Unit Group");
if not CRMIntegrationRecord.IsEmpty() then
CRMIntegrationRecord.DeleteAll();
CRMIntegrationRecord.SetFilter("Table ID", '%1', Database::"Item Unit of Measure");
if not CRMIntegrationRecord.IsEmpty() then
CRMIntegrationRecord.DeleteAll();
CRMIntegrationRecord.SetFilter("Table ID", '%1', Database::"Resource Unit of Measure");
if not CRMIntegrationRecord.IsEmpty() then
CRMIntegrationRecord.DeleteAll();
if CRMFullSyncReviewLine.Get('UNIT GROUP') then
CRMFullSyncReviewLine.Delete();
if CRMFullSyncReviewLine.Get('ITEM UOM') then
CRMFullSyncReviewLine.Delete();
if CRMFullSyncReviewLine.Get('RESOURCE UOM') then
CRMFullSyncReviewLine.Delete();
RemoveIntegrationTableMapping(Database::"Unit Group", Database::"CRM Uomschedule");
RemoveIntegrationTableMapping(Database::"Item Unit of Measure", Database::"CRM Uom");
RemoveIntegrationTableMapping(Database::"Resource Unit of Measure", Database::"CRM Uom");
end;
procedure AdjustUnitGroupCRMConnectionSetup()
var
CRMIntegrationRecord: Record "CRM Integration Record";
CRMFullSyncReviewLine: Record "CRM Full Synch. Review Line";
begin
CRMIntegrationRecord.SetFilter("Table ID", '%1', Database::"Unit of Measure");
if not CRMIntegrationRecord.IsEmpty() then
CRMIntegrationRecord.DeleteAll();
if CRMFullSyncReviewLine.Get('UNIT OF MEASURE') then
CRMFullSyncReviewLine.Delete();
RemoveIntegrationTableMapping(Database::"Unit of Measure", Database::"CRM Uomschedule");
end;
local procedure RemoveIntegrationTableMapping(TableId: Integer; IntTableId: Integer) JobExisted: Boolean;
var
IntegrationTableMapping: Record "Integration Table Mapping";
JobQueueEntry: Record "Job Queue Entry";
begin
IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
IntegrationTableMapping.SetRange("Table ID", TableId);
IntegrationTableMapping.SetRange("Integration Table ID", IntTableId);
IntegrationTableMapping.SetRange("Delete After Synchronization", false);
if IntegrationTableMapping.FindSet() then
repeat
JobQueueEntry.SetRange("Record ID to Process", IntegrationTableMapping.RecordId());
if not JobQueueEntry.IsEmpty() then begin
JobExisted := true;
JobQueueEntry.DeleteAll(true);
end;
IntegrationTableMapping.Delete(true);
until IntegrationTableMapping.Next() = 0;
end;
procedure UpdateItemUnitGroup()
var
Item: Record Item;
UnitGroup: Record "Unit Group";
CommitCounter: Integer;
begin
Item.SetLoadFields(SystemId, "No.", "Unit Group Exists");
Item.SetRange("Unit Group Exists", false);
if Item.FindSet() then
repeat
UnitGroup.SetRange("Source Id", Item.SystemId);
UnitGroup.SetRange("Source Type", UnitGroup."Source Type"::Item);
if UnitGroup.IsEmpty() then begin
UnitGroup.Init();
UnitGroup."Source Id" := Item.SystemId;
UnitGroup."Source No." := Item."No.";
UnitGroup."Source Type" := UnitGroup."Source Type"::Item;
UnitGroup.Insert();
CommitCounter += 1;
end;
if CommitCounter = 1000 then begin
Commit();
CommitCounter := 0;
end;
until Item.Next() = 0;
end;
procedure UpdateResourceUnitGroup()
var
Resource: Record Resource;
UnitGroup: Record "Unit Group";
CommitCounter: Integer;
begin
Resource.SetLoadFields(SystemId, "No.", "Unit Group Exists");
Resource.SetRange("Unit Group Exists", false);
if Resource.FindSet() then
repeat
UnitGroup.SetRange("Source Id", Resource.SystemId);
UnitGroup.SetRange("Source Type", UnitGroup."Source Type"::Resource);
if UnitGroup.IsEmpty() then begin
UnitGroup.Init();
UnitGroup."Source Id" := Resource.SystemId;
UnitGroup."Source No." := Resource."No.";
UnitGroup."Source Type" := UnitGroup."Source Type"::Resource;
UnitGroup.Insert();
CommitCounter += 1;
end;
if CommitCounter = 1000 then begin
Commit();
CommitCounter := 0;
end;
until Resource.Next() = 0;
end;
procedure IsUnitGroupMappingEnabled() FeatureEnabled: Boolean;
var
CRMConnectionSetup: Record "CRM Connection Setup";
begin
if not CRMConnectionSetup.Get() then
exit(false);
exit(CRMConnectionSetup."Unit Group Mapping Enabled");
end;
procedure MultipleCompanyLearnMore(var Notification: Notification)
begin
HyperLink(MultipleCompanyLinkLbl);
end;
procedure IsIntegrationRecordChild(TableID: Integer) ReturnValue: Boolean
var
Handled: Boolean;
begin
OnIsIntegrationRecordChild(TableID, Handled, ReturnValue);
if Handled then
exit(ReturnValue);
exit(TableID in
[Database::"Sales Line",
Database::"Currency Exchange Rate",
Database::"Sales Invoice Line",
Database::"Sales Cr.Memo Line",
Database::"Contact Alt. Address",
Database::"Contact Profile Answer",
Database::"Dimension Value",
Database::"Rlshp. Mgt. Comment Line",
Database::"Vendor Bank Account"]);
end;
local procedure GetOptionIdFieldRef(RecRef: RecordRef): FieldRef
var
FieldRef: FieldRef;
KeyRef: KeyRef;
begin
KeyRef := RecRef.KeyIndex(1);
if KeyRef.FieldCount() <> 1 then
exit(FieldRef);
FieldRef := RecRef.FieldIndex(1);
exit(FieldRef);
end;
procedure GetFieldServiceIntegrationAppSourceLink(): Text
begin
exit(FSIntegrationAppSourceLinkTxt);
end;
procedure IsFieldServiceIntegrationAppInstalled(): Boolean
var
ExtensionManagement: Codeunit "Extension Management";
begin
exit(ExtensionManagement.IsInstalledByAppId(FieldServiceIntegrationAppIdLbl));
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Int. Option Synch. Invoke", 'OnDeletionConflictDetected', '', false, false)]
local procedure HandleOnOptionDeletionConflictDetected(var IntegrationTableMapping: Record "Integration Table Mapping"; var SourceRecordRef: RecordRef; var DeletionConflictHandled: Boolean)
var
CRMOptionMapping: Record "CRM Option Mapping";
begin
if DeletionConflictHandled then
exit;
if not (IsCDSIntegrationEnabled() or IsCRMIntegrationEnabled()) then
exit;
if IntegrationTableMapping."Deletion-Conflict Resolution" in
[IntegrationTableMapping."Deletion-Conflict Resolution"::"Remove Coupling", IntegrationTableMapping."Deletion-Conflict Resolution"::"Restore Records"] then
if SourceRecordRef.Number = IntegrationTableMapping."Table ID" then
DeletionConflictHandled := RemoveOptionMappingFromRecRef(SourceRecordRef)
else
DeletionConflictHandled := RemoveOptionMapping(IntegrationTableMapping."Integration Table ID", IntegrationTableMapping."Integration Table UID Fld. No.", CRMOptionMapping.GetRecordRefOptionId(SourceRecordRef));
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeGetIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; TableID: Integer)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeGetIntegrationTableMappingFromCRMRecord(var IntegrationTableMapping: Record "Integration Table Mapping"; RecRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeHandleCustomIntegrationTableMapping(var IsHandled: Boolean; IntegrationTableMappingName: Code[20]; var IntegrationTableMapping: Record "Integration Table Mapping"; EnqueueJobQueEntries: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeSynchronyzeNowQuestion(var AllowedDirection: Integer; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnIsCRMIntegrationRecord(TableID: Integer; var isIntegrationRecord: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnEnabledDatabaseTriggersSetup(TableID: Integer; var Enabled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnGetTableIdFromCRMOption(RecRef: RecordRef; var TableId: Integer)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnIsCRMTable(TableID: Integer; var IsCRMTable: Boolean; var Handled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnIsIntegrationRecordChild(TableID: Integer; var Handled: Boolean; var ReturnValue: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeOpenCoupledNavRecordPage(CRMID: Guid; CRMEntityTypeName: Text; var Result: Boolean; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeFindCoupledToCRMField(TableNo: Integer; var IsHandled: Boolean; var CoupledFieldNo: Integer)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterGetCRMEntityUrlFromCRMID(CRMEntityUrlTemplateTxt: Text; NewestUIAppIdParameterTxt: Text; TableId: Integer; CRMId: Guid; var CRMEntityUrl: Text; CRMTableId: Integer)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnGetIntegrationTableMappingFromCRMIDOnBeforeFindTableID(var IntegrationTableMapping: Record "Integration Table Mapping"; var TableID: Integer; CRMID: Guid; var IsHandled: Boolean)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeGetIntegrationTableMappingWithRecordId(var IntegrationTableMapping: Record "Integration Table Mapping"; BCRecordId: RecordId; var TableID: Integer)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterGetIntegrationTableMappingFromCRMRecordBeforeFindRecord(var IntegrationTableMapping: Record "Integration Table Mapping"; RecRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterAddExtraFieldMappings(IntegrationTableMappingName: Code[20])
begin
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeRescheduleJobQueueEntries(TableNo: Integer; var RescheduleOffSetInMs: Integer)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnRemoveCouplingOnBeforeGetIntegrationTableMappingForUncoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; LocalRecordRef: RecordRef)
begin
end;
[IntegrationEvent(false, false)]
local procedure OnMatchBasedCouplingOnBeforeGetIntegrationTableMappingForMatchBasedCoupling(var IntegrationTableMapping: Record "Integration Table Mapping"; LocalRecordRef: RecordRef)
begin
end;
}