Table 5330 CRM Connection Setup, source in 29

Source29

src/Layers/W1/BaseApp/Integration/D365Sales/CRMConnectionSetup.Table.al1510 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.D365Sales;

using Microsoft.CRM.Contact;
using Microsoft.CRM.Outlook;
using Microsoft.CRM.Team;
using Microsoft.Finance.Currency;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Integration.Dataverse;
using Microsoft.Integration.SyncEngine;
using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Customer;
using Microsoft.Sales.Setup;
using Microsoft.Utilities;
using System;
using System.Environment;
using System.Environment.Configuration;
using System.Security.Encryption;
using System.Threading;

table 5330 "CRM Connection Setup"
{
    Caption = 'Microsoft Dynamics 365 Connection Setup';
    Permissions = tabledata "CRM Connection Setup" = r;
    InherentEntitlements = rX;
    InherentPermissions = rX;
    DataClassification = CustomerContent;

    fields
    {
        field(1; "Primary Key"; Code[20])
        {
            AllowInCustomizations = Never;
            Caption = 'Primary Key';
        }
        field(2; "Server Address"; Text[250])
        {
            Caption = 'Dynamics 365 Sales URL';
            ToolTip = 'Specifies the URL of the Dynamics 365 Sales server that hosts the Dynamics 365 Sales solution that you want to connect to.';

            trigger OnValidate()
            var
                EnvironmentInfo: Codeunit "Environment Information";
            begin
                CRMIntegrationManagement.CheckModifyCRMConnectionURL("Server Address");

                if "Server Address" <> '' then
                    if EnvironmentInfo.IsSaaS() or (StrPos("Server Address", '.dynamics.com') > 0) then
                        "Authentication Type" := "Authentication Type"::Office365
                    else
                        "Authentication Type" := "Authentication Type"::AD;
                UpdateConnectionString();
            end;
        }
        field(3; "User Name"; Text[250])
        {
            Caption = 'User Name';
            ToolTip = 'Specifies the user name of a Dynamics 365 Sales account.';
            DataClassification = EndUserIdentifiableInformation;

            trigger OnValidate()
            begin
                "User Name" := DelChr("User Name", '<>');
                CheckUserName();
                UpdateDomainName();
                UpdateConnectionString();
            end;
        }
        field(4; "User Password Key"; Guid)
        {
            Caption = 'User Password Key';
            DataClassification = EndUserPseudonymousIdentifiers;

            trigger OnValidate()
            begin
                if not IsTemporary() then
                    if "User Password Key" <> xRec."User Password Key" then
                        xRec.DeletePassword();
            end;
        }
        field(59; "Restore Connection"; Boolean)
        {
            Caption = 'Restore Connection';
        }
        field(60; "Is Enabled"; Boolean)
        {
            Caption = 'Is Enabled';
            ToolTip = 'Specifies if the connection to Dynamics 365 Sales is enabled. When you check this checkbox, you will be prompted to sign-in to Dataverse with an administrator user account. The account will be used one time to give consent to, install and configure applications and components that the integration requires.';

            trigger OnValidate()
            var
                CRMIntegrationTelemetry: Codeunit "CRM Integration Telemetry";
                CRMSetupDefaults: Codeunit "CRM Setup Defaults";
                CDSIntTableSubscriber: Codeunit "CDS Int. Table. Subscriber";
            begin
                if "Is Enabled" then
                    if "Unit Group Mapping Enabled" then
                        CRMIntegrationManagement.EnableUnitGroupMapping()
                    else
                        CRMIntegrationManagement.DisableUnitGroupMapping();

                EnableCRMConnection();
                RefreshDataFromCRM();
                if "Is Enabled" then begin
                    CRMIntegrationTelemetry.LogTelemetryWhenConnectionEnabled();
                    TestIntegrationUserRequirements();
                    CRMSetupDefaults.ResetSalesOrderMappingConfiguration(Rec);
                    CDSIntTableSubscriber.SyncDocumentOptionSets();
                    SetUseNewestUI();
                    Session.LogMessage('0000CM8', CRMConnEnabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
                end else begin
                    CRMIntegrationManagement.RemoveCRMNAVConnectionUrl("Dynamics NAV URL");
                    CRMIntegrationTelemetry.LogTelemetryWhenConnectionDisabled();
                    Session.LogMessage('0000CM9', CRMConnDisabledTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
                end;
            end;
        }
#if not CLEANSCHEMA26
        field(61; "Is User Mapping Required"; Boolean)
        {
            ObsoleteReason = 'This functionality is not in use and not supported';
            ObsoleteTag = '26.0';
            ObsoleteState = Removed;
            Caption = 'Business Central Users Must Map to Dynamics 365 Sales Users';

        }
        field(62; "Is User Mapped To CRM User"; Boolean)
        {
            ObsoleteTag = '26.0';
            ObsoleteState = Removed;
            ObsoleteReason = 'This functionality is not in use and not supported';
            Caption = 'Is User Mapped To CRM User';
        }
#endif
        field(63; "CRM Version"; Text[30])
        {
            Caption = 'CRM Version';
        }
        field(64; "Use Newest UI"; Boolean)
        {
            Caption = 'Use Newest UI';
            ToolTip = 'Specifies that coupled Dynamics 365 Sales entities should open in Sales Hub.';
            DataClassification = SystemMetadata;

            trigger OnValidate()
            begin
                if "Use Newest UI" = false then
                    "Newest UI AppModuleId" := '';

                if "Use Newest UI" = true then
                    "Newest UI AppModuleId" := NewestUIAppModuleId();
            end;
        }
        field(65; "Newest UI AppModuleId"; Text[50])
        {
            Caption = 'Newest UI AppModuleId';
            DataClassification = SystemMetadata;
        }
        field(66; "Is S.Order Integration Enabled"; Boolean)
        {
            Caption = 'Is S.Order Integration Enabled';
            ToolTip = 'Specifies that it is possible for Dynamics 365 Sales users to submit sales orders that can then be viewed and imported in Dynamics 365.';

            trigger OnValidate()
            begin
                if "Is S.Order Integration Enabled" then
                    if Confirm(StrSubstNo(SetCRMSOPEnableNoCredsReqQst, PRODUCTNAME.Short())) then
                        SetCRMSOPEnabled()
                    else
                        Error('')
                else
                    SetCRMSOPDisabled();
                RefreshDataFromCRM();

                if "Is S.Order Integration Enabled" then
                    Message(SetCRMSOPEnableConfirmMsg, CRMProductName.SHORT())
                else
                    Message(SetCRMSOPDisableConfirmMsg, CRMProductName.SHORT());
            end;
        }
        field(67; "Is CRM Solution Installed"; Boolean)
        {
            Caption = 'Is CRM Solution Installed';
            ToolTip = 'Specifies if the Integration Solution is installed and configured in Dynamics 365 Sales. You cannot change this setting.';
        }
#if not CLEANSCHEMA26
        field(68; "Is Enabled For User"; Boolean)
        {
            ObsoleteTag = '26.0';
            ObsoleteState = Removed;
            ObsoleteReason = 'Use field "Is Enabled" instead.';
            Caption = 'Is Enabled For User';
        }
#endif
        field(69; "Dynamics NAV URL"; Text[250])
        {
            Caption = 'Dynamics NAV URL';
            ToolTip = 'Specifies the URL to the Business Central web client. From records in Dynamics 365 Sales, such as an account or product, users can open a corresponding (coupled) record in Business Central. Set this field to the URL of the Business Central web client instance to use.';

            trigger OnValidate()
            begin
                CRMIntegrationManagement.SetCRMNAVConnectionUrl("Dynamics NAV URL");
            end;
        }
        field(75; "Default CRM Price List ID"; Guid)
        {
            Caption = 'Default CRM Price List ID';
        }
        field(76; "Proxy Version"; Integer)
        {
            Caption = 'Proxy Version';
            ToolTip = 'Specifies the Microsoft Dynamics 365 (CRM) software development kit version that is used to connect to Dynamics 365 Sales.';
            DataClassification = SystemMetadata;

            trigger OnValidate()
            begin
                UpdateProxyVersionInConnectionString();
            end;
        }
        field(80; "Auto Create Sales Orders"; Boolean)
        {
            Caption = 'Automatically Create Sales Orders';
            ToolTip = 'Specifies that sales orders will be created automatically from sales orders that are submitted in Dynamics 365 Sales.';
            DataClassification = SystemMetadata;

            trigger OnValidate()
            var
                CRMSetupDefaults: Codeunit "CRM Setup Defaults";
            begin
                if "Auto Create Sales Orders" then
                    CRMSetupDefaults.RecreateAutoCreateSalesOrdersJobQueueEntry(DoReadCRMData())
                else
                    CRMSetupDefaults.DeleteAutoCreateSalesOrdersJobQueueEntry();
            end;
        }
        field(81; "Auto Process Sales Quotes"; Boolean)
        {
            Caption = 'Automatically Process Sales Quotes';
            ToolTip = 'Specifies that sales quotes will be automatically processed on sales quotes creation/revision/winning submitted in Dynamics 365 Sales quotes entities.';
            DataClassification = SystemMetadata;

            trigger OnValidate()
            var
                CRMSetupDefaults: Codeunit "CRM Setup Defaults";
            begin
                if "Auto Process Sales Quotes" then
                    CRMSetupDefaults.RecreateAutoProcessSalesQuotesJobQueueEntry(DoReadCRMData())
                else
                    CRMSetupDefaults.DeleteAutoProcessSalesQuotesJobQueueEntry();
            end;
        }
        field(118; CurrencyDecimalPrecision; Integer)
        {
            Caption = 'Currency Decimal Precision';
            Description = 'Number of decimal places that can be used for currency.';
        }
        field(124; BaseCurrencyId; Guid)
        {
            Caption = 'Currency';
            Description = 'Unique identifier of the base currency of the organization.';
            TableRelation = "CRM Transactioncurrency".TransactionCurrencyId;
        }
        field(133; BaseCurrencyPrecision; Integer)
        {
            Caption = 'Base Currency Precision';
            Description = 'Number of decimal places that can be used for the base currency.';
            MaxValue = 4;
            MinValue = 0;
        }
        field(134; BaseCurrencySymbol; Text[5])
        {
            Caption = 'Base Currency Symbol';
            Description = 'Symbol used for the base currency.';
        }
        field(135; "Authentication Type"; Option)
        {
            Caption = 'Authentication Type';
            ToolTip = 'Specifies the authentication type that will be used to authenticate with Dynamics 365 Sales';
            OptionCaption = 'OAuth 2.0,AD,IFD,OAuth';
            OptionMembers = Office365,AD,IFD,OAuth;

            trigger OnValidate()
            begin
                case "Authentication Type" of
                    "Authentication Type"::Office365:
                        Domain := '';
                    "Authentication Type"::AD:
                        UpdateDomainName();
                end;
                UpdateConnectionString();
            end;
        }
        field(136; "Connection String"; Text[250])
        {
            Caption = 'Connection String';
        }
        field(137; Domain; Text[250])
        {
            Caption = 'Domain';
            ToolTip = 'Specifies the domain name of your Dynamics 365 Sales deployment.';
            DataClassification = OrganizationIdentifiableInformation;
            Editable = false;
        }
        field(138; "Server Connection String"; BLOB)
        {
            Caption = 'Server Connection String';
        }
        field(139; "Disable Reason"; Text[250])
        {
            Caption = 'Disable Reason';
        }
        field(140; "Item Availability Enabled"; Boolean)
        {
            Caption = 'Item Availability Enabled';
            ToolTip = 'Specifies that item availability job queue entry will be scheduled.';

            trigger OnValidate()
            var
                CRMSetupDefaults: Codeunit "CRM Setup Defaults";
            begin
                if "Item Availability Enabled" then begin
                    CRMSetupDefaults.CreateInventoryQuantityFieldMapping();
                    CRMSetupDefaults.RecreateItemAvailabilityJobQueueEntry(DoReadCRMData());
                end else
                    CRMSetupDefaults.DeleteItemAvailabilityJobQueueEntry();
            end;
        }
        field(141; "Bidirectional Sales Order Int."; Boolean)
        {
            Caption = 'Bidirectional Sales Order Int.';
            ToolTip = 'Specifies that it is possible to synchronize Sales Order bidirectionally. This feature will also enable Archiving Orders.';

            trigger OnValidate()
            var
                SalesReceivablesSetup: Record "Sales & Receivables Setup";
                CRMSetupDefaults: Codeunit "CRM Setup Defaults";
            begin
                if "Bidirectional Sales Order Int." then begin
                    if SalesReceivablesSetup.Get() then
                        if not SalesReceivablesSetup."Archive Orders" then begin
                            SalesReceivablesSetup."Archive Orders" := true;
                            SalesReceivablesSetup.Modify();
                        end;
                    CRMSetupDefaults.ResetSalesOrderMappingConfiguration(Rec);
                end else
                    CRMSetupDefaults.ResetSalesOrderMappingConfiguration(Rec);
            end;
        }
        field(151; "Unit Group Mapping Enabled"; Boolean)
        {
            Caption = 'Unit Group Mapping Enabled';
            ToolTip = 'Specifies that unit group mapping is enabled.';
            InitValue = true;

            trigger OnValidate()
            var
                CRMIntegrationManagement: Codeunit "CRM Integration Management";
                JobQueueEntry: Record "Job Queue Entry";
                JobQueueEntryId: Guid;
            begin
                if "Unit Group Mapping Enabled" then
                    if Confirm(UnitGroupMappingEnabledConfirmLbl) then begin
                        JobQueueEntryId := CRMIntegrationManagement.EnableUnitGroupMapping();
                        if IsNullGuid(JobQueueEntryId) then
                            Error(ScheduleJobFailedLbl);
                        if Confirm(UnitGroupMappingEnabledLbl) then begin
                            JobQueueEntry.SetRange(ID, JobQueueEntryId);
                            if JobQueueEntry.FindFirst() then
                                Page.Run(Page::"Job Queue Entry Card", JobQueueEntry);
                        end else
                            exit;
                    end else
                        Error('');
            end;
        }
    }

    keys
    {
        key(Key1; "Primary Key")
        {
            Clustered = true;
        }
    }

    fieldgroups
    {
    }

    trigger OnModify()
    begin
        if IsTemporary() then
            exit;
        if "User Password Key" <> xRec."User Password Key" then
            xRec.DeletePassword();
    end;

    trigger OnDelete()
    begin
        if IsTemporary() then
            exit;
        DeletePassword();
    end;

    var
        CRMIntegrationManagement: Codeunit "CRM Integration Management";
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
        CRMProductName: Codeunit "CRM Product Name";
        IsolatedStorageManagement: Codeunit "Isolated Storage Management";
        TempUserPassword: SecretText;

        ConnectionErr: Label 'The connection setup cannot be validated. Verify the settings and try again.\Detailed error description: %1.', Comment = '%1 Error message from the provider (.NET exception message)';
        ConnectionStringFormatTok: Label 'Url=%1; UserName=%2; Password=%3; ProxyVersion=%4; %5', Locked = true;
        ConnectionSuccessMsg: Label 'The connection test was successful. The settings are valid.';
        CannotResolveUserFromConnectionSetupErr: Label 'The %1 user that is specified in the CRM connection setup does not exist.', Comment = '%1 = CRM product name';
        DetailsMissingErr: Label 'A %1 URL and user name are required to enable a connection.', Comment = '%1 = CRM product name';
        MissingUsernameTok: Label '{USER}', Locked = true;
        MissingPasswordTok: Label '{PASSWORD}', Locked = true;
        AccessTokenTok: Label 'AccessToken', Locked = true;
        ClientSecretConnectionStringFormatTxt: Label '%1; Url=%2; ClientId=%3; ClientSecret=%4; ProxyVersion=%5', Locked = true;
        ClientSecretAuthTxt: Label 'AuthType=ClientSecret', Locked = true;
        ClientSecretTok: Label '{CLIENTSECRET}', Locked = true;
        CertificateConnectionStringFormatTxt: Label '%1; Url=%2; ClientId=%3; Certificate=%4; ProxyVersion=%5', Locked = true;
        CertificateAuthTxt: Label 'AuthType=Certificate', Locked = true;
        CertificateTok: Label '{CERTIFICATE}', Locked = true;
        ClientIdTok: Label '{CLIENTID}', Locked = true;
        UserCRMSetupTxt: Label 'User CRM Setup';
        LCYMustMatchBaseCurrencyErr: Label 'Your local currency code %1 does not match any ISO Currency Code in the Dataverse currency table. To continue, make sure that the local currency code in General Ledger Setup complies with the ISO standard and create a currency in Dataverse currency table that uses it as ISO Currency Code.', Comment = '%1 - ISO currency code';
        UserNameMustIncludeDomainErr: Label 'The user name must include the domain when the authentication type is set to Active Directory.';
        UserNameMustBeEmailErr: Label 'The user name must be a valid email address when the authentication type is set to Office 365.';
        ConnectionStringPwdPlaceHolderMissingErr: Label 'The connection string must include the password placeholder {PASSWORD}.';
        ConnectionStringPwdOrClientSecretPlaceHolderMissingErr: Label 'The connection string must include either the password placeholder {PASSWORD}, the client secret placeholder {CLIENTSECRET} or the certificate placeholder {CERTIFICATE}.', Comment = '{PASSWORD}, {CERTIFICATE} and {CLIENTSECRET} are locked strings - do not translate them.';
        CannotDisableSalesOrderIntErr: Label 'You cannot disable CRM sales order integration when a CRM sales order has the Submitted status.';
        SetCRMSOPEnableNoCredsReqQst: Label 'Enabling Sales Order Integration will allow you to create %1 Sales Orders from Dynamics CRM.\\Do you want to continue?', Comment = '%1 - product name';
        SetCRMSOPEnableConfirmMsg: Label 'Sales Order Integration with %1 is enabled.', Comment = '%1 = CRM product name';
        SetCRMSOPDisableConfirmMsg: Label 'Sales Order Integration with %1 is disabled.', Comment = '%1 = CRM product name';
        SalesHubAppModuleNameTxt: Label 'Sales Hub', Locked = true;
        SystemAdminRoleTemplateIdTxt: Label '{627090FF-40A3-4053-8790-584EDC5BE201}', Locked = true;
        SystemAdminErr: Label 'User %1 has the %2 role on server %3.\\You must choose a user that does not have the %2 role.', Comment = '%1 user name, %2 - security role name, %3 - server address';
        BCRolesErr: Label 'User %1 does not have the required roles on server %4.\\You must choose a user that has the roles %2 and %3.', Comment = '%1 user name, %2 - security role name,  %3 - security role name, %4 - server address';
        UserNotLicensedErr: Label 'User %1 is not licensed on server %2.', Comment = '%1 user name, %2 - server address';
        UserNotActiveErr: Label 'User %1 is disabled on server %2.', Comment = '%1 user name, %2 - server address';
        UserHasNoRolesErr: Label 'User %1 has no user roles assigned on server %2.', Comment = '%1 user name, %2 - server address';
        BCIntegrationAdministratorRoleIdTxt: Label '{8c8d4f51-a72b-e511-80d9-3863bb349780}', Locked = true;
        BCIntegrationUserRoleIdTxt: Label '{6f960e32-a72b-e511-80d9-3863bb349780}', Locked = true;
        CDSConnectionMustBeEnabledErr: Label 'You must enable the connection to Dataverse before you can set up the connection to %1.\\Choose ''Set up Dataverse connection'' in %2 page.', Comment = '%1 = CRM product name, %2 = Assisted Setup page caption.';
        ShowDataverseConnectionSetupLbl: Label 'Show Dataverse Connection Setup';
        DeploySucceedMsg: Label 'The solution, user roles, and entities have been deployed.';
        DeployFailedMsg: Label 'The deployment of the solution succeeded, but the deployment of user roles failed.';
        DeploySolutionFailedMsg: Label 'The deployment of the solution failed.';
        CategoryTok: Label 'AL Dataverse Integration', Locked = true;
        CRMConnDisabledTxt: Label 'CRM connection has been disabled.', Locked = true;
        CRMConnEnabledTxt: Label 'CRM connection has been enabled.', Locked = true;
        UnitGroupMappingEnabledConfirmLbl: Label 'When you enable unit group mapping integration table mappings will be updated and a data upgrade job to update unit groups will be scheduled. Do you want to enable?';
        UnitGroupMappingEnabledLbl: Label 'Unit group mapping is enabled and a data upgrade job is scheduled. Please wait the job to be completed before you start synchronizing data. Do you want to navigate to the job?';
        ScheduleJobFailedLbl: Label 'Scheduling data upgrade job failed.';
        DefaultingToDataverseServiceClientTxt: Label 'Defaulting to DataverseServiceClient', Locked = true;

    [Scope('OnPrem')]
    procedure EnsureCDSConnectionIsEnabled();
    var
        CDSConnectionSetup: Record "CDS Connection Setup";
    begin
        if Get() then
            if "Is Enabled" then
                exit;

        if CDSConnectionSetup.Get() then
            if CDSConnectionSetup."Is Enabled" then
                exit;

        CDSConnectionNotEnabledError();
    end;

    [Scope('OnPrem')]
    procedure LoadConnectionStringElementsFromCDSConnectionSetup();
    var
        CDSConnectionSetup: Record "CDS Connection Setup";
    begin
        if Get() then
            if "Is Enabled" then
                exit;

        if CDSConnectionSetup.Get() then
            if CDSConnectionSetup."Is Enabled" then begin
                "Server Address" := CDSConnectionSetup."Server Address";
                CRMIntegrationManagement.CheckCRMConnectionURL("Server Address");
                "User Name" := CDSConnectionSetup."User Name";
                "User Password Key" := CDSConnectionSetup."User Password Key";
                "Authentication Type" := CDSConnectionSetup."Authentication Type";
                "Proxy Version" := CDSConnectionSetup."Proxy Version";
                if not Modify() then
                    Insert();
                SetConnectionString(CDSConnectionSetup."Connection String");
                exit;
            end;

        CDSConnectionNotEnabledError();
    end;

    [Scope('OnPrem')]
    [NonDebuggable]
    procedure DeployCRMSolution(ForceRedeploy: Boolean);
    var
        DummyCRMConnectionSetup: Record "CRM Connection Setup";
        AdminEmail: Text;
        AdminPassword: SecretText;
        AdminPasswordProvided: Text;
        AccessToken: SecretText;
        AdminADDomain: Text;
        ImportSolutionFailed: Boolean;
    begin
        if not ForceRedeploy and CRMIntegrationManagement.IsCRMSolutionInstalled() then
            exit;

        DummyCRMConnectionSetup.EnsureCDSConnectionIsEnabled();
        case "Authentication Type" of
            "Authentication Type"::Office365:
                begin
                    CRMIntegrationManagement.CheckCRMConnectionURL("Server Address");
                    CDSIntegrationImpl.GetAccessToken("Server Address", true, AccessToken);
                end;
            "Authentication Type"::AD:
                if not PromptForCredentials(AdminEmail, AdminPasswordProvided, AdminADDomain) then begin
                    AdminPassword := AdminPasswordProvided;
                    exit;
                end;
            else
                if not PromptForCredentials(AdminEmail, AdminPasswordProvided) then begin
                    AdminPassword := AdminPasswordProvided;
                    exit;
                end;
        end;

        CRMIntegrationManagement.CheckCRMConnectionURL("Server Address");
        if CRMIntegrationManagement.ImportCRMSolution("Server Address", "User Name", AdminEmail, AdminPassword, AccessToken, AdminADDomain, GetProxyVersion(), ForceRedeploy, ImportSolutionFailed) then
            Message(DeploySucceedMsg)
        else
            if ImportSolutionFailed then
                Message(DeploySolutionFailedMsg)
            else
                Message(DeployFailedMsg);
    end;

    procedure CountCRMJobQueueEntries(var ActiveJobs: Integer; var TotalJobs: Integer)
    var
        JobQueueEntry: Record "Job Queue Entry";
    begin
        if not "Is Enabled" then begin
            TotalJobs := 0;
            ActiveJobs := 0;
        end else begin
            if "Is CRM Solution Installed" then
                JobQueueEntry.SetFilter("Object ID to Run", GetJobQueueEntriesObjectIDToRunFilter())
            else
                JobQueueEntry.SetRange("Object ID to Run", CODEUNIT::"Integration Synch. Job Runner");
            TotalJobs := JobQueueEntry.Count();

            JobQueueEntry.SetFilter(Status, '%1|%2', JobQueueEntry.Status::Ready, JobQueueEntry.Status::"In Process");
            ActiveJobs := JobQueueEntry.Count();
        end;
    end;

    [Scope('OnPrem')]
    procedure HasPassword(): Boolean
    begin
        exit(not GetSecretPassword().IsEmpty());
    end;


    [Scope('OnPrem')]
    procedure SetPassword(PasswordText: SecretText)
    begin
        if IsTemporary() then begin
            TempUserPassword := PasswordText;
            exit;
        end;
        if IsNullGuid("User Password Key") then
            "User Password Key" := CreateGuid();

        IsolatedStorageManagement.Set("User Password Key", PasswordText, DATASCOPE::Company);
    end;

    [Scope('OnPrem')]
    procedure DeletePassword()
    begin
        if IsTemporary() then begin
            Clear(TempUserPassword);
            exit;
        end;

        if IsNullGuid("User Password Key") then
            exit;

        IsolatedStorageManagement.Delete(Format("User Password Key"), DATASCOPE::Company);
    end;

    procedure RegisterConnection()
    begin
        if not HasTableConnection(TABLECONNECTIONTYPE::CRM, "Primary Key") then
            RegisterConnectionWithName("Primary Key");
    end;

    [NonDebuggable]
    procedure RegisterConnectionWithName(ConnectionName: Text)
    begin
        RegisterTableConnection(TABLECONNECTIONTYPE::CRM, ConnectionName, GetSecretConnectionStringWithCredentials().Unwrap());
        SetDefaultTableConnection(TABLECONNECTIONTYPE::CRM, GetDefaultCRMConnection(ConnectionName));
    end;

    procedure UnregisterConnection(): Boolean
    begin
        exit(UnregisterConnectionWithName("Primary Key"));
    end;

    [TryFunction]
    procedure UnregisterConnectionWithName(ConnectionName: Text)
    begin
        UnregisterTableConnection(TABLECONNECTIONTYPE::CRM, ConnectionName);
    end;


    [Scope('OnPrem')]
    procedure GetSecretConnectionStringWithCredentials() ConnectionString: SecretText
    var
        [NonDebuggable]
        ConnectionStringWithPlaceholders: Text;
        PasswordPlaceHolderPos: Integer;
    begin
        ConnectionStringWithPlaceholders := GetConnectionString();

        // if the setup record is temporary and connection string contains access token, this is a temp setup record constructed for the admin log-on
        // in this case just use the connection string
        if IsTemporary() and ConnectionStringWithPlaceholders.Contains(AccessTokenTok) then
            exit(ConnectionStringWithPlaceholders);

        if ConnectionStringWithPlaceholders = '' then
            ConnectionStringWithPlaceholders := UpdateConnectionString();

        // if auth type is Office365 and connection string contains {ClientSecret} token
        // then we will connect via OAuth client credentials grant flow, and construct the connection string accordingly, with the actual client secret
        if "Authentication Type" = "Authentication Type"::Office365 then begin
            CRMIntegrationManagement.CheckCRMConnectionURL("Server Address");

            if ConnectionStringWithPlaceholders.Contains(ClientSecretTok) then begin
                ConnectionStringWithPlaceholders := StrSubstNo(ClientSecretConnectionStringFormatTxt, ClientSecretAuthTxt, "Server Address", CDSIntegrationImpl.GetCDSConnectionClientId(), '%1', GetProxyVersion());
                ConnectionString := SecretStrSubstNo(ConnectionStringWithPlaceholders, CDSIntegrationImpl.GetCDSConnectionClientSecret());
                exit(ConnectionString);
            end;

            if ConnectionStringWithPlaceholders.Contains(CertificateTok) then begin
                ConnectionString := StrSubstNo(CertificateConnectionStringFormatTxt, CertificateAuthTxt, "Server Address", CDSIntegrationImpl.GetCDSConnectionFirstPartyAppId(), CDSIntegrationImpl.GetCDSConnectionFirstPartyAppCertificate(), GetProxyVersion());
                exit(ConnectionString);
            end;
        end;

        PasswordPlaceHolderPos := StrPos(ConnectionStringWithPlaceholders, MissingPasswordTok);
        ConnectionStringWithPlaceholders :=
          CopyStr(ConnectionStringWithPlaceholders, 1, PasswordPlaceHolderPos - 1) + '%1' +
          CopyStr(ConnectionStringWithPlaceholders, PasswordPlaceHolderPos + StrLen(MissingPasswordTok));
        ConnectionString := SecretStrSubstNo(ConnectionStringWithPlaceholders, GetSecretPassword());
    end;

    procedure GetIntegrationUserID() IntegrationUserID: Guid
    var
        CRMSystemuser: Record "CRM Systemuser";
    begin
        Get();
        TestField("Is Enabled");
        FilterCRMSystemUser(CRMSystemuser);
        if CRMSystemuser.FindFirst() then
            IntegrationUserID := CRMSystemuser.SystemUserId;
        if IsNullGuid(IntegrationUserID) then
            ShowError(UserCRMSetupTxt, StrSubstNo(CannotResolveUserFromConnectionSetupErr, CRMProductName.SHORT()));
    end;


    [Scope('OnPrem')]
    procedure GetSecretPassword(): SecretText
    var
        Value: SecretText;
    begin
        if IsTemporary() then
            exit(TempUserPassword);
        if not IsNullGuid("User Password Key") then
            IsolatedStorageManagement.Get("User Password Key", DataScope::Company, Value);
        exit(Value);
    end;

    local procedure GetUserName() UserName: Text
    begin
        if "User Name" = '' then
            UserName := MissingUsernameTok
        else
            UserName := CopyStr("User Name", StrPos("User Name", '\') + 1);
    end;

    procedure GetJobQueueEntriesObjectIDToRunFilter(): Text
    begin
        exit(
          StrSubstNo(
            '%1|%2|%3|%4|%5|%6',
            CODEUNIT::"Integration Synch. Job Runner",
            CODEUNIT::"CRM Statistics Job",
            CODEUNIT::"Auto Create Sales Orders",
            CODEUNIT::"Auto Process Sales Quotes",
            CODEUNIT::"Int. Uncouple Job Runner",
            CODEUNIT::"Int. Coupling Job Runner"));
    end;

    [Scope('OnPrem')]
    procedure PerformTestConnection()
    begin
        VerifyTestConnection();
        Message(ConnectionSuccessMsg);
    end;

    [Scope('OnPrem')]
    procedure VerifyTestConnection(): Boolean
    begin
        if ("Server Address" = '') or ("User Name" = '') then
            Error(DetailsMissingErr, CRMProductName.SHORT());

        CRMIntegrationManagement.ClearState();

        if not TestConnection() then
            Error(ConnectionErr, CRMIntegrationManagement.GetLastErrorMessage());

        TestIntegrationUserRequirements();

        exit(true);
    end;

    procedure TestConnection() Success: Boolean
    var
        TestConnectionName: Text;
    begin
        TestConnectionName := Format(CreateGuid());
        UnregisterConnectionWithName(TestConnectionName);
        RegisterConnectionWithName(TestConnectionName);
        SetDefaultTableConnection(
          TABLECONNECTIONTYPE::CRM, GetDefaultCRMConnection(TestConnectionName), true);
        Success := TryReadSystemUsers();

        UnregisterConnectionWithName(TestConnectionName);
    end;

    procedure TestIntegrationUserRequirements()
    var
        CRMRole: Record "CRM Role";
        TempCRMRole: Record "CRM Role" temporary;
        CRMSystemuserroles: Record "CRM Systemuserroles";
        CRMSystemuser: Record "CRM Systemuser";
        BCIntAdminCRMRoleName: Text;
        BCIntUserCRMRoleName: Text;
        SystemAdminCRMRoleName: Text;
        TestConnectionName: Text;
        BCIntegrationAdminRoleDeployed: Boolean;
        BCIntegrationUserRoleDeployed: Boolean;
        BCIntegrationRolesDeployed: Boolean;
        ChosenUserIsSystemAdmin: Boolean;
        ChosenUserIsBCIntegrationAdmin: Boolean;
        ChosenUserIsBCIntegrationUser: Boolean;
    begin
        TestConnectionName := Format(CreateGuid());
        UnregisterConnectionWithName(TestConnectionName);
        RegisterConnectionWithName(TestConnectionName);
        SetDefaultTableConnection(
          TABLECONNECTIONTYPE::CRM, GetDefaultCRMConnection(TestConnectionName), true);

        if CRMRole.FindSet() then
            repeat
                TempCRMRole.TransferFields(CRMRole);
                TempCRMRole.Insert();
                if LowerCase(Format(TempCRMRole.RoleId)) = BCIntegrationAdministratorRoleIdTxt then begin
                    BCIntegrationAdminRoleDeployed := true;
                    BCIntAdminCRMRoleName := TempCRMRole.Name;
                end;
                if LowerCase(Format(TempCRMRole.RoleId)) = BCIntegrationUserRoleIdTxt then begin
                    BCIntegrationUserRoleDeployed := true;
                    BCIntUserCRMRoleName := TempCRMRole.Name;
                end;
            until CRMRole.Next() = 0;

        BCIntegrationRolesDeployed := BCIntegrationAdminRoleDeployed and BCIntegrationUserRoleDeployed;

        CRMSystemuser.SetFilter(InternalEMailAddress, StrSubstNo('@%1', "User Name"));
        if CRMSystemuser.FindFirst() then begin
            if CRMSystemuser.IsDisabled then
                Error(UserNotActiveErr, "User Name", "Server Address");
            if "Authentication Type" <> "Authentication Type"::Office365 then
                if not CRMSystemuser.IsLicensed then
                    Error(UserNotLicensedErr, "User Name", "Server Address");

            CRMSystemuserroles.SetRange(SystemUserId, CRMSystemuser.SystemUserId);
            if CRMSystemuserroles.FindSet() then
                repeat
                    if TempCRMRole.Get(CRMSystemuserroles.RoleId) then begin
                        if UpperCase(Format(TempCRMRole.RoleTemplateId)) = SystemAdminRoleTemplateIdTxt then begin
                            ChosenUserIsSystemAdmin := true;
                            SystemAdminCRMRoleName := TempCRMRole.Name
                        end;
                        if LowerCase(Format(TempCRMRole.RoleId)) = BCIntegrationAdministratorRoleIdTxt then
                            ChosenUserIsBCIntegrationAdmin := true;
                        if LowerCase(Format(TempCRMRole.RoleId)) = BCIntegrationUserRoleIdTxt then
                            ChosenUserIsBCIntegrationUser := true;
                    end;
                until CRMSystemuserroles.Next() = 0
            else
                if ("Server Address" <> '') and ("Server Address" <> '@@test@@') then
                    Error(UserHasNoRolesErr, "User Name", "Server Address");

            if ChosenUserIsSystemAdmin then
                Error(SystemAdminErr, "User Name", SystemAdminCRMRoleName, "Server Address");

            if BCIntegrationRolesDeployed and not (ChosenUserIsBCIntegrationAdmin and ChosenUserIsBCIntegrationUser) then
                Error(BCRolesErr, "User Name", BCIntAdminCRMRoleName, BCIntUserCRMRoleName, "Server Address");
        end;

        UnregisterConnectionWithName(TestConnectionName);
    end;

    [TryFunction]
    procedure TryReadSystemUsers()
    var
        CRMSystemuser: Record "CRM Systemuser";
    begin
        CRMSystemuser.FindFirst();
    end;

    [NonDebuggable]
    local procedure CreateOrganizationService(var CRMHelper: DotNet CrmHelper)
    begin
        CRMHelper := CRMHelper.CrmHelper(GetSecretConnectionStringWithCredentials().Unwrap());
    end;

    [TryFunction]
    local procedure GetCrmVersion(var Version: Text)
    var
        CRMHelper: DotNet CrmHelper;
    begin
        if not DoReadCRMData() then
            exit;

        Version := '';
        CreateOrganizationService(CRMHelper);
        Version := CRMHelper.GetConnectedCrmVersion();
    end;

    procedure IsVersionValid(): Boolean
    var
        Version: DotNet Version;
    begin
        if "CRM Version" <> '' then
            if Version.TryParse("CRM Version", Version) then
                exit((Version.Major > 6) and not ((Version.Major = 7) and (Version.Minor = 1)));
        exit(false);
    end;


    procedure UpdateFromWizard(var SourceCRMConnectionSetup: Record "CRM Connection Setup"; PasswordText: SecretText)
    begin
        if not Get() then begin
            Init();
            Insert();
        end;
        Validate("Server Address", SourceCRMConnectionSetup."Server Address");
        Validate("Authentication Type", "Authentication Type"::Office365);
        Validate("User Name", SourceCRMConnectionSetup."User Name");
        SetPassword(PasswordText);
        Validate("Proxy Version", SourceCRMConnectionSetup."Proxy Version");
        "Is S.Order Integration Enabled" := SourceCRMConnectionSetup."Is S.Order Integration Enabled";
        "Bidirectional Sales Order Int." := SourceCRMConnectionSetup."Bidirectional Sales Order Int.";
        "Item Availability Enabled" := SourceCRMConnectionSetup."Item Availability Enabled";
        Modify(true);
    end;

    local procedure EnableCRMConnection()
    begin
        if "Is Enabled" = xRec."Is Enabled" then
            exit;

        if not UnregisterConnection() then
            ClearLastError();

        if "Is Enabled" then begin
            VerifyTestConnection();
            RegisterConnection();
            VerifyBaseCurrencyMatchesLCY();
            InstallIntegrationSolution();
            EnableIntegrationTables();
            if "Disable Reason" <> '' then
                CRMIntegrationManagement.ClearConnectionDisableReason(Rec);
        end else begin
            "CRM Version" := '';
            "Is CRM Solution Installed" := false;
            CurrencyDecimalPrecision := 0;
            Clear(BaseCurrencyId);
            BaseCurrencyPrecision := 0;
            BaseCurrencySymbol := '';
            UpdateCRMJobQueueEntriesStatus();
            CRMIntegrationManagement.ClearState();
        end;
    end;

    [NonDebuggable]
    local procedure InstallIntegrationSolution()
    var
        AdminEmail: Text;
        AdminPassword: Text;
        AccessToken: SecretText;
        AdminADDomain: Text;
        ImportSolutionFailed: Boolean;
    begin
        if CRMIntegrationManagement.IsCRMSolutionInstalled() then
            exit;

        CRMIntegrationManagement.CheckCRMConnectionURL("Server Address");

        case "Authentication Type" of
            "Authentication Type"::Office365:
                CDSIntegrationImpl.GetAccessToken("Server Address", true, AccessToken);
            "Authentication Type"::AD:
                if not PromptForCredentials(AdminEmail, AdminPassword, AdminADDomain) then
                    exit;
            else
                if not PromptForCredentials(AdminEmail, AdminPassword) then
                    exit;
        end;

        CRMIntegrationManagement.ImportCRMSolution(
            "Server Address", "User Name", AdminEmail, AdminPassword, AccessToken, AdminADDomain, GetProxyVersion(), false, ImportSolutionFailed);
    end;

    local procedure EnableIntegrationTables()
    var
        CRMSetupDefaults: Codeunit "CRM Setup Defaults";
    begin
        Modify(); // Job Queue to read "Is Enabled"
        Commit();
        CRMSetupDefaults.ResetConfiguration(Rec);
    end;

    procedure EnableCRMConnectionFromWizard()
    begin
        Get();
        Validate("Is Enabled", true);
        Modify(true);
    end;

    procedure RestoreConnection()
    begin
        // This function should be called from OnAfterUpgradeComplete trigger (when introduced)
        if "Restore Connection" then begin
            "Restore Connection" := false;
            Modify();
            Commit();
            if TestConnection() then
                Validate("Is Enabled", true);
            Modify();
        end;
    end;

    procedure SetCRMSOPEnabled()
    begin
        TestField("Is CRM Solution Installed", true);
        SetCRMSOPEnabledWithCredentials('', '', true);
    end;

    procedure SetCRMSOPDisabled()
    var
        CRMSalesorder: Record "CRM Salesorder";
    begin
        CRMSalesorder.SetRange(StateCode, CRMSalesorder.StateCode::Submitted);
        if not CRMSalesorder.IsEmpty() then
            Error(CannotDisableSalesOrderIntErr);
        SetCRMSOPEnabledWithCredentials('', '', false);
        Validate("Auto Create Sales Orders", false);
    end;

    [NonDebuggable]
    [Scope('OnPrem')]
    procedure SetCRMSOPEnabledWithCredentials(AdminEmail: Text; AdminPassword: Text; SOPIntegrationEnable: Boolean)
    var
        CRMOrganization: Record "CRM Organization";
        TempCRMConnectionSetup: Record "CRM Connection Setup" temporary;
        CRMConnectionSetup: Record "CRM Connection Setup";
        ConnectionName: Text;
    begin
        CreateTempAdminConnection(TempCRMConnectionSetup);
        if (AdminEmail <> '') and (AdminPassword <> '') then begin
            TempCRMConnectionSetup.SetPassword(AdminPassword);
            TempCRMConnectionSetup.Validate("User Name", COPYSTR(AdminEmail, 1, MaxStrLen(TempCRMConnectionSetup."User Name")));
            TempCRMConnectionSetup.SetConnectionString(Rec.GetConnectionString());
        end
        else begin
            CRMConnectionSetup.Get();
            TempCRMConnectionSetup.Validate("User Name", CRMConnectionSetup."User Name");
            TempCRMConnectionSetup.SetPassword(CRMConnectionSetup.GetSecretPassword());
            TempCRMConnectionSetup.SetConnectionString(CRMConnectionSetup.GetConnectionString());
        end;
        ConnectionName := Format(CreateGuid());
        TempCRMConnectionSetup.RegisterConnectionWithName(ConnectionName);
        SetDefaultTableConnection(
          TABLECONNECTIONTYPE::CRM, GetDefaultCRMConnection(ConnectionName), true);

        CRMOrganization.FindFirst();
        if CRMOrganization.IsSOPIntegrationEnabled <> SOPIntegrationEnable then begin
            CRMOrganization.IsSOPIntegrationEnabled := SOPIntegrationEnable;
            CRMOrganization.Modify(true);
        end;

        TempCRMConnectionSetup.UnregisterConnectionWithName(ConnectionName);
    end;

    procedure SetUserAsIntegrationUser()
    var
        CRMSystemuser: Record "CRM Systemuser";
        TempCRMConnectionSetup: Record "CRM Connection Setup" temporary;
        ConnectionName: Text;
    begin
        CreateTempAdminConnection(TempCRMConnectionSetup);
        ConnectionName := Format(CreateGuid());
        TempCRMConnectionSetup.RegisterConnectionWithName(ConnectionName);
        SetDefaultTableConnection(
          TABLECONNECTIONTYPE::CRM, GetDefaultCRMConnection(ConnectionName), true);
        FilterCRMSystemUser(CRMSystemuser);
        CRMSystemuser.FindFirst();

        if (CRMSystemuser.InviteStatusCode <> CRMSystemuser.InviteStatusCode::InvitationAccepted) or
           (not CRMSystemuser.IsIntegrationUser)
        then begin
            CRMSystemuser.InviteStatusCode := CRMSystemuser.InviteStatusCode::InvitationAccepted;
            CRMSystemuser.IsIntegrationUser := true;
            CRMSystemuser.Modify(true);
        end;

        TempCRMConnectionSetup.UnregisterConnectionWithName(ConnectionName);
    end;

    local procedure CreateTempAdminConnection(var CRMConnectionSetup: Record "CRM Connection Setup")
    begin
        CreateTempNoDelegateConnection(CRMConnectionSetup);
        Clear(CRMConnectionSetup."User Password Key");
        CRMConnectionSetup.Validate("User Name", '');
    end;

    local procedure CreateTempNoDelegateConnection(var CRMConnectionSetup: Record "CRM Connection Setup")
    begin
        CRMConnectionSetup.Init();
        CalcFields("Server Connection String");
        CRMConnectionSetup.TransferFields(Rec);
        CRMConnectionSetup.SetConnectionString(Rec.GetConnectionString());
        CRMConnectionSetup."Primary Key" := CopyStr('TEMP' + "Primary Key", 1, MaxStrLen(CRMConnectionSetup."Primary Key"));
        CRMConnectionSetup."Is Enabled" := true;
    end;

    [Scope('OnPrem')]
    procedure RefreshDataFromCRM()
    begin
        RefreshDataFromCRM(true);
    end;

    [Scope('OnPrem')]
    procedure RefreshDataFromCRM(ResetSalesOrderMappingConfiguration: Boolean)
    var
        CRMSetupDefaults: Codeunit "CRM Setup Defaults";
    begin
        if "Is Enabled" then begin
            "Is CRM Solution Installed" := CRMIntegrationManagement.IsCRMSolutionInstalled();
            RefreshFromCRMConnectionInformation();
            if TryRefreshCRMSettings() then
                if ResetSalesOrderMappingConfiguration then
                    CRMSetupDefaults.ResetSalesOrderMappingConfiguration(Rec);
        end;
    end;

    local procedure RefreshFromCRMConnectionInformation()
    var
        CRMNAVConnection: Record "CRM NAV Connection";
    begin
        if "Is CRM Solution Installed" then
            if CRMNAVConnection.FindFirst() then
                "Dynamics NAV URL" := CRMNAVConnection."Dynamics NAV URL";
    end;

    [TryFunction]
    local procedure TryRefreshCRMSettings()
    var
        CRMOrganization: Record "CRM Organization";
    begin
        GetCrmVersion("CRM Version");
        Validate("CRM Version");

        if CRMOrganization.FindFirst() then begin
            "Is S.Order Integration Enabled" := CRMOrganization.IsSOPIntegrationEnabled;
            CurrencyDecimalPrecision := CRMOrganization.CurrencyDecimalPrecision;
            BaseCurrencyId := CRMOrganization.BaseCurrencyId;
            BaseCurrencyPrecision := CRMOrganization.BaseCurrencyPrecision;
            BaseCurrencySymbol := CRMOrganization.BaseCurrencySymbol;
        end else
            "Is S.Order Integration Enabled" := false;
    end;

    local procedure VerifyBaseCurrencyMatchesLCY()
    var
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
        GeneralLedgerSetup: Record "General Ledger Setup";
        IsHandled: Boolean;
    begin
        IsHandled := false;
        OnBeforeVerifyBaseCurrencyMatchesLCY(IsHandled);
        if IsHandled then
            exit;

        GeneralLedgerSetup.Get();
        CRMTransactionCurrency.Reset();
        CRMTransactioncurrency.SetRange(ISOCurrencyCode, CopyStr(GeneralLedgerSetup."LCY Code", 1, MaxStrLen(CRMTransactioncurrency.ISOCurrencyCode)));
        if (GeneralLedgerSetup."LCY Code" = '') or CRMTransactioncurrency.IsEmpty() then
            Error(LCYMustMatchBaseCurrencyErr, GeneralLedgerSetup."LCY Code");
    end;

    [Scope('OnPrem')]
    procedure PerformWebClientUrlReset()
    var
        TempCRMConnectionSetup: Record "CRM Connection Setup" temporary;
        CRMSetupDefaults: Codeunit "CRM Setup Defaults";
        ConnectionName: Text;
    begin
        CreateTempNoDelegateConnection(TempCRMConnectionSetup);
        ConnectionName := Format(CreateGuid());
        TempCRMConnectionSetup.RegisterConnectionWithName(ConnectionName);
        SetDefaultTableConnection(
          TABLECONNECTIONTYPE::CRM, GetDefaultCRMConnection(ConnectionName), true);

        CRMSetupDefaults.ResetCRMNAVConnectionData();

        TempCRMConnectionSetup.UnregisterConnectionWithName(ConnectionName);

        RefreshDataFromCRM();
    end;

    procedure SynchronizeNow(DoFullSynch: Boolean)
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        TempNameValueBuffer: Record "Name/Value Buffer" temporary;
        CRMSetupDefaults: Codeunit "CRM Setup Defaults";
    begin
        CRMSetupDefaults.GetPrioritizedMappingList(TempNameValueBuffer);

        TempNameValueBuffer.Ascending(true);
        if not TempNameValueBuffer.FindSet() then
            exit;

        repeat
            if IntegrationTableMapping.Get(TempNameValueBuffer.Value) then
                IntegrationTableMapping.SynchronizeNow(DoFullSynch);
        until TempNameValueBuffer.Next() = 0;
    end;

    [NonDebuggable]
    procedure PromptForCredentials(var AdminEmail: Text; var AdminPassword: Text): Boolean
    var
        TempOfficeAdminCredentials: Record "Office Admin. Credentials" temporary;
    begin
        if TempOfficeAdminCredentials.IsEmpty() then begin
            TempOfficeAdminCredentials.Init();
            TempOfficeAdminCredentials.Insert(true);
            Commit();
            if PAGE.RunModal(PAGE::"Dynamics CRM Admin Credentials", TempOfficeAdminCredentials) <> ACTION::LookupOK then
                exit(false);
        end;
        if (not TempOfficeAdminCredentials.FindFirst()) or
           (TempOfficeAdminCredentials.Email = '') or (TempOfficeAdminCredentials.Password = '')
        then begin
            TempOfficeAdminCredentials.DeleteAll(true);
            exit(false);
        end;

        AdminEmail := TempOfficeAdminCredentials.Email;
        AdminPassword := TempOfficeAdminCredentials.Password;
        exit(true);
    end;

    [Scope('OnPrem')]
    [NonDebuggable]
    procedure PromptForCredentials(var AdminEmail: Text; var AdminPassword: Text; var AdminADDomain: Text): Boolean
    var
        TempOfficeAdminCredentials: Record "Office Admin. Credentials" temporary;
        BackslashPos: Integer;
    begin
        if TempOfficeAdminCredentials.IsEmpty() then begin
            TempOfficeAdminCredentials.Init();
            TempOfficeAdminCredentials.Insert(true);
            Commit();
            if PAGE.RunModal(PAGE::"Dynamics CRM Admin Credentials", TempOfficeAdminCredentials) <> ACTION::LookupOK then
                exit(false);
        end;
        if (not TempOfficeAdminCredentials.FindFirst()) or
           (TempOfficeAdminCredentials.Email = '') or (TempOfficeAdminCredentials.Password = '')
        then begin
            TempOfficeAdminCredentials.DeleteAll(true);
            exit(false);
        end;

        BackslashPos := StrPos(TempOfficeAdminCredentials.Email, '\');
        if (BackslashPos <= 1) or (BackslashPos = StrLen(TempOfficeAdminCredentials.Email)) then
            Error(UserNameMustIncludeDomainErr);
        AdminADDomain := CopyStr(TempOfficeAdminCredentials.Email, 1, BackslashPos - 1);
        AdminEmail := CopyStr(TempOfficeAdminCredentials.Email, BackslashPos + 1);
        AdminPassword := TempOfficeAdminCredentials.Password;
        exit(true);
    end;

    local procedure ShowError(ActivityDescription: Text[128]; ErrorMessage: Text)
    var
        MyNotifications: Record "My Notifications";
        SystemInitialization: Codeunit "System Initialization";
    begin
        if (not SystemInitialization.IsInProgress()) and (GetExecutionContext() = ExecutionContext::Normal) then
            Error(ErrorMessage);

        MyNotifications.InsertDefault(GetCRMNotificationId(), ActivityDescription, ErrorMessage, true);
    end;

    local procedure GetCRMNotificationId(): Guid
    begin
        exit('692A2701-4BBB-4C5B-B4C0-629D96B60644');
    end;

    procedure DoReadCRMData(): Boolean
    var
        SkipReading: Boolean;
    begin
        OnReadingCRMData(SkipReading);
        exit(not SkipReading);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnReadingCRMData(var SkipReading: Boolean)
    begin
    end;

    [Scope('OnPrem')]
    procedure GetDefaultCRMConnection(ConnectionName: Text): Text
    begin
        OnGetDefaultCRMConnection(ConnectionName);
        exit(ConnectionName);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetDefaultCRMConnection(var ConnectionName: Text)
    begin
    end;

    local procedure CrmAuthenticationType(): Text
    begin
        case "Authentication Type" of
            "Authentication Type"::Office365:
                exit('AuthType=Office365;');
            "Authentication Type"::AD:
                exit('AuthType=AD;' + GetDomain());
            "Authentication Type"::IFD:
                exit('AuthType=IFD;' + GetDomain() + 'HomeRealmUri= ;');
            "Authentication Type"::OAuth:
                exit('AuthType=OAuth;' + 'AppId= ;' + 'RedirectUri= ;' + 'TokenCacheStorePath= ;' + 'LoginPrompt=Auto;');
        end;
    end;

    [Scope('OnPrem')]
    procedure UpdateConnectionString() ConnectionString: Text
    begin
        CRMIntegrationManagement.CheckCRMConnectionURL("Server Address");

        if "Authentication Type" <> "Authentication Type"::Office365 then
            ConnectionString := StrSubstNo(ConnectionStringFormatTok, "Server Address", GetUserName(), MissingPasswordTok, GetProxyVersion(), CrmAuthenticationType())
        else
            if CDSIntegrationImpl.GetCDSConnectionFirstPartyAppId() <> '' then
                ConnectionString := StrSubstNo(CertificateConnectionStringFormatTxt, CertificateAuthTxt, "Server Address", ClientIdTok, CertificateTok, GetProxyVersion())
            else
                ConnectionString := StrSubstNo(ClientSecretConnectionStringFormatTxt, ClientSecretAuthTxt, "Server Address", ClientIdTok, ClientSecretTok, GetProxyVersion());

        SetConnectionString(ConnectionString);
    end;

    local procedure UpdateProxyVersionInConnectionString() ConnectionString: Text
    var
        LeftPart: Text;
        RightPart: Text;
        ProxyVersionTok: Text;
        IndexOfProxyVersion: Integer;
    begin
        ProxyVersionTok := 'ProxyVersion=';
        ConnectionString := GetConnectionString();

        // if the connection string is empty, just initialize it the standard way
        if ConnectionString = '' then begin
            ConnectionString := UpdateConnectionString();
            exit;
        end;

        IndexOfProxyVersion := ConnectionString.IndexOf(ProxyVersionTok);

        // if there is no proxy version in the connection string, just add it to the end
        if IndexOfProxyVersion = 0 then begin
            ConnectionString += ('; ' + ProxyVersionTok + Format(GetProxyVersion()));
            SetConnectionString(ConnectionString);
            exit;
        end;

        LeftPart := CopyStr(ConnectionString, 1, IndexOfProxyVersion - 1);
        RightPart := CopyStr(ConnectionString, IndexOfProxyVersion);

        // RightPart starts with ProxyVersion=
        // if there is no ; in it, then this is the end of the original connection string
        // just add proxy version to the end of LeftPart
        if RightPart.IndexOf(';') = 0 then begin
            ConnectionString := LeftPart + ProxyVersionTok + Format(GetProxyVersion());
            SetConnectionString(ConnectionString);
            exit;
        end;

        // in the remaining case, ProxyVersion=XYZ is in the middle of the string
        RightPart := CopyStr(RightPart, RightPart.IndexOf(';'));
        ConnectionString := LeftPart + ProxyVersionTok + Format(GetProxyVersion()) + RightPart;
        SetConnectionString(ConnectionString);
    end;

    local procedure UpdateDomainName()
    begin
        if "User Name" <> '' then
            if StrPos("User Name", '\') > 0 then
                Validate(Domain, CopyStr("User Name", 1, StrPos("User Name", '\') - 1))
            else
                Domain := '';
    end;

    local procedure CheckUserName()
    begin
        if "User Name" <> '' then
            case "Authentication Type" of
                "Authentication Type"::AD:
                    if StrPos("User Name", '\') = 0 then
                        Error(UserNameMustIncludeDomainErr);
                "Authentication Type"::Office365:
                    if StrPos("User Name", '@') = 0 then
                        Error(UserNameMustBeEmailErr);
            end;
    end;

    local procedure GetDomain(): Text
    begin
        if Domain <> '' then
            exit(StrSubstNo('Domain=%1;', Domain));
    end;

    local procedure FilterCRMSystemUser(var CRMSystemuser: Record "CRM Systemuser")
    begin
        case "Authentication Type" of
            "Authentication Type"::Office365, "Authentication Type"::OAuth:
                CRMSystemuser.SetRange(InternalEMailAddress, "User Name");
            "Authentication Type"::AD, "Authentication Type"::IFD:
                CRMSystemuser.SetRange(DomainName, "User Name");
        end;
    end;

    procedure UpdateCRMJobQueueEntriesStatus()
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        JobQueueEntry: Record "Job Queue Entry";
        NewStatus: Option;
        JobQueueEntryCodeunitFilter: Text;
    begin
        if "Is Enabled" then
            NewStatus := JobQueueEntry.Status::Ready
        else
            NewStatus := JobQueueEntry.Status::"On Hold";
        IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
        IntegrationTableMapping.SetRange("Synch. Codeunit ID", CODEUNIT::"CRM Integration Table Synch.");
        IntegrationTableMapping.SetRange("Delete After Synchronization", false);
        if CDSIntegrationImpl.IsIntegrationEnabled() then
            IntegrationTableMapping.SetFilter("Table ID", StrSubstNo('<>%1&<>%2&<>%3&<>%4&<>%5', Database::Currency, Database::"Salesperson/Purchaser", Database::Contact, Database::Customer, Database::Vendor));
        if IntegrationTableMapping.FindSet() then
            repeat
                JobQueueEntry.SetRange("Record ID to Process", IntegrationTableMapping.RecordId);
                if JobQueueEntry.FindSet() then
                    repeat
                        JobQueueEntry.SetStatus(NewStatus);
                    until JobQueueEntry.Next() = 0;
            until IntegrationTableMapping.Next() = 0;
        JobQueueEntryCodeunitFilter := StrSubstNo('%1|%2|%3|%4|%5', CODEUNIT::"Auto Create Sales Orders", CODEUNIT::"Auto Process Sales Quotes", CODEUNIT::"CRM Notes Synch Job", CODEUNIT::"CRM Order Status Update Job", CODEUNIT::"CRM Statistics Job");
        JobQueueEntry.Reset();
        JobQueueEntry.SetRange("Object Type to Run", JobQueueEntry."Object Type to Run"::Codeunit);
        JobQueueEntry.SetFilter("Object ID to Run", JobQueueEntryCodeunitFilter);
        if JobQueueEntry.FindSet() then
            repeat
                JobQueueEntry.SetStatus(NewStatus);
            until JobQueueEntry.Next() = 0;
    end;

    [Scope('OnPrem')]
    procedure GetConnectionString() ConnectionString: Text
    var
        CRMConnectionSetup: Record "CRM Connection Setup";
        InStream: InStream;
    begin
        if CRMConnectionSetup.Get("Primary Key") then
            CalcFields("Server Connection String");
        "Server Connection String".CreateInStream(InStream);
        InStream.ReadText(ConnectionString);
    end;

    [Scope('OnPrem')]
    procedure SetConnectionString(ConnectionString: Text)
    var
        OutStream: OutStream;
    begin
        if ConnectionString = '' then
            Clear("Server Connection String")
        else begin
            if "Authentication Type" <> "Authentication Type"::Office365 then
                if StrPos(ConnectionString, MissingPasswordTok) = 0 then
                    Error(ConnectionStringPwdPlaceHolderMissingErr);

            if "Authentication Type" = "Authentication Type"::Office365 then
                if (StrPos(ConnectionString, MissingPasswordTok) = 0) and (StrPos(ConnectionString, ClientSecretTok) = 0) and (StrPos(ConnectionString, CertificateTok) = 0) then
                    Error(ConnectionStringPwdOrClientSecretPlaceHolderMissingErr);

            Clear("Server Connection String");
            "Server Connection String".CreateOutStream(OutStream);
            OutStream.WriteText(ConnectionString);
        end;
        if not Modify() then;
    end;

    procedure IsEnabled(): Boolean
    begin
        if not Get() then
            exit(false);
        exit("Is Enabled");
    end;

    procedure IsBidirectionalSalesOrderIntEnabled(): Boolean
    begin
        if not Get() then
            exit(false);
        exit("Bidirectional Sales Order Int." and "Is Enabled");
    end;

    local procedure NewestUIAppModuleId(): Text[50]
    var
        CRMAppmodule: Record "CRM Appmodule";
    begin
        if not TryFindAppModuleIdByName(CRMAppmodule, SalesHubAppModuleNameTxt) then
            exit('');
        if CRMAppmodule.Name <> SalesHubAppModuleNameTxt then
            exit('');
        exit(CopyStr(LowerCase(Format(CRMAppmodule.AppModuleId, 0, 4)), 1, 50));
    end;

    [TryFunction]
    local procedure TryFindAppModuleIdByName(var CRMAppmodule: Record "CRM Appmodule"; AppModuleName: Text)
    begin
        CRMAppmodule.SetRange(Name, AppModuleName);
        if CRMAppmodule.FindFirst() then;
    end;

    procedure SetUseNewestUI()
    begin
        "Newest UI AppModuleId" := NewestUIAppModuleId();
        if "Newest UI AppModuleId" <> '' then
            "Use Newest UI" := true;
    end;

    internal procedure GetProxyVersion(): Integer
    var
        EnvironmentInformation: Codeunit "Environment Information";
    begin
        if "Proxy Version" >= 100 then
            exit("Proxy Version");

        if not EnvironmentInformation.IsSaaS() then
            exit("Proxy Version");

        Session.LogMessage('0000K7P', DefaultingToDataverseServiceClientTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
        exit(100);
    end;

    local procedure CDSConnectionNotEnabledError()
    var
        AssistedSetup: Page "Assisted Setup";
        CDSConnectionNotEnabledErrorInfo: ErrorInfo;
    begin
        CDSConnectionNotEnabledErrorInfo.DataClassification := CDSConnectionNotEnabledErrorInfo.DataClassification::SystemMetadata;
        CDSConnectionNotEnabledErrorInfo.ErrorType := CDSConnectionNotEnabledErrorInfo.ErrorType::Client;
        CDSConnectionNotEnabledErrorInfo.Verbosity := CDSConnectionNotEnabledErrorInfo.Verbosity::Error;
        CDSConnectionNotEnabledErrorInfo.Message := StrSubstNo(CDSConnectionMustBeEnabledErr, CRMProductName.SHORT(), AssistedSetup.Caption());
        CDSConnectionNotEnabledErrorInfo.AddNavigationAction(ShowDataverseConnectionSetupLbl);
        CDSConnectionNotEnabledErrorInfo.PageNo(Page::"CDS Connection Setup Wizard");
        Error(CDSConnectionNotEnabledErrorInfo);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeVerifyBaseCurrencyMatchesLCY(var IsHandled: Boolean)
    begin
    end;
}