Codeunit 5342 CRM Synch. Helper

App
Base Application
Namespace
Microsoft.Integration.Dataverse
Versions
17-28

Procedures, 74Events, 19Obsolete, 4

Versions171819202122232425262728

Source29

Source in 29

src/Layers/W1/BaseApp/Integration/D365Sales/CRMSynchHelper.Codeunit.al2194 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.BusinessRelation;
using Microsoft.CRM.Contact;
using Microsoft.CRM.Opportunity;
using Microsoft.CRM.Outlook;
using Microsoft.CRM.Setup;
using Microsoft.CRM.Team;
using Microsoft.Finance.Currency;
using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Foundation.UOM;
using Microsoft.Integration.D365Sales;
using Microsoft.Integration.SyncEngine;
using Microsoft.Inventory.Item;
using Microsoft.Pricing.Asset;
using Microsoft.Pricing.PriceList;
using Microsoft.Projects.Resources.Resource;
using Microsoft.Purchases.Vendor;
using Microsoft.Sales.Customer;
using Microsoft.Sales.Document;
using Microsoft.Sales.History;
using Microsoft.Sales.Receivables;
using Microsoft.Utilities;
using System;
using System.IO;
using System.Reflection;

codeunit 5342 "CRM Synch. Helper"
{
    Permissions = TableData "Sales Invoice Header" = m;

    trigger OnRun()
    begin
    end;

    var
        TempCRMPricelevel: Record "CRM Pricelevel" temporary;
        TempCRMTransactioncurrency: Record "CRM Transactioncurrency" temporary;
        TempCRMUom: Record "CRM Uom" temporary;
        TempCRMUomschedule: Record "CRM Uomschedule" temporary;
        CRMProductName: Codeunit "CRM Product Name";
        CRMBaseCurrencyNotFoundInNAVErr: Label 'The currency with the ISO code ''%1'' cannot be found. Therefore, the exchange rate between ''%2'' and ''%3'' cannot be calculated.', Comment = '%1,%2,%3=the ISO code of a currency (example: DKK);';
        DynamicsCRMTransactionCurrencyRecordNotFoundErr: Label 'Cannot find the currency with the value ''%1'' in Dataverse.', Comment = '%1=the currency code';
        DynamicsCRMUoMNotFoundInGroupErr: Label 'Cannot find any unit of measure inside the unit group ''%1'' in %2.', Comment = '%1=Unit Group Name, %2 = Dataverse service name';
        DynamicsCRMUoMFoundMultipleInGroupErr: Label 'Multiple units of measure were found in the unit group ''%1'' in %2.', Comment = '%1=Unit Group Name, %2 = Dataverse service name';
        IncorrectCRMUoMNameErr: Label 'The unit of measure in the unit group ''%1'' has an incorrect name: expected name ''%2'', found name ''%3''.', Comment = '%1=Unit Group name (ex: NAV PIECE), %2=Expected name (ex: PIECE), %3=Actual name (ex: BOX)';
        IncorrectCRMUoMQuantityErr: Label 'The quantity on the unit of measure ''%1'' should be 1.', Comment = '%1=unit of measure name (ex: PIECE).';
        DynamicsCRMUomscheduleNotFoundErr: Label 'Cannot find the unit group ''%1'' in %2.', Comment = '%1 = unit group name, %2 = Dataverse service name';
        IncorrectCRMUoMStatusErr: Label 'The unit of measure ''%1'' is not the base unit of measure of the unit group ''%2''.', Comment = '%1=value of the unit of measure, %2=value of the unit group';
        InvalidDestinationRecordNoErr: Label 'Invalid destination record number.';
        NavTxt: Label 'NAV', Locked = true;
        RecordMustBeCoupledErr: Label '%1 %2 must be coupled to a %3 record.', Comment = '%1 = table caption, %2 = primary key value, %3 = CRM Table caption';
        UnitOfMeasureMustBeCoupledErr: Label '%1 Unit of Measure (%2, %3) must be coupled to a %4 record.', Comment = '%1 = table caption, %2 = primary key value, %3 = primary key value, %4 = CRM Table caption';
        RecordNotFoundErr: Label '%1 could not be found in %2.', Comment = '%1=value;%2=table name in which the value was searched';
        CanOnlyUseSystemUserOwnerTypeErr: Label 'Only %1 Owner of Type SystemUser can be mapped to Salespeople.', Comment = 'Dataverse entity owner property can be of type team or systemuser. Only the type systemuser is supported. %1 = Dataverse service name';
        DefaultNAVPriceListNameTxt: Label '%1 Default Price List', Comment = '%1 - product name';
        BaseCurrencyIsNullErr: Label 'The base currency is not defined. Disable and enable CRM connection to initialize setup properly.';
        CurrencyPriceListNameTxt: Label 'Price List in %1', Comment = '%1 - currency code';
        UnableToFindPageForRecordErr: Label 'Unable to find a page for record %1.', Comment = '%1 ID of the record';
        MappingMustBeSetForGUIDFieldErr: Label 'Table %1 must be mapped to table %2 to transfer value between fields %3 and %4.', Comment = '%1 and %2 are table captions, %3 and %4 are field captions.';
        CategoryTok: Label 'AL Dataverse Integration', Locked = true;
        SetContactParentCompanyTxt: Label 'Setting contact parent company.', Locked = true;
        SetContactParentCompanySuccessfulTxt: Label 'Set contact parent company successfuly. Company No.: %1', Locked = true, Comment = '%1 = parent company no.';
        ContactBusinessRelationOptionalTxt: Label 'Contact business relation is optional.', Locked = true;
        ContactTypeCheckIgnoredTxt: Label 'Contact type check is ignored.', Locked = true;
        ItemUnitGroupNotFoundErr: Label 'Item Unit Group for Item %1 is not found.', Comment = '%1 - item number';
        ResourceUnitGroupNotFoundErr: Label 'Resource Unit Group for Resource %1 is not found.', Comment = '%1 - resource number';
        CRMUnitGroupNotFoundErr: Label 'CRM Unit Group %1 does not exist.', Comment = '%1 - unit group name';
        BaseUnitOfMeasureCannotBeEmptyErr: Label 'Base Unit of Measure must have a value in %1. It cannot be zero or empty.', Comment = '%1 - record';
        RecordMustBeCoupledExtErr: Label '%1 %2 must be coupled to a %3 row.', Comment = '%1 = BC table caption, %2 = primary key value, %3 - Dataverse table caption';
        CannotUseSameUnitGroupErr: Label 'Unit group %1 is assigned to multiple products. You cannot use the same unit group for multiple products.', Comment = '%1 - Unit group name';
        UnitOfMeasureDoesNotExistErr: Label 'Unit of measure %1 does not exist.', Comment = '%1 - unit of measure code';
        IncorrectFormatOrTypeErr: Label 'The value that you are trying to convert is in incorrect format.';
        OrderPriceListLbl: Label 'Business Central Order %1 Price List', Locked = true, Comment = '%1 - Order No.';

    procedure ClearCache()
    begin
        TempCRMPricelevel.Reset();
        TempCRMPricelevel.DeleteAll();
        Clear(TempCRMPricelevel);

        TempCRMTransactioncurrency.Reset();
        TempCRMTransactioncurrency.DeleteAll();
        Clear(TempCRMTransactioncurrency);

        TempCRMUom.Reset();
        TempCRMUom.DeleteAll();
        Clear(TempCRMUom);

        TempCRMUomschedule.Reset();
        TempCRMUomschedule.DeleteAll();
        Clear(TempCRMUomschedule);
    end;

    procedure GetDefaultPriceListName(): Text[100]
    begin
        exit(StrSubstNo(DefaultNAVPriceListNameTxt, PRODUCTNAME.Short()));
    end;

    local procedure GetDefaultPriceListNameWithLCY(): Text[100]
    var
        GeneralLedgerSetup: Record "General Ledger Setup";
    begin
        GeneralLedgerSetup.Get();
        exit(StrSubstNo(DefaultNAVPriceListNameTxt, PRODUCTNAME.Short() + ' ' + GeneralLedgerSetup."LCY Code"));
    end;

    local procedure CreateCRMDefaultPriceList(var CRMPricelevel: Record "CRM Pricelevel")
    var
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
    begin
        FindNAVLocalCurrencyInCRM(CRMTransactioncurrency);
        CRMPricelevel.Reset();
        CRMPricelevel.SetRange(Name, GetDefaultPriceListName());

        if CRMPricelevel.FindFirst() then begin
            if CRMPricelevel.TransactionCurrencyId = CRMTransactioncurrency.TransactionCurrencyId then begin
                AddToCacheCRMPriceLevel(CRMPricelevel);
                exit;
            end;
            Clear(CRMPriceLevel);
            CRMPricelevel.Name := GetDefaultPriceListNameWithLCY();
            CRMPricelevel.TransactionCurrencyId := CRMTransactioncurrency.TransactionCurrencyId;
            CRMPricelevel.Insert();
            AddToCacheCRMPriceLevel(CRMPricelevel);
            exit;
        end;

        CRMPriceLevel.Init();
        CRMPricelevel.Name := GetDefaultPriceListName();
        CRMPricelevel.TransactionCurrencyId := CRMTransactioncurrency.TransactionCurrencyId;
        CRMPricelevel.Insert();
        AddToCacheCRMPriceLevel(CRMPricelevel);
    end;

    procedure CreateCRMPricelevelInCurrency(var CRMPricelevel: Record "CRM Pricelevel"; CurrencyCode: Code[10]; NewExchangeRate: Decimal)
    var
        CRMOrganization: Record "CRM Organization";
        CRMIntegrationRecord: Record "CRM Integration Record";
    begin
        CRMOrganization.FindFirst();
        FindCurrencyCRMIntegrationRecord(CRMIntegrationRecord, CurrencyCode);

        CRMPricelevel.Init();
        CRMPricelevel.PriceLevelId := Format(CreateGuid());
        CRMPricelevel.OrganizationId := CRMOrganization.OrganizationId;
        CRMPricelevel.Name :=
          CopyStr(StrSubstNo(CurrencyPriceListNameTxt, CurrencyCode), 1, MaxStrLen(CRMPricelevel.Name));
        CRMPricelevel.TransactionCurrencyId := CRMIntegrationRecord."CRM ID";
        CRMPricelevel.ExchangeRate := NewExchangeRate;
        CRMPricelevel.Insert();
    end;

    procedure CreateCRMPriceList(SalesHeader: Record "Sales Header"; var CRMPricelevel: Record "CRM Pricelevel")
    var
        SalesLine: Record "Sales Line";
        Item: Record Item;
        Resource: Record Resource;
        CRMIntegrationRecord: Record "CRM Integration Record";
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
        CRMProduct: Record "CRM Product";
        CRMUom: Record "CRM Uom";
        CRMId: Guid;
    begin
        CRMPricelevel.Init();
        CRMPricelevel.Name := StrSubstNo(OrderPriceListLbl, SalesHeader."No.");
        if SalesHeader."Currency Code" = '' then begin
            FindNAVLocalCurrencyInCRM(CRMTransactioncurrency);
            CRMPricelevel.TransactionCurrencyId := CRMTransactioncurrency.TransactionCurrencyId;
        end else begin
            FindCurrencyCRMIntegrationRecord(CRMIntegrationRecord, SalesHeader."Currency Code");
            CRMPricelevel.TransactionCurrencyId := CRMIntegrationRecord."CRM ID";
        end;
        CRMPricelevel.Insert();

        SalesLine.SetRange("Document Type", SalesLine."Document Type"::Order);
        SalesLine.SetRange("Document No.", SalesHeader."No.");
        if SalesLine.FindSet() then
            repeat
                case SalesLine.Type of
                    SalesLine.Type::Item:
                        begin
                            Item.Get(SalesLine."No.");
                            if CRMIntegrationRecord.FindIDFromRecordID(Item.RecordId, CRMId) then
                                if CRMProduct.Get(CRMId) then begin
                                    CRMUom.SetRange(UoMScheduleId, CRMProduct.DefaultUoMScheduleId);
                                    if CRMUom.FindSet() then
                                        repeat
                                            CreateCRMProductpricelevelForProductAndUom(CRMProduct, CRMPricelevel.PriceLevelId, CRMUom);
                                        until CRMUom.Next() = 0;
                                end;
                        end;
                    SalesLine.Type::Resource:
                        begin
                            Resource.Get(SalesLine."No.");
                            if CRMIntegrationRecord.FindIDFromRecordID(Resource.RecordId, CRMId) then
                                if CRMProduct.Get(CRMId) then begin
                                    CRMUom.SetRange(UoMScheduleId, CRMProduct.DefaultUoMScheduleId);
                                    if CRMUom.FindSet() then
                                        repeat
                                            CreateCRMProductpricelevelForProductAndUom(CRMProduct, CRMPricelevel.PriceLevelId, CRMUom);
                                        until CRMUom.Next() = 0;
                                end;
                        end;
                end;
            until SalesLine.Next() = 0;
    end;

    procedure UpdateCRMPriceList(SalesHeader: Record "Sales Header"; CRMPricelevelId: Guid)
    var
        SalesLine: Record "Sales Line";
        Item: Record Item;
        Resource: Record Resource;
        CRMIntegrationRecord: Record "CRM Integration Record";
        CRMProduct: Record "CRM Product";
        CRMProductpricelevel: Record "CRM Productpricelevel";
        CRMUom: Record "CRM Uom";
        CRMId: Guid;
    begin
        SalesLine.SetRange("Document Type", SalesLine."Document Type"::Order);
        SalesLine.SetRange("Document No.", SalesHeader."No.");
        if SalesLine.FindSet() then
            repeat
                case SalesLine.Type of
                    SalesLine.Type::Item:
                        begin
                            Item.Get(SalesLine."No.");
                            if CRMIntegrationRecord.FindIDFromRecordID(Item.RecordId, CRMId) then begin
                                CRMProductpricelevel.SetRange(PriceLevelId, CRMPricelevelId);
                                CRMProductpricelevel.SetRange(ProductId, CRMId);
                                if CRMProductpricelevel.IsEmpty() then
                                    if CRMProduct.Get(CRMId) then begin
                                        CRMUom.SetRange(UoMScheduleId, CRMProduct.DefaultUoMScheduleId);
                                        if CRMUom.FindSet() then
                                            repeat
                                                CreateCRMProductpricelevelForProductAndUom(CRMProduct, CRMPricelevelId, CRMUom);
                                            until CRMUom.Next() = 0;
                                    end;
                            end;
                        end;
                    SalesLine.Type::Resource:
                        begin
                            Resource.Get(SalesLine."No.");
                            if CRMIntegrationRecord.FindIDFromRecordID(Resource.RecordId, CRMId) then begin
                                CRMProductpricelevel.SetRange(PriceLevelId, CRMPricelevelId);
                                CRMProductpricelevel.SetRange(ProductId, CRMId);
                                if CRMProductpricelevel.IsEmpty() then
                                    if CRMProduct.Get(CRMId) then begin
                                        CRMUom.SetRange(UoMScheduleId, CRMProduct.DefaultUoMScheduleId);
                                        if CRMUom.FindSet() then
                                            repeat
                                                CreateCRMProductpricelevelForProductAndUom(CRMProduct, CRMPricelevelId, CRMUom);
                                            until CRMUom.Next() = 0;
                                    end;
                            end;
                        end;
                end;
            until SalesLine.Next() = 0;
    end;

    local procedure CreateCRMProductpricelevelForProduct(CRMProduct: Record "CRM Product"; NewPriceLevelId: Guid)
    var
        CRMProductpricelevel: Record "CRM Productpricelevel";
    begin
        CRMProductpricelevel.Init();
        CRMProductpricelevel.PriceLevelId := NewPriceLevelId;
        CRMProductpricelevel.UoMId := CRMProduct.DefaultUoMId;
        CRMProductpricelevel.UoMScheduleId := CRMProduct.DefaultUoMScheduleId;
        CRMProductpricelevel.ProductId := CRMProduct.ProductId;
        CRMProductpricelevel.Amount := CRMProduct.Price;
        CRMProductpricelevel.TransactionCurrencyId := CRMProduct.TransactionCurrencyId;
        CRMProductpricelevel.ProductNumber := CRMProduct.ProductNumber;
        CRMProductpricelevel.Insert();
    end;

    procedure CreateCRMProductpricelevelForProductAndUom(CRMProduct: Record "CRM Product"; NewPriceLevelId: Guid; CRMUom: Record "CRM Uom")
    var
        CRMProductpricelevel: Record "CRM Productpricelevel";
    begin
        CRMProductpricelevel.Init();
        CRMProductpricelevel.PriceLevelId := NewPriceLevelId;
        CRMProductpricelevel.UoMId := CRMUom.UoMId;
        CRMProductpricelevel.UoMScheduleId := CRMProduct.DefaultUoMScheduleId;
        CRMProductpricelevel.ProductId := CRMProduct.ProductId;
        CRMProductpricelevel.Amount := CRMProduct.Price * CRMUom.Quantity;
        CRMProductpricelevel.ProductNumber := CRMProduct.ProductNumber;
        CRMProductpricelevel.Insert();
    end;

    procedure CreateCRMProductpriceIfAbsent(CRMInvoicedetail: Record "CRM Invoicedetail")
    var
        CRMInvoice: Record "CRM Invoice";
    begin
        if not IsNullGuid(CRMInvoicedetail.ProductId) then begin
            CRMInvoice.Get(CRMInvoicedetail.InvoiceId);
            if not IsNullGuid(CRMInvoice.PriceLevelId) then
                if not FindCRMProductPriceFromCRMInvoicedetailAndCRMInvoice(CRMInvoicedetail, CRMInvoice) then
                    CreateCRMProductpriceFromCRMInvoiceDetail(CRMInvoicedetail);
        end;
    end;

    local procedure CreateCRMProductpriceFromCRMInvoiceDetail(CRMInvoicedetail: Record "CRM Invoicedetail")
    var
        CRMInvoice: Record "CRM Invoice";
        CRMProductpricelevel: Record "CRM Productpricelevel";
        CRMUom: Record "CRM Uom";
    begin
        if CRMInvoicedetail.PricePerUnit <= 0 then
            exit;
        CRMInvoice.Get(CRMInvoicedetail.InvoiceId);
        CRMUom.Get(CRMInvoicedetail.UoMId);
        CRMProductpricelevel.Init();
        CRMProductpricelevel.PriceLevelId := CRMInvoice.PriceLevelId;
        CRMProductpricelevel.ProductId := CRMInvoicedetail.ProductId;
        CRMProductpricelevel.UoMId := CRMInvoicedetail.UoMId;
        CRMProductpricelevel.UoMScheduleId := CRMUom.UoMScheduleId;
        CRMProductpricelevel.Amount := CRMInvoicedetail.PricePerUnit;
        CRMProductpricelevel.Insert();
    end;

    local procedure CreateCRMTransactioncurrency(var CRMTransactioncurrency: Record "CRM Transactioncurrency"; CurrencyCode: Code[10])
    begin
        CRMTransactioncurrency.Init();
        CRMTransactioncurrency.ISOCurrencyCode := CopyStr(CurrencyCode, 1, MaxStrLen(CRMTransactioncurrency.ISOCurrencyCode));
        CRMTransactioncurrency.CurrencyName := CRMTransactioncurrency.ISOCurrencyCode;
        CRMTransactioncurrency.CurrencySymbol := CRMTransactioncurrency.ISOCurrencyCode;
        CRMTransactioncurrency.CurrencyPrecision := GetCRMCurrencyDefaultPrecision();
        CRMTransactioncurrency.ExchangeRate := GetCRMLCYToFCYExchangeRate(CRMTransactioncurrency.ISOCurrencyCode);
        CRMTransactioncurrency.Insert();
    end;

    procedure FindCRMDefaultPriceList(var CRMPricelevel: Record "CRM Pricelevel")
    var
        CRMConnectionSetup: Record "CRM Connection Setup";
    begin
        CRMConnectionSetup.Get();
        if not FindCRMPriceList(CRMPricelevel, CRMConnectionSetup."Default CRM Price List ID") then begin
            CreateCRMDefaultPriceList(CRMPricelevel);
            CRMConnectionSetup.Validate("Default CRM Price List ID", CRMPricelevel.PriceLevelId);
            CRMConnectionSetup.Modify();
        end;
    end;

    local procedure FindCRMPriceList(var CRMPricelevel: Record "CRM Pricelevel"; PriceListId: Guid): Boolean
    begin
        if not IsNullGuid(PriceListId) then begin
            CRMPricelevel.Reset();
            CRMPricelevel.SetRange(PriceLevelId, PriceListId);
            exit(FindCachedCRMPriceLevel(CRMPricelevel));
        end;
    end;

    procedure FindCRMPriceListByCurrencyCode(var CRMPricelevel: Record "CRM Pricelevel"; CurrencyCode: Code[10]): Boolean
    var
        CRMIntegrationRecord: Record "CRM Integration Record";
    begin
        if CurrencyCode = '' then begin
            FindCRMDefaultPriceList(CRMPricelevel);
            exit(true);
        end;

        FindCurrencyCRMIntegrationRecord(CRMIntegrationRecord, CurrencyCode);
        CRMPricelevel.Reset();
        CRMPricelevel.SetRange(Name, CopyStr(StrSubstNo(CurrencyPriceListNameTxt, CurrencyCode), 1, MaxStrLen(CRMPricelevel.Name)));
        CRMPricelevel.SetRange(TransactionCurrencyId, CRMIntegrationRecord."CRM ID");
        exit(FindCachedCRMPriceLevel(CRMPricelevel));
    end;

    procedure FindCRMProductPriceFromCRMInvoicedetail(CRMInvoicedetail: Record "CRM Invoicedetail"): Boolean
    var
        CRMInvoice: Record "CRM Invoice";
        CRMProductpricelevel: Record "CRM Productpricelevel";
    begin
        CRMInvoice.Get(CRMInvoicedetail.InvoiceId);
        CRMProductpricelevel.SetRange(PriceLevelId, CRMInvoice.PriceLevelId);
        CRMProductpricelevel.SetRange(ProductId, CRMInvoicedetail.ProductId);
        CRMProductpricelevel.SetRange(UoMId, CRMInvoicedetail.UoMId);
        exit(not CRMProductpricelevel.IsEmpty());
    end;

    local procedure FindCRMProductPriceFromCRMInvoicedetailAndCRMInvoice(CRMInvoicedetail: Record "CRM Invoicedetail"; CRMInvoice: Record "CRM Invoice"): Boolean
    var
        CRMProductpricelevel: Record "CRM Productpricelevel";
    begin
        CRMProductpricelevel.SetRange(PriceLevelId, CRMInvoice.PriceLevelId);
        CRMProductpricelevel.SetRange(ProductId, CRMInvoicedetail.ProductId);
        CRMProductpricelevel.SetRange(UoMId, CRMInvoicedetail.UoMId);
        exit(not CRMProductpricelevel.IsEmpty());
    end;

    local procedure FindCurrencyCRMIntegrationRecord(var CRMIntegrationRecord: Record "CRM Integration Record"; CurrencyCode: Code[10])
    var
        Currency: Record Currency;
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
    begin
        Currency.Get(CurrencyCode);
        if not CRMIntegrationRecord.FindByRecordID(Currency.RecordId()) then
            Error(RecordMustBeCoupledErr, Currency.TableCaption(), CurrencyCode, CRMTransactioncurrency.TableCaption());
    end;

    local procedure FindContactByAccountId(var Contact: Record Contact; AccountId: Guid; var OutOfMapFilter: Boolean): Boolean
    var
        IsVendorsSyncEnabled: Boolean;
        CustomerOutOfMapFilter: Boolean;
        VendorOutOfMapFilter: Boolean;
    begin
        if FindCustomersContactByAccountId(Contact, AccountId, CustomerOutOfMapFilter) then
            exit(true);

        OnGetVendorSyncEnabled(IsVendorsSyncEnabled);
        if IsVendorsSyncEnabled then
            if FindVendorsContactByAccountId(Contact, AccountId, VendorOutOfMapFilter) then
                exit(true);

        OutOfMapFilter := CustomerOutOfMapFilter or VendorOutOfMapFilter;
        exit(false);
    end;

    local procedure FindCustomersContactByAccountId(var Contact: Record Contact; AccountId: Guid; var OutOfMapFilter: Boolean): Boolean
    var
        ContactBusinessRelation: Record "Contact Business Relation";
        CRMIntegrationRecord: Record "CRM Integration Record";
        Customer: Record Customer;
        CustomerRecordID: RecordID;
        IsHandled: Boolean;
    begin
        if IsNullGuid(AccountId) then
            exit(false);

        OnFindAndSynchRecordIDFromIntegrationSystemId(AccountId, Database::Customer, CustomerRecordID, IsHandled);
        if not IsHandled then
            if not CRMIntegrationRecord.FindRecordIDFromID(AccountId, Database::Customer, CustomerRecordID) then
                if SynchRecordIfMappingExists(Database::Customer, Database::"CRM Account", AccountId, OutOfMapFilter) then begin
                    if not CRMIntegrationRecord.FindRecordIDFromID(AccountId, Database::Customer, CustomerRecordID) then
                        exit(false);
                end else
                    if OutOfMapFilter then
                        exit(false);

        if Customer.Get(CustomerRecordID) then begin
            ContactBusinessRelation.SetCurrentKey("Link to Table", "No.");
            ContactBusinessRelation.SetRange("Link to Table", ContactBusinessRelation."Link to Table"::Customer);
            ContactBusinessRelation.SetRange("No.", Customer."No.");
            if ContactBusinessRelation.FindFirst() then
                exit(Contact.Get(ContactBusinessRelation."Contact No."));
        end;

        exit(false);
    end;

    local procedure FindVendorsContactByAccountId(var Contact: Record Contact; AccountId: Guid; var OutOfMapFilter: Boolean): Boolean
    var
        ContactBusinessRelation: Record "Contact Business Relation";
        CRMIntegrationRecord: Record "CRM Integration Record";
        Vendor: Record Vendor;
        VendorRecordID: RecordID;
        IsHandled: Boolean;
    begin
        if IsNullGuid(AccountId) then
            exit(false);

        OnFindAndSynchRecordIDFromIntegrationSystemId(AccountId, Database::Vendor, VendorRecordID, IsHandled);
        if not IsHandled then
            if not CRMIntegrationRecord.FindRecordIDFromID(AccountId, Database::Vendor, VendorRecordID) then
                if SynchRecordIfMappingExists(Database::Vendor, Database::"CRM Account", AccountId, OutOfMapFilter) then begin
                    if not CRMIntegrationRecord.FindRecordIDFromID(AccountId, Database::Vendor, VendorRecordID) then
                        exit(false);
                end else
                    if OutOfMapFilter then
                        exit(false);

        if Vendor.Get(VendorRecordID) then begin
            ContactBusinessRelation.SetCurrentKey("Link to Table", "No.");
            ContactBusinessRelation.SetRange("Link to Table", ContactBusinessRelation."Link to Table"::Vendor);
            ContactBusinessRelation.SetRange("No.", Vendor."No.");
            if ContactBusinessRelation.FindFirst() then
                exit(Contact.Get(ContactBusinessRelation."Contact No."));
        end;

        exit(false);
    end;

    procedure FindNAVLocalCurrencyInCRM(var CRMTransactioncurrency: Record "CRM Transactioncurrency"): Guid
    var
        NAVLCYCode: Code[10];
    begin
        NAVLCYCode := GetNavLCYCode();
        CRMTransactioncurrency.SetRange(ISOCurrencyCode, NAVLCYCode);
        if not FindCachedCRMTransactionCurrency(CRMTransactioncurrency) then begin
            CreateCRMTransactioncurrency(CRMTransactioncurrency, NAVLCYCode);
            AddToCacheCRMTransactionCurrency(CRMTransactioncurrency);
        end;
        exit(CRMTransactioncurrency.TransactionCurrencyId);
    end;

    procedure GetBaseCurrencyPrecision() DecimalPrecision: Decimal
    var
        CRMConnectionSetup: Record "CRM Connection Setup";
        BaseCurrencyPrecision: integer;
        handled: Boolean;
    begin
        DecimalPrecision := 1;
        OnGetCDSBaseCurrencyPrecision(BaseCurrencyPrecision, handled);

        if not handled then begin
            CRMConnectionSetup.Get();
            BaseCurrencyPrecision := CRMConnectionSetup.BaseCurrencyPrecision;
        end;

        if BaseCurrencyPrecision > 0 then
            DecimalPrecision := Power(10, -BaseCurrencyPrecision);
    end;

    procedure GetCRMCurrencyDefaultPrecision(): Integer
    var
        CRMConnectionSetup: Record "CRM Connection Setup";
        CurrencyDecimalPrecision: Integer;
        handled: Boolean;
    begin
        OnGetCDSCurrencyDecimalPrecision(CurrencyDecimalPrecision, handled);

        if handled then
            exit(CurrencyDecimalPrecision);

        CRMConnectionSetup.Get();
        exit(CRMConnectionSetup.CurrencyDecimalPrecision);
    end;

    local procedure GetCRMBaseCurrencyId(): Guid
    var
        CRMConnectionSetup: Record "CRM Connection Setup";
        BaseCurrencyId: Guid;
        handled: Boolean;
    begin
        OnGetCDSBaseCurrencyId(BaseCurrencyId, handled);

        if handled then
            exit(BaseCurrencyId);

        CRMConnectionSetup.Get();
        exit(CRMConnectionSetup.BaseCurrencyId);
    end;

    local procedure GetCRMBaseCurrencySymbol(): Text[5]
    var
        CRMConnectionSetup: Record "CRM Connection Setup";
        BaseCurrencySymbol: Text[5];
        handled: Boolean;
    begin
        OnGetCDSBaseCurrencySymbol(BaseCurrencySymbol, handled);

        if handled then
            exit(BaseCurrencySymbol);

        CRMConnectionSetup.Get();
        exit(CRMConnectionSetup.BaseCurrencySymbol);
    end;

    local procedure GetCRMExchangeRateRoundingPrecision(): Decimal
    begin
        exit(0.0000000001);
    end;

    procedure GetCRMLCYToFCYExchangeRate(ToCurrencyISOCode: Text[10]): Decimal
    var
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
        BaseCurrencyId: Guid;
        BaseCurrencySymbol: Text[5];
    begin
        BaseCurrencyId := GetCRMBaseCurrencyId();
        BaseCurrencySymbol := GetCRMBaseCurrencySymbol();
        if IsNullGuid(BaseCurrencyId) then
            Error(BaseCurrencyIsNullErr);
        if ToCurrencyISOCode = DelChr(BaseCurrencySymbol) then
            exit(1.0);

        CRMTransactioncurrency.SetRange(TransactionCurrencyId, BaseCurrencyId);
        if not FindCachedCRMTransactionCurrency(CRMTransactioncurrency) then
            Error(DynamicsCRMTransactionCurrencyRecordNotFoundErr, BaseCurrencySymbol);
        exit(GetFCYtoFCYExchangeRate(CRMTransactioncurrency.ISOCurrencyCode, ToCurrencyISOCode));
    end;

    procedure GetFCYtoFCYExchangeRate(FromFCY: Code[10]; ToFCY: Code[10]): Decimal
    var
        Currency: Record Currency;
        CurrencyExchangeRate: Record "Currency Exchange Rate";
        CalculatedExchangeRate: Decimal;
        NavLCYCode: Code[10];
    begin
        FromFCY := DelChr(FromFCY);
        ToFCY := DelChr(ToFCY);
        if (FromFCY = '') or (ToFCY = '') then
            Error(CRMBaseCurrencyNotFoundInNAVErr, '', ToFCY, FromFCY);

        if ToFCY = FromFCY then
            exit(1.0);

        NavLCYCode := GetNavLCYCode();
        if ToFCY = NavLCYCode then
            ToFCY := '';

        if FromFCY = NavLCYCode then
            exit(CurrencyExchangeRate.GetCurrentCurrencyFactor(ToFCY));

        if not Currency.Get(FromFCY) then
            Error(CRMBaseCurrencyNotFoundInNAVErr, FromFCY, ToFCY, FromFCY);

        // In CRM exchange rate is inverted, so ExchangeAmtFCYToFCY takes (ToFCY,FromFCY) instead of (FromFCY,ToFCY)
        CalculatedExchangeRate := CurrencyExchangeRate.ExchangeAmtFCYToFCY(WorkDate(), ToFCY, FromFCY, 1);
        CalculatedExchangeRate := Round(CalculatedExchangeRate, GetCRMExchangeRateRoundingPrecision(), '=');
        exit(CalculatedExchangeRate);
    end;

    local procedure GetNavLCYCode(): Code[10]
    var
        GeneralLedgerSetup: Record "General Ledger Setup";
    begin
        GeneralLedgerSetup.Get();
        GeneralLedgerSetup.TestField("LCY Code");
        exit(GeneralLedgerSetup."LCY Code");
    end;

    procedure GetUnitGroupName(UnitOfMeasureCode: Text): Text[200]
    begin
        exit(CopyStr(StrSubstNo('%1 %2', NavTxt, UnitOfMeasureCode), 1, 200));
    end;

    procedure GetUnitOfMeasureName(UnitOfMeasureRecordRef: RecordRef): Text[100]
    var
        UnitOfMeasure: Record "Unit of Measure";
        UnitOfMeasureCodeFieldRef: FieldRef;
    begin
        UnitOfMeasureCodeFieldRef := UnitOfMeasureRecordRef.Field(UnitOfMeasure.FieldNo(Code));
        exit(CopyStr(Format(UnitOfMeasureCodeFieldRef.Value()), 1, 100));
    end;

    procedure SetCRMDecimalsSupportedValue(var CRMProduct: Record "CRM Product")
    var
        CRMSetupDefaults: Codeunit "CRM Setup Defaults";
    begin
        CRMProduct.QuantityDecimal := CRMSetupDefaults.GetProductQuantityPrecision();
    end;

    procedure SetCRMDefaultPriceListOnProduct(var CRMProduct: Record "CRM Product") AdditionalFieldsWereModified: Boolean
    var
        CRMPricelevel: Record "CRM Pricelevel";
    begin
        FindCRMDefaultPriceList(CRMPricelevel);

        if CRMProduct.PriceLevelId <> CRMPricelevel.PriceLevelId then begin
            CRMProduct.PriceLevelId := CRMPricelevel.PriceLevelId;
            AdditionalFieldsWereModified := true;
        end;
    end;

    procedure SetCRMProductStateToActive(var CRMProduct: Record "CRM Product")
    begin
        CRMProduct.StateCode := CRMProduct.StateCode::Active;
        CRMProduct.StatusCode := CRMProduct.StatusCode::Active;
    end;

    procedure SetCRMProductStateToRetired(var CRMProduct: Record "CRM Product")
    begin
        CRMProduct.StateCode := CRMProduct.StateCode::Retired;
        CRMProduct.StatusCode := CRMProduct.StatusCode::Retired;
    end;

    procedure SetContactParentCompany(AccountID: Guid; DestinationContactRecordRef: RecordRef): Boolean
    var
        Contact: Record Contact;
        CompanyContact: Record Contact;
        Result: Boolean;
        OutOfMapFilter: Boolean;
    begin
        Session.LogMessage('0000ECD', SetContactParentCompanyTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
        if DestinationContactRecordRef.Number() <> DATABASE::Contact then
            Error(InvalidDestinationRecordNoErr);

        Result := FindContactByAccountId(CompanyContact, AccountID, OutOfMapFilter);

        if not Result then
            OnFailedFindContactByAccountId(DestinationContactRecordRef, CompanyContact, AccountID, OutOfMapFilter, Result);

        DestinationContactRecordRef.SetTable(Contact);
        Contact."Company No." := CompanyContact."No.";
        Contact."Company Name" := CompanyContact.Name;
        Contact.UpdateBusinessRelation();
        DestinationContactRecordRef.GetTable(Contact);

        Session.LogMessage('0000ECI', StrSubstNo(SetContactParentCompanySuccessfulTxt, CompanyContact."No."), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
        exit(Result or OutOfMapFilter);
    end;

    [Scope('Cloud')]
    procedure SynchRecordIfMappingExists(TableNo: Integer; IntegrationTableNo: Integer; PrimaryKey: Variant): Boolean
    var
        OutOfMapFilter: Boolean;
    begin
        exit(SynchRecordIfMappingExists(TableNo, IntegrationTableNo, PrimaryKey, OutOfMapFilter));
    end;

    [Scope('Cloud')]
    procedure SynchRecordIfMappingExists(TableNo: Integer; IntegrationTableNo: Integer; PrimaryKey: Variant; var OutOfMapFilter: Boolean): Boolean
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        IntegrationSynchJob: Record "Integration Synch. Job";
        CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
        NewJobEntryId: Guid;
    begin
        if IntegrationTableMapping.FindMapping(TableNo, IntegrationTableNo) then begin
            NewJobEntryId := CRMIntegrationTableSynch.SynchRecord(IntegrationTableMapping, PrimaryKey, true, false);
            OutOfMapFilter := CRMIntegrationTableSynch.GetOutOfMapFilter();
        end;

        if IsNullGuid(NewJobEntryId) then
            exit(false);
        if IntegrationSynchJob.Get(NewJobEntryId) then
            exit(
              (IntegrationSynchJob.Inserted > 0) or
              (IntegrationSynchJob.Modified > 0) or
              (IntegrationSynchJob.Unchanged > 0));
    end;

    procedure UpdateCRMCurrencyIdIfChanged(CurrencyCode: Text; var DestinationCurrencyIDFieldRef: FieldRef): Boolean
    begin
        // Given a source NAV currency code, find a currency with the same ISO code in CRM and update the target CRM currency value if needed
        exit(UpdateFieldRefValueIfChanged(DestinationCurrencyIDFieldRef, GetCRMTransactioncurrency(CurrencyCode)));
    end;

    procedure UpdateCRMInvoiceStatus(var CRMInvoice: Record "CRM Invoice"; SalesInvoiceHeader: Record "Sales Invoice Header")
    var
        CustLedgerEntry: Record "Cust. Ledger Entry";
    begin
        CustLedgerEntry.SetRange("Document No.", SalesInvoiceHeader."No.");
        CustLedgerEntry.SetRange("Document Type", CustLedgerEntry."Document Type"::Invoice);
        if CustLedgerEntry.FindFirst() then
            UpdateCRMInvoiceStatusFromEntry(CRMInvoice, CustLedgerEntry);
    end;

    internal procedure CancelCRMInvoice(var CRMInvoice: Record "CRM Invoice"): Integer
    var
        NewCRMInvoice: Record "CRM Invoice";
        ChangeNeeded: Boolean;
    begin
        NewCRMInvoice.StateCode := NewCRMInvoice.StateCode::Canceled;
        NewCRMInvoice.StatusCode := NewCRMInvoice.StatusCode::Canceled;
        ChangeNeeded := false;
        OnCancelCRMInvoiceOnBeforeCheckFieldsChanged(CRMInvoice, NewCRMInvoice, ChangeNeeded);
        if ChangeNeeded or (NewCRMInvoice.StateCode <> CRMInvoice.StateCode) or (NewCRMInvoice.StatusCode <> CRMInvoice.StatusCode) then begin
            ActivateInvoiceForFurtherUpdate(CRMInvoice);
            CRMInvoice.StateCode := NewCRMInvoice.StateCode;
            CRMInvoice.StatusCode := NewCRMInvoice.StatusCode;
            OnCancelCRMInvoiceOnBeforeModifyCRMInvoice(CRMInvoice, NewCRMInvoice);
            CRMInvoice.Modify();
            exit(1);
        end;
    end;

    procedure UpdateCRMInvoiceStatusFromEntry(var CRMInvoice: Record "CRM Invoice"; CustLedgerEntry: Record "Cust. Ledger Entry"): Integer
    var
        NewCRMInvoice: Record "CRM Invoice";
        ChangeNeeded: Boolean;
    begin
        CalculateActualStatusCode(CustLedgerEntry, NewCRMInvoice);
        ChangeNeeded := false;
        OnUpdateCRMInvoiceStatusFromEntryOnBeforeCheckFieldsChanged(CRMInvoice, NewCRMInvoice, CustLedgerEntry, ChangeNeeded);
        if ChangeNeeded or (NewCRMInvoice.StateCode <> CRMInvoice.StateCode) or (NewCRMInvoice.StatusCode <> CRMInvoice.StatusCode) then begin
            ActivateInvoiceForFurtherUpdate(CRMInvoice);
            CRMInvoice.StateCode := NewCRMInvoice.StateCode;
            CRMInvoice.StatusCode := NewCRMInvoice.StatusCode;
            OnUpdateCRMInvoiceStatusFromEntryOnBeforeModify(CRMInvoice, NewCRMInvoice, CustLedgerEntry);
            CRMInvoice.Modify();
            exit(1);
        end;
    end;

    local procedure CalculateActualStatusCode(CustLedgerEntry: Record "Cust. Ledger Entry"; var CRMInvoice: Record "CRM Invoice")
    var
        IsHandled: Boolean;
    begin
        CustLedgerEntry.CalcFields("Remaining Amount", Amount);

        IsHandled := false;
        OnBeforeCalculateActualStatusCode(CustLedgerEntry, CRMInvoice, IsHandled);
        if IsHandled then
            exit;

        if CustLedgerEntry."Remaining Amount" = 0 then begin
            CRMInvoice.StateCode := CRMInvoice.StateCode::Paid;
            CRMInvoice.StatusCode := CRMInvoice.StatusCode::Complete;
        end else
            if CustLedgerEntry."Remaining Amount" <> CustLedgerEntry.Amount then begin
                CRMInvoice.StateCode := CRMInvoice.StateCode::Paid;
                CRMInvoice.StatusCode := CRMInvoice.StatusCode::Partial;
            end else begin
                CRMInvoice.StateCode := CRMInvoice.StateCode::Active;
                CRMInvoice.StatusCode := CRMInvoice.StatusCode::Billed;
            end;
    end;

    local procedure ActivateInvoiceForFurtherUpdate(var CRMInvoice: Record "CRM Invoice")
    begin
        if CRMInvoice.StateCode <> CRMInvoice.StateCode::Active then begin
            CRMInvoice.StateCode := CRMInvoice.StateCode::Active;
            CRMInvoice.StatusCode := CRMInvoice.StatusCode::Billed;
            CRMInvoice.Modify();
        end;
    end;

    procedure UpdateCRMPriceListItem(var CRMProduct: Record "CRM Product") AdditionalFieldsWereModified: Boolean
    var
        CRMProductpricelevel: Record "CRM Productpricelevel";
    begin
        if IsNullGuid(CRMProduct.ProductId) then
            exit(false);

        AdditionalFieldsWereModified := SetCRMDefaultPriceListOnProduct(CRMProduct);
        CRMProductpricelevel.SetRange(ProductId, CRMProduct.ProductId);
        CRMProductpricelevel.SetRange(PriceLevelId, CRMProduct.PriceLevelId);
        if CRMProductpricelevel.FindFirst() then
            exit(UpdateCRMProductpricelevel(CRMProductpricelevel, CRMProduct) or AdditionalFieldsWereModified);

        CreateCRMProductpricelevelForProduct(CRMProduct, CRMProduct.PriceLevelId);
        exit(true);
    end;

    procedure UpdateCRMPriceListItems(var CRMProduct: Record "CRM Product") AdditionalFieldsWereModified: Boolean
    var
        CRMUom: Record "CRM Uom";
    begin
        if IsNullGuid(CRMProduct.ProductId) then
            exit(false);

        AdditionalFieldsWereModified := SetCRMDefaultPriceListOnProduct(CRMProduct);
        CRMUom.SetRange(UoMScheduleId, CRMProduct.DefaultUoMScheduleId);
        if CRMUom.FindSet() then
            repeat
                if UpdateOrCreateCRMProductprivelevelForUom(CRMProduct, CRMUom) then
                    AdditionalFieldsWereModified := true;
            until CRMUom.Next() = 0;
        exit(AdditionalFieldsWereModified);
    end;

    procedure UpdateCRMPriceListItemForUom(var CRMProduct: Record "CRM Product"; CRMUom: Record "CRM Uom") AdditionalFieldsWereModified: Boolean
    begin
        if IsNullGuid(CRMProduct.ProductId) or IsNullGuid(CRMUom.UoMId) then
            exit(false);

        AdditionalFieldsWereModified := SetCRMDefaultPriceListOnProduct(CRMProduct);

        if UpdateOrCreateCRMProductprivelevelForUom(CRMProduct, CRMUom) then
            AdditionalFieldsWereModified := true;

        exit(AdditionalFieldsWereModified);
    end;

    local procedure UpdateOrCreateCRMProductprivelevelForUom(var CRMProduct: Record "CRM Product"; CRMUom: Record "CRM Uom"): Boolean
    var
        CRMProductpricelevel: Record "CRM Productpricelevel";
    begin
        CRMProductpricelevel.SetRange(ProductId, CRMProduct.ProductId);
        CRMProductpricelevel.SetRange(PriceLevelId, CRMProduct.PriceLevelId);
        CRMProductpricelevel.SetRange(UoMScheduleId, CRMProduct.DefaultUoMScheduleId);
        CRMProductpricelevel.SetRange(UoMId, CRMUom.UoMId);
        if CRMProductpricelevel.FindFirst() then begin
            if UpdateCRMProductpricelevelWithUom(CRMProductpricelevel, CRMProduct, CRMUom) then
                exit(true);
        end else begin
            CreateCRMProductpricelevelForProductAndUom(CRMProduct, CRMProduct.PriceLevelId, CRMUom);
            exit(true);
        end;
    end;

    procedure UpdateCRMProductPriceIfNegative(var CRMProduct: Record "CRM Product"): Boolean
    begin
        // CRM doesn't allow negative prices. Update the price to zero, if negative (this preserves the behavior of the old CRM Connector)
        if CRMProduct.Price < 0 then begin
            CRMProduct.Price := 0;
            exit(true);
        end;

        exit(false);
    end;

    procedure UpdateCRMProductQuantityOnHandIfNegative(var CRMProduct: Record "CRM Product"): Boolean
    begin
        // Update to zero, if negative (this preserves the behavior of the old CRM Connector)
        if CRMProduct.QuantityOnHand < 0 then begin
            CRMProduct.QuantityOnHand := 0;
            exit(true);
        end;

        exit(false);
    end;

    local procedure UpdateCRMProductpricelevel(var CRMProductpricelevel: Record "CRM Productpricelevel"; CRMProduct: Record "CRM Product") AdditionalFieldsWereModified: Boolean
    begin
        if CRMProductpricelevel.PriceLevelId <> CRMProduct.PriceLevelId then begin
            CRMProductpricelevel.PriceLevelId := CRMProduct.PriceLevelId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.UoMId <> CRMProduct.DefaultUoMId then begin
            CRMProductpricelevel.UoMId := CRMProduct.DefaultUoMId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.UoMScheduleId <> CRMProduct.DefaultUoMScheduleId then begin
            CRMProductpricelevel.UoMScheduleId := CRMProduct.DefaultUoMScheduleId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.Amount <> CRMProduct.Price then begin
            CRMProductpricelevel.Amount := CRMProduct.Price;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.TransactionCurrencyId <> CRMProduct.TransactionCurrencyId then begin
            CRMProductpricelevel.TransactionCurrencyId := CRMProduct.TransactionCurrencyId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.ProductNumber <> CRMProduct.ProductNumber then begin
            CRMProductpricelevel.ProductNumber := CRMProduct.ProductNumber;
            AdditionalFieldsWereModified := true;
        end;

        if AdditionalFieldsWereModified then
            CRMProductpricelevel.Modify();
    end;

    local procedure UpdateCRMProductpricelevelWithUom(var CRMProductpricelevel: Record "CRM Productpricelevel"; CRMProduct: Record "CRM Product"; CRMUom: Record "CRM Uom") AdditionalFieldsWereModified: Boolean
    begin
        if CRMProductpricelevel.PriceLevelId <> CRMProduct.PriceLevelId then begin
            CRMProductpricelevel.PriceLevelId := CRMProduct.PriceLevelId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.UoMId <> CRMUom.UoMId then begin
            CRMProductpricelevel.UoMId := CRMUom.UoMId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.UoMScheduleId <> CRMProduct.DefaultUoMScheduleId then begin
            CRMProductpricelevel.UoMScheduleId := CRMProduct.DefaultUoMScheduleId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.Amount <> CRMProduct.Price * CRMUom.Quantity then begin
            CRMProductpricelevel.Amount := CRMProduct.Price * CRMUom.Quantity;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.TransactionCurrencyId <> CRMProduct.TransactionCurrencyId then begin
            CRMProductpricelevel.TransactionCurrencyId := CRMProduct.TransactionCurrencyId;
            AdditionalFieldsWereModified := true;
        end;

        if CRMProductpricelevel.ProductNumber <> CRMProduct.ProductNumber then begin
            CRMProductpricelevel.ProductNumber := CRMProduct.ProductNumber;
            AdditionalFieldsWereModified := true;
        end;

        if AdditionalFieldsWereModified then
            CRMProductpricelevel.Modify();
    end;

    procedure UpdateCRMProductTypeCodeIfChanged(var CRMProduct: Record "CRM Product"; NewProductTypeCode: Integer): Boolean
    begin
        // We use ProductTypeCode::SalesInventory and ProductTypeCode::Services to trace back later,
        // where this CRM product originated from: a NAV Item, or a NAV Resource
        if CRMProduct.ProductTypeCode <> NewProductTypeCode then begin
            CRMProduct.ProductTypeCode := NewProductTypeCode;
            exit(true);
        end;

        exit(false);
    end;

    procedure UpdateCRMProductStateCodeIfChanged(var CRMProduct: Record "CRM Product"; NewBlocked: Boolean): Boolean
    var
        NewStateCode: Option;
    begin
        if NewBlocked then
            NewStateCode := CRMProduct.StateCode::Retired
        else
            NewStateCode := CRMProduct.StateCode::Active;

        if NewStateCode <> CRMProduct.StateCode then begin
            if NewBlocked then
                SetCRMProductStateToRetired(CRMProduct)
            else
                SetCRMProductStateToActive(CRMProduct);
            exit(true);
        end;

        exit(false);
    end;

    procedure UpdateItemBlockedIfChanged(var Item: Record Item; NewBlocked: Boolean): Boolean
    begin
        if Item.Blocked <> NewBlocked then begin
            Item.Blocked := NewBlocked;
            exit(true);
        end;
    end;

    procedure UpdateResourceBlockedIfChanged(var Resource: Record Resource; NewBlocked: Boolean): Boolean
    begin
        if Resource.Blocked <> NewBlocked then begin
            Resource.Blocked := NewBlocked;
            exit(true);
        end;
    end;

    procedure UpdateCRMProductUoMFieldsIfChanged(var CRMProduct: Record "CRM Product"; UnitOfMeasureCode: Code[10]): Boolean
    var
        CRMUom: Record "CRM Uom";
        CRMUomschedule: Record "CRM Uomschedule";
        AdditionalFieldsWereModified: Boolean;
    begin
        // Get the unit of measure ID used in this product
        // On that unit of measure ID, get the UoMName, UomscheduleID, UomscheduleName and update them in the product if needed

        GetValidCRMUnitOfMeasureRecords(CRMUom, CRMUomschedule, UnitOfMeasureCode);

        // Update UoM ID if changed
        if CRMProduct.DefaultUoMId <> CRMUom.UoMId then begin
            CRMProduct.DefaultUoMId := CRMUom.UoMId;
            AdditionalFieldsWereModified := true;
        end;

        // Update the Uomschedule ID if changed
        if CRMProduct.DefaultUoMScheduleId <> CRMUomschedule.UoMScheduleId then begin
            CRMProduct.DefaultUoMScheduleId := CRMUomschedule.UoMScheduleId;
            AdditionalFieldsWereModified := true;
        end;

        exit(AdditionalFieldsWereModified);
    end;

    procedure UpdateCRMProductUomscheduleId(var CRMProduct: Record "CRM Product"; SourceRecordRef: RecordRef): Boolean
    var
        Item: Record Item;
        Resource: Record Resource;
        UnitGroup: Record "Unit Group";
        CRMUomschedule: Record "CRM Uomschedule";
        AdditionalFieldsWereModified: Boolean;
    begin
        if SourceRecordRef.Number() = Database::Item then begin
            SourceRecordRef.SetTable(Item);
            if not UnitGroup.Get(UnitGroup."Source Type"::Item, Item.SystemId) then
                Error(ItemUnitGroupNotFoundErr, Item."No.");
        end;

        if SourceRecordRef.Number() = Database::Resource then begin
            SourceRecordRef.SetTable(Resource);
            if not UnitGroup.Get(UnitGroup."Source Type"::Resource, Resource.SystemId) then
                Error(ResourceUnitGroupNotFoundErr, Resource."No.");
        end;

        CRMUomschedule.SetRange(Name, UnitGroup.GetCode());
        if not CRMUomschedule.FindFirst() then
            Error(CRMUnitGroupNotFoundErr, UnitGroup.GetCode());

        if CRMProduct.DefaultUoMScheduleId <> CRMUomschedule.UoMScheduleId then begin
            CRMProduct.DefaultUoMScheduleId := CRMUomschedule.UoMScheduleId;
            AdditionalFieldsWereModified := true;
        end;

        exit(AdditionalFieldsWereModified);
    end;

    procedure UpdateCRMProductVendorNameIfChanged(var CRMProduct: Record "CRM Product"): Boolean
    var
        Vendor: Record Vendor;
    begin
        if not Vendor.Get(CopyStr(CRMProduct.VendorID, 1, MaxStrLen(Vendor."No."))) then
            exit(false);

        if CRMProduct.VendorName <> Vendor.Name then begin
            CRMProduct.VendorName := Vendor.Name;
            exit(true);
        end;

        exit(false);
    end;

    procedure UpdateOwnerIfChanged(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; SourceSalespersonCodeFieldNo: Integer; DestinationOwnerFieldNo: Integer; DestinationOwnerTypeFieldNo: Integer; DestinationOwnerTypeValue: Option): Boolean
    var
        SalespersonPurchaser: Record "Salesperson/Purchaser";
        CRMIntegrationRecord: Record "CRM Integration Record";
        IntegrationTableMapping: Record "Integration Table Mapping";
        SalespersonCodeFieldRef: FieldRef;
        OwnerFieldRef: FieldRef;
        OwnerTypeFieldRef: FieldRef;
        OwnerGuid: Guid;
        CurrentOwnerGuid: Guid;
    begin
        IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
        IntegrationTableMapping.SetRange("Table ID", DATABASE::"Salesperson/Purchaser");
        IntegrationTableMapping.SetRange("Integration Table ID", DATABASE::"CRM Systemuser");
        if not IntegrationTableMapping.FindFirst() then
            exit(false); // There are no mapping for salepeople to SystemUsers

        SalespersonCodeFieldRef := SourceRecordRef.Field(SourceSalespersonCodeFieldNo);

        // Ignore empty salesperson code.
        if Format(SalespersonCodeFieldRef.Value()) = '' then
            exit(false);

        SalespersonPurchaser.SetFilter(Code, Format(SalespersonCodeFieldRef.Value()));
        if not SalespersonPurchaser.FindFirst() then
            Error(RecordNotFoundErr, SalespersonCodeFieldRef.Value(), SalespersonPurchaser.TableCaption());

        if not CRMIntegrationRecord.FindIDFromRecordID(SalespersonPurchaser.RecordId(), OwnerGuid) then
            Error(
              RecordMustBeCoupledErr, SalespersonPurchaser.TableCaption(), SalespersonCodeFieldRef.Value(),
              IntegrationTableMapping.GetExtendedIntegrationTableCaption());

        OwnerFieldRef := DestinationRecordRef.Field(DestinationOwnerFieldNo);
        CurrentOwnerGuid := OwnerFieldRef.Value();
        if CurrentOwnerGuid <> OwnerGuid then begin
            OwnerFieldRef.Value := OwnerGuid;
            OwnerTypeFieldRef := DestinationRecordRef.Field(DestinationOwnerTypeFieldNo);
            OwnerTypeFieldRef.Value := DestinationOwnerTypeValue;
            exit(true);
        end;

        exit(false);
    end;

    procedure UpdateContactOnModifyCustomer(RecRef: RecordRef)
    var
        Customer: Record Customer;
        CustContUpdate: Codeunit "CustCont-Update";
    begin
        if RecRef.Number() = DATABASE::Customer then begin
            RecRef.SetTable(Customer);
            CustContUpdate.OnModify(Customer);
        end;
    end;

    procedure UpdateContactOnModifyVendor(RecRef: RecordRef)
    var
        Vendor: Record Vendor;
        VendContUpdate: Codeunit "VendCont-Update";
    begin
        if RecRef.Number() = DATABASE::Vendor then begin
            RecRef.SetTable(Vendor);
            VendContUpdate.OnModify(Vendor);
        end;
    end;

    internal procedure FindOpportunityRelatedCustomer(SourceRecordRef: RecordRef; var ContactBusinessRelation: Record "Contact Business Relation"): Boolean
    var
        Opportunity: Record Opportunity;
        ContactCompanyNo: Code[20];
    begin
        ContactCompanyNo := SourceRecordRef.Field(Opportunity.FieldNo("Contact Company No.")).Value();
        exit(FindContactCompanyRelatedCustomer(ContactCompanyNo, ContactBusinessRelation));
    end;

    procedure FindContactRelatedCustomer(SourceRecordRef: RecordRef; var ContactBusinessRelation: Record "Contact Business Relation"): Boolean
    var
        Contact: Record Contact;
        ContactCompanyNo: Code[20];
    begin
        ContactCompanyNo := SourceRecordRef.Field(Contact.FieldNo("Company No.")).Value();
        exit(FindContactCompanyRelatedCustomer(ContactCompanyNo, ContactBusinessRelation));
    end;

    local procedure FindContactCompanyRelatedCustomer(ContactCompanyNo: Code[20]; var ContactBusinessRelation: Record "Contact Business Relation"): Boolean
    var
        Contact: Record Contact;
        MarketingSetup: Record "Marketing Setup";
    begin
        if not Contact.Get(ContactCompanyNo) then
            exit(false);

        MarketingSetup.Get();
        ContactBusinessRelation.SetFilter("Business Relation Code", MarketingSetup."Bus. Rel. Code for Customers");
        ContactBusinessRelation.SetRange("Link to Table", ContactBusinessRelation."Link to Table"::Customer);
        ContactBusinessRelation.SetFilter("Contact No.", Contact."No.");
        exit(ContactBusinessRelation.FindFirst());
    end;

    procedure FindContactRelatedVendor(SourceRecordRef: RecordRef; var ContactBusinessRelation: Record "Contact Business Relation"): Boolean
    var
        Vendor: Record Contact;
        MarketingSetup: Record "Marketing Setup";
        CompanyNoFieldRef: FieldRef;
    begin
        // Tranfer the parent company id to the ParentCustomerId
        CompanyNoFieldRef := SourceRecordRef.Field(Vendor.FieldNo("Company No."));
        if not Vendor.Get(Format(CompanyNoFieldRef.Value())) then
            exit(false);

        MarketingSetup.Get();
        ContactBusinessRelation.SetFilter("Business Relation Code", MarketingSetup."Bus. Rel. Code for Vendors");
        ContactBusinessRelation.SetRange("Link to Table", ContactBusinessRelation."Link to Table"::Vendor);
        ContactBusinessRelation.SetFilter("Contact No.", Vendor."No.");
        exit(ContactBusinessRelation.FindFirst());
    end;

    procedure UpdateSalesPersonCodeIfChanged(SourceRecordRef: RecordRef; var DestinationRecordRef: RecordRef; SourceOwnerIDFieldNo: Integer; SourceOwnerTypeFieldNo: Integer; AllowedOwnerTypeValue: Option; DestinationSalesPersonCodeFieldNo: Integer): Boolean
    var
        CRMIntegrationRecord: Record "CRM Integration Record";
        SalespersonPurchaser: Record "Salesperson/Purchaser";
        IntegrationTableMapping: Record "Integration Table Mapping";
        OutlookSynchTypeConv: Codeunit "Outlook Synch. Type Conv";
        SalesPersonRecordID: RecordID;
        SourceFieldRef: FieldRef;
        DestinationFieldRef: FieldRef;
        CRMSystemUserID: Guid;
        CurrentOptionValue: Integer;
    begin
        if not IntegrationTableMapping.FindMapping(Database::"Salesperson/Purchaser", Database::"CRM Systemuser") then
            exit(false); // There are no mapping for salepeople to SystemUsers

        SourceFieldRef := SourceRecordRef.Field(SourceOwnerTypeFieldNo);
        CurrentOptionValue := OutlookSynchTypeConv.TextToOptionValue(Format(SourceFieldRef.Value()), SourceFieldRef.OptionMembers());
        // Allow 0 as it is the default value for CRM options.
        if (CurrentOptionValue <> 0) and (CurrentOptionValue <> AllowedOwnerTypeValue) then
            Error(CanOnlyUseSystemUserOwnerTypeErr, CRMProductName.CDSServiceName());

        SourceFieldRef := SourceRecordRef.Field(SourceOwnerIDFieldNo);
        CRMSystemUserID := SourceFieldRef.Value();

        if IsNullGuid(CRMSystemUserID) then
            exit(false);

        DestinationFieldRef := DestinationRecordRef.Field(DestinationSalesPersonCodeFieldNo);

        if not CRMIntegrationRecord.FindRecordIDFromID(CRMSystemUserID, Database::"Salesperson/Purchaser", SalesPersonRecordID) then begin
            if not SynchRecordIfMappingExists(Database::"Salesperson/Purchaser", Database::"CRM Systemuser", CRMSystemUserID) then
                exit(false);
            if not CRMIntegrationRecord.FindRecordIDFromID(CRMSystemUserID, Database::"Salesperson/Purchaser", SalesPersonRecordID) then
                exit(false);
        end;

        if not SalespersonPurchaser.Get(SalesPersonRecordID) then
            exit(false);

        exit(UpdateFieldRefValueIfChanged(DestinationFieldRef, SalespersonPurchaser.Code));
    end;

    procedure UpdateFieldRefValueIfChanged(var DestinationFieldRef: FieldRef; NewFieldValue: Text): Boolean
    begin
        // Compare and updates the fieldref value, if different
        if Format(DestinationFieldRef.Value()) = NewFieldValue then
            exit(false);

        // Return TRUE if the value was changed
        DestinationFieldRef.Value := NewFieldValue;
        exit(true);
    end;

    procedure GetValidCRMUnitOfMeasureRecords(var CRMUom: Record "CRM Uom"; var CRMUomschedule: Record "CRM Uomschedule"; UnitOfMeasureCode: Code[10])
    var
        CRMUnitGroupName: Text;
    begin
        // This function checks that the CRM Unit of Measure and its parent group exist in CRM, and that the user didn't change their properties from
        // the expected ones

        // Attempt to get the Uomschedule with the expected name = 'NAV ' + UnitOfMeasureCode
        CRMUnitGroupName := GetUnitGroupName(UnitOfMeasureCode);
        CRMUomschedule.SetRange(Name, CRMUnitGroupName);

        // CRM Unit Group - Not found
        if not FindCachedCRMUomschedule(CRMUomschedule) then
            Error(DynamicsCRMUomscheduleNotFoundErr, CRMUnitGroupName, CRMProductName.CDSServiceName());

        // CRM Unit Group  - Multiple found
        if CountCRMUomschedule(CRMUomschedule) > 1 then
            Error(DynamicsCRMUoMFoundMultipleInGroupErr, CRMUnitGroupName, CRMProductName.CDSServiceName());

        // CRM Unit Group - One found - check its child unit of measure, should be just one
        CRMUom.SetRange(UoMScheduleId, CRMUomschedule.UoMScheduleId);

        // CRM Unit of Measure - not found
        if not FindCachedCRMUom(CRMUom) then
            Error(DynamicsCRMUoMNotFoundInGroupErr, CRMUomschedule.Name, CRMProductName.CDSServiceName());

        // CRM Unit of Measure - multiple found
        if CountCRMUom(CRMUom) > 1 then
            Error(DynamicsCRMUoMFoundMultipleInGroupErr, CRMUomschedule.Name, CRMProductName.CDSServiceName());

        // CRM Unit of Measure - one found, does it have the correct name?
        if CRMUom.Name <> UnitOfMeasureCode then
            Error(IncorrectCRMUoMNameErr, CRMUomschedule.Name, UnitOfMeasureCode, CRMUom.Name);

        // CRM Unit of Measure should be the base
        if not CRMUom.IsScheduleBaseUoM then
            Error(IncorrectCRMUoMStatusErr, CRMUom.Name, CRMUomschedule.Name);

        // CRM Unit of Measure should have the conversion rate of 1
        if CRMUom.Quantity <> 1 then
            Error(IncorrectCRMUoMQuantityErr, CRMUom.Name);

        // All checks passed. We're good to go
    end;

    procedure GetNavCurrencyCode(TransactionCurrencyId: Guid): Code[10]
    var
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
        Currency: Record Currency;
        NAVLCYCode: Code[10];
        CRMCurrencyCode: Code[10];
    begin
        if IsNullGuid(TransactionCurrencyId) then
            exit('');
        NAVLCYCode := GetNavLCYCode();
        CRMTransactioncurrency.SetRange(TransactionCurrencyId, TransactionCurrencyId);
        if not FindCachedCRMTransactionCurrency(CRMTransactioncurrency) then
            Error(DynamicsCRMTransactionCurrencyRecordNotFoundErr, TransactionCurrencyId);
        CRMCurrencyCode := DelChr(CRMTransactioncurrency.ISOCurrencyCode);
        if CRMCurrencyCode = NAVLCYCode then
            exit('');

        Currency.Get(CRMCurrencyCode);
        exit(Currency.Code);
    end;

    procedure GetCRMTransactioncurrency(CurrencyCode: Text): Guid
    var
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
        NAVLCYCode: Code[10];
    begin
        // In NAV, an empty currency means local currency (LCY)
        NAVLCYCode := GetNavLCYCode();
        if DelChr(CurrencyCode) = '' then
            CurrencyCode := NAVLCYCode;

        if CurrencyCode = NAVLCYCode then
            FindNAVLocalCurrencyInCRM(CRMTransactioncurrency)
        else begin
            CRMTransactioncurrency.SetRange(ISOCurrencyCode, CurrencyCode);
            if not FindCachedCRMTransactionCurrency(CRMTransactioncurrency) then
                Error(DynamicsCRMTransactionCurrencyRecordNotFoundErr, CurrencyCode);
        end;
        exit(CRMTransactioncurrency.TransactionCurrencyId)
    end;

    local procedure AddToCacheCRMPriceLevel(CRMPricelevel: Record "CRM Pricelevel")
    begin
        if TempCRMPricelevel.Get(CRMPricelevel.PriceLevelId) then begin
            TempCRMPricelevel := CRMPricelevel;
            TempCRMPricelevel.Modify();
        end else begin
            TempCRMPricelevel := CRMPricelevel;
            TempCRMPricelevel.Insert();
        end;
    end;

    local procedure CacheCRMPriceLevel(): Boolean
    var
        CRMPricelevel: Record "CRM Pricelevel";
    begin
        TempCRMPricelevel.Reset();
        if TempCRMPricelevel.IsEmpty() then
            if CRMPricelevel.FindSet() then
                repeat
                    AddToCacheCRMPriceLevel(CRMPricelevel)
                until CRMPricelevel.Next() = 0;
        exit(not TempCRMPricelevel.IsEmpty());
    end;

    local procedure FindCachedCRMPriceLevel(var CRMPricelevel: Record "CRM Pricelevel"): Boolean
    begin
        if not CacheCRMPriceLevel() then
            exit(false);
        TempCRMPricelevel.Copy(CRMPricelevel);
        if TempCRMPricelevel.FindFirst() then begin
            CRMPricelevel.Copy(TempCRMPricelevel);
            exit(true);
        end;
    end;

    local procedure AddToCacheCRMTransactionCurrency(CRMTransactioncurrency: Record "CRM Transactioncurrency")
    begin
        TempCRMTransactioncurrency := CRMTransactioncurrency;
        TempCRMTransactioncurrency.Insert();
    end;

    local procedure CacheCRMTransactionCurrency(): Boolean
    var
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
    begin
        TempCRMTransactioncurrency.Reset();
        if TempCRMTransactioncurrency.IsEmpty() then
            if CRMTransactioncurrency.FindSet() then
                repeat
                    AddToCacheCRMTransactionCurrency(CRMTransactioncurrency)
                until CRMTransactioncurrency.Next() = 0;
        exit(not TempCRMTransactioncurrency.IsEmpty());
    end;

    local procedure FindCachedCRMTransactionCurrency(var CRMTransactioncurrency: Record "CRM Transactioncurrency"): Boolean
    begin
        if not CacheCRMTransactionCurrency() then
            exit(false);
        TempCRMTransactioncurrency.Copy(CRMTransactioncurrency);
        if TempCRMTransactioncurrency.FindFirst() then begin
            CRMTransactioncurrency.Copy(TempCRMTransactioncurrency);
            exit(true);
        end;
    end;

    local procedure AddToCacheCRMUom(CRMUom: Record "CRM Uom")
    begin
        TempCRMUom := CRMUom;
        TempCRMUom.Insert();
    end;

    local procedure CacheCRMUom(): Boolean
    var
        CRMUom: Record "CRM Uom";
    begin
        TempCRMUom.Reset();
        if TempCRMUom.IsEmpty() then
            if CRMUom.FindSet() then
                repeat
                    AddToCacheCRMUom(CRMUom)
                until CRMUom.Next() = 0;
        exit(not TempCRMUom.IsEmpty());
    end;

    local procedure CountCRMUom(var CRMUom: Record "CRM Uom"): Integer
    begin
        TempCRMUom.Copy(CRMUom);
        exit(TempCRMUom.Count());
    end;

    local procedure FindCachedCRMUom(var CRMUom: Record "CRM Uom"): Boolean
    begin
        if not CacheCRMUom() then
            exit(false);
        TempCRMUom.Copy(CRMUom);
        if TempCRMUom.FindFirst() then begin
            CRMUom.Copy(TempCRMUom);
            exit(true);
        end;
    end;

    local procedure AddToCacheCRMUomschedule(CRMUomschedule: Record "CRM Uomschedule")
    begin
        TempCRMUomschedule := CRMUomschedule;
        TempCRMUomschedule.Insert();
    end;

    local procedure CacheCRMUomschedule(): Boolean
    var
        CRMUomschedule: Record "CRM Uomschedule";
    begin
        TempCRMUomschedule.Reset();
        if TempCRMUomschedule.IsEmpty() then
            if CRMUomschedule.FindSet() then
                repeat
                    AddToCacheCRMUomschedule(CRMUomschedule)
                until CRMUomschedule.Next() = 0;
        exit(not TempCRMUomschedule.IsEmpty());
    end;

    local procedure CountCRMUomschedule(var CRMUomschedule: Record "CRM Uomschedule"): Integer
    begin
        TempCRMUomschedule.Copy(CRMUomschedule);
        exit(TempCRMUomschedule.Count());
    end;

    local procedure FindCachedCRMUomschedule(var CRMUomschedule: Record "CRM Uomschedule"): Boolean
    begin
        if not CacheCRMUomschedule() then
            exit(false);
        TempCRMUomschedule.Copy(CRMUomschedule);
        if TempCRMUomschedule.FindFirst() then begin
            CRMUomschedule.Copy(TempCRMUomschedule);
            exit(true);
        end;
    end;

    procedure ShowPage(RecordID: RecordID)
    var
        TableMetadata: Record "Table Metadata";
        PageManagement: Codeunit "Page Management";
        CRMIntegrationManagement: Codeunit "CRM Integration Management";
        CrmId: Guid;
        CrmIdText: Text;
    begin
        if RecordID.TableNo() = 0 then
            exit;
        if not TableMetadata.Get(RecordID.TableNo()) then
            exit;

        if not TableMetadata.DataIsExternal then begin
            PageManagement.PageRun(RecordID);
            exit;
        end;

        if TableMetadata.TableType = TableMetadata.TableType::CRM then begin
            CrmIdText := Format(RecordID);
            CrmIdText := CopyStr(CrmIdText, StrPos(CrmIdText, ':') + 1);
            Evaluate(CrmId, CrmIdText);
            HyperLink(CRMIntegrationManagement.GetCRMEntityUrlFromCRMID(RecordID.TableNo(), CrmId));
            exit;
        end;

        Error(UnableToFindPageForRecordErr, Format(RecordID, 0, 1));
    end;

    procedure FindNewValueForSpecialMapping(SourceFieldRef: FieldRef; var NewValue: Variant) IsValueFound: Boolean
    var
        CRMTransactioncurrency: Record "CRM Transactioncurrency";
        CRMID: Guid;
    begin
        case SourceFieldRef.Relation() of
            DATABASE::Currency: // special handling of Local currency
                if Format(SourceFieldRef.Value()) = '' then begin
                    FindNAVLocalCurrencyInCRM(CRMTransactioncurrency);
                    NewValue := CRMTransactioncurrency.TransactionCurrencyId;
                    IsValueFound := true;
                end;
            DATABASE::"CRM Transactioncurrency": // special handling of Local currency
                begin
                    CRMID := SourceFieldRef.Value();
                    if GetNavCurrencyCode(CRMID) = '' then begin
                        NewValue := '';
                        IsValueFound := true;
                    end;
                end;
        end;
    end;

    procedure FindPKByRecordID(RecID: RecordID; var PrimaryKey: Variant) Found: Boolean
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        KeyRef: KeyRef;
    begin
        if RecID.TableNo() = 0 then
            exit;
        Found := RecordRef.Get(RecID);
        KeyRef := RecordRef.KeyIndex(1);
        FieldRef := KeyRef.FieldIndex(1);
        PrimaryKey := FieldRef.Value();
    end;

    procedure FindRecordIDByPK(TableID: Integer; PrimaryKey: Variant; var RecID: RecordID) Found: Boolean
    var
        RecordRef: RecordRef;
        FieldRef: FieldRef;
        KeyRef: KeyRef;
    begin
        RecordRef.Open(TableID);
        KeyRef := RecordRef.KeyIndex(1);
        FieldRef := KeyRef.FieldIndex(1);
        FieldRef.SetRange(PrimaryKey);
        Found := RecordRef.FindFirst();
        RecID := RecordRef.RecordId();
        RecordRef.Close();
    end;

    procedure FindSourceIntegrationTableMapping(var IntegrationTableMapping: Record "Integration Table Mapping"; SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef): Boolean
    var
        SourceRecRef: RecordRef;
        DestinationRecRef: RecordRef;
    begin
        SourceRecRef := SourceFieldRef.Record();
        DestinationRecRef := DestinationFieldRef.Record();
        IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
        IntegrationTableMapping.SetRange("Table ID", SourceRecRef.Number());
        IntegrationTableMapping.SetRange("Integration Table ID", DestinationRecRef.Number());
        IntegrationTableMapping.SetRange("Delete After Synchronization", false);
        exit(IntegrationTableMapping.FindFirst());
    end;

    procedure IsClearValueOnFailedSync(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef): Boolean
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
        IntegrationFieldMapping: Record "Integration Field Mapping";
    begin
        if FindSourceIntegrationTableMapping(IntegrationTableMapping, SourceFieldRef, DestinationFieldRef) then begin
            IntegrationFieldMapping.SetRange("Integration Table Mapping Name", IntegrationTableMapping.Name);
            IntegrationFieldMapping.SetRange("Field No.", SourceFieldRef.Number());
            IntegrationFieldMapping.SetRange("Integration Table Field No.", DestinationFieldRef.Number());
            IntegrationFieldMapping.FindFirst();
            exit(IntegrationFieldMapping."Clear Value on Failed Sync");
        end;
        exit(false);
    end;

    procedure AreFieldsRelatedToMappedTables(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var IntegrationTableMapping: Record "Integration Table Mapping"): Boolean
    var
        SourceTableID: Integer;
        DestinationTableID: Integer;
        Direction: Integer;
        SourceTableName: Text;
        DestinationTableName: Text;
    begin
        SourceTableID := GetFieldRelation(SourceFieldRef, SourceTableName);
        DestinationTableID := GetFieldRelation(DestinationFieldRef, DestinationTableName);
        if (SourceTableID <> 0) and (DestinationTableID <> 0) then begin
            if DestinationFieldRef.Type() = FieldType::GUID then begin
                IntegrationTableMapping.SetRange("Table ID", SourceTableID);
                IntegrationTableMapping.SetRange("Integration Table ID", DestinationTableID);
                Direction := IntegrationTableMapping.Direction::ToIntegrationTable;
            end else begin
                IntegrationTableMapping.SetRange("Table ID", DestinationTableID);
                IntegrationTableMapping.SetRange("Integration Table ID", SourceTableID);
                Direction := IntegrationTableMapping.Direction::FromIntegrationTable;
            end;
            IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
            IntegrationTableMapping.SetRange("Delete After Synchronization", false);
            if IntegrationTableMapping.FindFirst() then begin
                IntegrationTableMapping.Direction := Direction;
                exit(true);
            end;
            Error(
                MappingMustBeSetForGUIDFieldErr,
                SourceTableName, DestinationTableName, SourceFieldRef.Name(), DestinationFieldRef.Name());
        end;
    end;

    procedure GetCoupledCDSUserId(SourceRecordRef: RecordRef): Guid
    var
        Customer: Record Customer;
        Vendor: Record Vendor;
        Contact: Record Contact;
        SalesHeader: Record "Sales Header";
        CRMIntegrationRecord: Record "CRM Integration Record";
        IntegrationTableMapping: Record "Integration Table Mapping";
        SalesPersonPurchaser: Record "Salesperson/Purchaser";
        SalesPersonPurchaserFieldRef: FieldRef;
        SalesPersonPurchaserCode: Code[20];
        CDSUserId: Guid;
    begin
        case SourceRecordRef.Number() of
            Database::Customer:
                SalesPersonPurchaserFieldRef := SourceRecordRef.Field(Customer.FieldNo(Customer."Salesperson Code"));
            Database::Vendor:
                SalesPersonPurchaserFieldRef := SourceRecordRef.Field(Vendor.FieldNo(Vendor."Purchaser Code"));
            Database::Contact:
                SalesPersonPurchaserFieldRef := SourceRecordRef.Field(Contact.FieldNo(Contact."Salesperson Code"));
            Database::"Sales Header":
                SalesPersonPurchaserFieldRef := SourceRecordRef.Field(SalesHeader.FieldNo(SalesHeader."Salesperson Code"));
            else
                exit(CDSUserId);
        end;

        Evaluate(SalesPersonPurchaserCode, Format(SalesPersonPurchaserFieldRef.Value()));
        if not SalesPersonPurchaser.Get(SalesPersonPurchaserCode) then
            exit(CDSUserId);

        if not CRMIntegrationRecord.FindIDFromRecordID(SalesPersonPurchaser.RecordId(), CDSUserId) then
            Error(
              RecordMustBeCoupledExtErr, SalesPersonPurchaser.TableCaption(), SalesPersonPurchaserFieldRef.Value(),
              IntegrationTableMapping.GetExtendedIntegrationTableCaption());

        exit(CDSUserId);
    end;

    procedure ConvertBaseUnitOfMeasureToUomId(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant)
    var
        ItemUnitOfMeasure: Record "Item Unit of Measure";
        ResourceUnitOfMeasure: Record "Resource Unit of Measure";
        CRMIntegrationRecord: Record "CRM Integration Record";
        Item: Record Item;
        Resource: Record Resource;
        PriceListLine: Record "Price List Line";
        SourceRecordCode: Code[20];
    begin
        if Format(SourceFieldRef.Value()) = '' then
            Error(BaseUnitOfMeasureCannotBeEmptyErr, SourceFieldRef.Record().RecordId)
        else begin
            case SourceFieldRef.Record().Number() of
                Database::Item:
                    begin
                        SourceFieldRef.Record().SetTable(Item);
                        SourceRecordCode := Item."No.";
                        if ItemUnitOfMeasure.Get(SourceRecordCode, Format(SourceFieldRef.Value())) then
                            if CRMIntegrationRecord.FindIDFromRecordID(ItemUnitOfMeasure.RecordId, NewValue) then
                                exit
                            else begin
                                CoupleAndSyncItemUoM(DestinationFieldRef, ItemUnitOfMeasure);
                                if CRMIntegrationRecord.FindIDFromRecordID(ItemUnitOfMeasure.RecordId, NewValue) then
                                    exit;
                            end;
                    end;
                Database::Resource:
                    begin
                        SourceFieldRef.Record().SetTable(Resource);
                        SourceRecordCode := Resource."No.";
                        if ResourceUnitOfMeasure.Get(SourceRecordCode, Format(SourceFieldRef.Value())) then
                            if CRMIntegrationRecord.FindIDFromRecordID(ResourceUnitOfMeasure.RecordId, NewValue) then
                                exit
                            else begin
                                CoupleAndSyncResourceUoM(DestinationFieldRef, ResourceUnitOfMeasure);
                                if CRMIntegrationRecord.FindIDFromRecordID(ResourceUnitOfMeasure.RecordId, NewValue) then
                                    exit;
                            end;
                    end;
                Database::"Price List Line":
                    begin
                        SourceFieldRef.Record().SetTable(PriceListLine);
                        case PriceListLine."Asset Type" of
                            PriceListLine."Asset Type"::Item:
                                if ItemUnitOfMeasure.Get(PriceListLine."Asset No.", Format(SourceFieldRef.Value())) then
                                    if CRMIntegrationRecord.FindIDFromRecordID(ItemUnitOfMeasure.RecordId, NewValue) then
                                        exit;
                            PriceListLine."Asset Type"::Resource:
                                if ResourceUnitOfMeasure.Get(PriceListLine."Asset No.", Format(SourceFieldRef.Value())) then
                                    if CRMIntegrationRecord.FindIDFromRecordID(ResourceUnitOfMeasure.RecordId, NewValue) then
                                        exit;
                        end;
                    end;
            end;

            Error(UnitOfMeasureMustBeCoupledErr, SourceFieldRef.Record().Name, SourceRecordCode, SourceFieldRef.Value(), CRMProductName.Short());
        end;
    end;

    local procedure CoupleAndSyncItemUoM(DestinationFieldRef: FieldRef; ItemUnitOfMeasure: Record "Item Unit of Measure")
    var
        CRMProduct: Record "CRM Product";
        IntegrationTableMapping: Record "Integration Table Mapping";
        CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
        UnitOfMeasureRecRef: RecordRef;
    begin
        DestinationFieldRef.Record().SetTable(CRMProduct);
        if IsNullGuid(CRMProduct.ProductId) then begin // CRM product is not created
            IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
            IntegrationTableMapping.SetRange("Table ID", Database::"Unit Group");
            IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uomschedule");
            if IntegrationTableMapping.FindFirst() then
                if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                    IntegrationTableMapping.SetRange("Table ID", Database::"Item Unit of Measure");
                    IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uom");
                    if IntegrationTableMapping.FindFirst() then
                        if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                            ItemUnitOfMeasure.SetRange(SystemId, ItemUnitOfMeasure.SystemId);
                            UnitOfMeasureRecRef.GetTable(ItemUnitOfMeasure);
                            CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(UnitOfMeasureRecRef, false, true);
                        end;
                end;
        end;
    end;

    local procedure CoupleAndSyncResourceUoM(DestinationFieldRef: FieldRef; ResourceUnitOfMeasure: Record "Resource Unit of Measure")
    var
        CRMProduct: Record "CRM Product";
        IntegrationTableMapping: Record "Integration Table Mapping";
        CRMIntegrationTableSynch: Codeunit "CRM Integration Table Synch.";
        UnitOfMeasureRecRef: RecordRef;
    begin
        DestinationFieldRef.Record().SetTable(CRMProduct);
        if IsNullGuid(CRMProduct.ProductId) then begin // CRM product is not created
            IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
            IntegrationTableMapping.SetRange("Table ID", Database::"Unit Group");
            IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uomschedule");
            if IntegrationTableMapping.FindFirst() then
                if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                    IntegrationTableMapping.SetRange("Table ID", Database::"Resource Unit of Measure");
                    IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uom");
                    if IntegrationTableMapping.FindFirst() then
                        if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                            ResourceUnitOfMeasure.SetRange(SystemId, ResourceUnitOfMeasure.SystemId);
                            UnitOfMeasureRecRef.GetTable(ResourceUnitOfMeasure);
                            CRMIntegrationTableSynch.SynchRecordsToIntegrationTable(UnitOfMeasureRecRef, false, true);
                        end;
                end;
        end;
    end;

    procedure ConvertUomIdToBaseUnitOfMeasure(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant)
    var
        CRMIntegrationRecord: Record "CRM Integration Record";
        ItemUnitOfMeasure: Record "Item Unit of Measure";
        ResourceUnitOfMeasure: Record "Resource Unit of Measure";
        IntegrationTableMapping: Record "Integration Table Mapping";
        Item: Record Item;
        Resource: Record Resource;
        CRMProduct: Record "CRM Product";
        UnitOfMeasure: Record "Unit of Measure";
        CRMUom: Record "CRM Uom";
        RecId: RecordId;
        DefaultUoMCode: Code[10];
        CRMId: Guid;
    begin
        CRMID := SourceFieldRef.Value();
        if IsNullGuid(CRMId) then begin
            NewValue := '';
            exit;
        end else begin
            case DestinationFieldRef.Record().Number() of
                Database::Item:
                    if CRMIntegrationRecord.FindRecordIDFromID(CRMId, Database::"Item Unit of Measure", RecId) then begin
                        ItemUnitOfMeasure.Get(RecId);
                        NewValue := ItemUnitOfMeasure.Code;
                        exit;
                    end else begin
                        DestinationFieldRef.Record().SetTable(Item);
                        if IsNullGuid(Item.SystemId) then begin // Item is not created
                            IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
                            IntegrationTableMapping.SetRange("Table ID", Database::"Unit Group");
                            IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uomschedule");
                            if IntegrationTableMapping.FindFirst() then
                                if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                                    IntegrationTableMapping.SetRange("Table ID", Database::"Item Unit of Measure");
                                    IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uom");
                                    if IntegrationTableMapping.FindFirst() then
                                        if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                                            CRMProduct.SetRange(DefaultUoMScheduleId, SourceFieldRef.Record().Field(CRMProduct.FieldNo(DefaultUoMScheduleId)).Value);
                                            if CRMProduct.Count > 1 then
                                                Error(CannotUseSameUnitGroupErr, SourceFieldRef.Value);

                                            CRMUom.Get(CRMID);
                                            Evaluate(DefaultUoMCode, CRMUom.Name);
                                            if not UnitOfMeasure.Get(DefaultUoMCode) then
                                                Error(UnitOfMeasureDoesNotExistErr, CRMProduct.DefaultUoMIdName);
                                            NewValue := UnitOfMeasure.Code;
                                            exit;
                                        end;
                                end;
                        end;
                    end;
                Database::Resource:
                    if CRMIntegrationRecord.FindRecordIDFromID(CRMId, Database::"Resource Unit of Measure", RecId) then begin
                        ResourceUnitOfMeasure.Get(RecId);
                        NewValue := ResourceUnitOfMeasure.Code;
                        exit;
                    end else begin
                        DestinationFieldRef.Record().SetTable(Resource);
                        if IsNullGuid(Resource.SystemId) then begin // Resource is not created
                            IntegrationTableMapping.SetRange(Type, IntegrationTableMapping.Type::Dataverse);
                            IntegrationTableMapping.SetRange("Table ID", Database::"Unit Group");
                            IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uomschedule");
                            if IntegrationTableMapping.FindFirst() then
                                if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                                    IntegrationTableMapping.SetRange("Table ID", Database::"Resource Unit of Measure");
                                    IntegrationTableMapping.SetRange("Integration Table ID", Database::"CRM Uom");
                                    if IntegrationTableMapping.FindFirst() then
                                        if not IntegrationTableMapping."Synch. Only Coupled Records" then begin
                                            CRMProduct.SetRange(DefaultUoMScheduleId, SourceFieldRef.Record().Field(CRMProduct.FieldNo(DefaultUoMScheduleId)).Value);
                                            if CRMProduct.Count > 1 then
                                                Error(CannotUseSameUnitGroupErr, SourceFieldRef.Value);

                                            CRMUom.Get(CRMID);
                                            Evaluate(DefaultUoMCode, CRMUom.Name);
                                            if not UnitOfMeasure.Get(DefaultUoMCode) then
                                                Error(UnitOfMeasureDoesNotExistErr, CRMProduct.DefaultUoMIdName);
                                            NewValue := UnitOfMeasure.Code;
                                            exit;
                                        end;
                                end;
                        end;
                    end;
            end;

            Error(RecordMustBeCoupledErr, SourceFieldRef.Caption(), CRMID, CRMProductName.Short());
        end;
    end;

    procedure PrefixUnitGroupCode(SourceFieldRef: FieldRef; var NewValue: Variant)
    var
        UnitGroup: Record "Unit Group";
    begin
        SourceFieldRef.Record().SetTable(UnitGroup);
        NewValue := UnitGroup.GetCode();
    end;

    local procedure GetFieldRelation(FldRef: FieldRef; var TableName: Text) TableID: Integer
    var
        PriceListLine: Record "Price List Line";
        RecRef: RecordRef;
    begin
        TableID := FldRef.Relation();
        if TableID = 0 then begin
            RecRef := FldRef.Record();
            case RecRef.Number of
                Database::"Price List Line":
                    begin
                        RecRef.SetTable(PriceListLine);
                        TableID := GetFieldRelation(PriceListLine, FldRef.Number);
                        RecRef.Close();
                    end;
            end;
            OnAfterGetFieldRelation(RecRef, FldRef, TableID);
            RecRef.Close();
        end;
        if TableID <> 0 then begin
            RecRef.Open(TableID);
            TableName := RecRef.Name;
            RecRef.Close();
        end;
    end;

    local procedure GetFieldRelation(PriceListLine: Record "Price List Line"; FieldId: Integer) TableID: Integer;
    var
        PriceAsset: Record "Price Asset";
    begin
        case FieldId of
            PriceListLine.FieldNo("Asset No."):
                if PriceListLine."Asset No." <> '' then begin
                    PriceAsset.Validate("Asset Type", PriceListLine."Asset Type");
                    PriceAsset.Validate("Asset No.", PriceListLine."Asset No.");
                    TableID := PriceAsset."Table Id";
                end;
            PriceListLine.FieldNo("Unit of Measure Code"):
                TableID := Database::"Unit of Measure";
        end;
    end;

    procedure ConvertTableToOption(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var OptionValue: Integer) TableIsMapped: Boolean
    var
        CRMOptionMapping: Record "CRM Option Mapping";
        RecID: RecordID;
    begin
        TableIsMapped := false;
        OptionValue := 0;
        if IsTableMappedToCRMOption(SourceFieldRef, DestinationFieldRef) then begin
            TableIsMapped := true;
            if FindRecordIDByPK(SourceFieldRef.Relation(), SourceFieldRef.Value(), RecID) then begin
                CRMOptionMapping.SetRange("Record ID", RecID);
                if CRMOptionMapping.FindFirst() then
                    OptionValue := CRMOptionMapping."Option Value"
                else
                    Error(RecordMustBeCoupledErr, SourceFieldRef.Caption(), SourceFieldRef.Value(), CRMProductName.CDSServiceName());
            end;
        end;
        exit(TableIsMapped);
    end;

    procedure ConvertOptionToTable(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var TableValue: Text) TableIsMapped: Boolean
    var
        CRMOptionMapping: Record "CRM Option Mapping";
        CRMAccount: Record "CRM Account";
        CRMSalesorder: Record "CRM Salesorder";
        PrimaryKey: Variant;
        IsHandled: Boolean;
    begin
        TableIsMapped := false;
        if IsTableMappedToCRMOption(DestinationFieldRef, SourceFieldRef) then begin
            TableIsMapped := true;

            if Format(SourceFieldRef.Value()) = ' ' then
                exit;

            CRMOptionMapping.SetRange("Option Value Caption", SourceFieldRef.Value());
            CRMOptionMapping.SetRange("Table ID", DestinationFieldRef.Relation());

            IsHandled := false;
            OnConvertOptionToTableOnBeforeSetRangeForIntegrationFieldID(CRMOptionMapping, SourceFieldRef, IsHandled);
            if not IsHandled then
                if SourceFieldRef.Record().Number = Database::"CRM Salesorder" then
                    case SourceFieldRef.Number of
                        CRMsalesorder.FieldNo(PaymentTermsCodeEnum):
                            CRMOptionMapping.SetRange("Integration Field ID", CRMAccount.FieldNo(PaymentTermsCodeEnum));
                        CRMsalesorder.FieldNo(ShippingMethodCodeEnum):
                            CRMOptionMapping.SetRange("Integration Field ID", CRMAccount.FieldNo(Address1_ShippingMethodCodeEnum));
                        CRMsalesorder.FieldNo(FreightTermsCodeEnum):
                            CRMOptionMapping.SetRange("Integration Field ID", CRMAccount.FieldNo(Address1_FreightTermsCodeEnum));
                    end
                else
                    CRMOptionMapping.SetRange("Integration Field ID", SourceFieldRef.Number());

            if CRMOptionMapping.FindFirst() then begin
                FindPKByRecordID(CRMOptionMapping."Record ID", PrimaryKey);
                Evaluate(TableValue, PrimaryKey);
            end else begin
                CRMOptionMapping.SetRange("Option Value Caption");
                CRMOptionMapping.SetRange("Option Value", SourceFieldRef.Value());
                if CRMOptionMapping.FindFirst() then begin
                    FindPKByRecordID(CRMOptionMapping."Record ID", PrimaryKey);
                    Evaluate(TableValue, PrimaryKey);
                end else
                    Error(RecordMustBeCoupledErr, SourceFieldRef.Caption(), SourceFieldRef.Value(), CRMProductName.CDSServiceName());
            end;
        end;
        exit(TableIsMapped);
    end;

    procedure UpdateCDSOptionMapping(OldRecId: RecordId; NewRecId: RecordId)
    var
        CRMOptionMapping: Record "CRM Option Mapping";
    begin
        CRMOptionMapping.SetRange("Record ID", OldRecId);
        if CRMOptionMapping.FindFirst() then
            CRMOptionMapping.Rename(NewRecId);
    end;

    internal procedure FindNewValueForSpecialMapping(SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef; var NewValue: Variant) IsValueFound: Boolean
    var
        TempOpportunity: Record Opportunity temporary;
        TempCRMOpportunity: Record "CRM Opportunity" temporary;
    begin
        if SourceFieldRef.Number() = TempOpportunity.FieldNo("Contact Company No.") then
            if DestinationFieldRef.Number() = TempCRMOpportunity.FieldNo(ParentAccountId) then
                if SourceFieldRef.Record().Number() = Database::Opportunity then
                    if DestinationFieldRef.Record().Number() = Database::"CRM Opportunity" then begin
                        NewValue := FindParentCRMAccountForOpportunity(SourceFieldRef.Record());
                        IsValueFound := true;
                        exit;
                    end;
    end;

    local procedure FindParentCRMAccountForOpportunity(SourceRecordRef: RecordRef) AccountId: Guid
    var
        ContactBusinessRelation: Record "Contact Business Relation";
        Customer: Record Customer;
        CRMIntegrationRecord: Record "CRM Integration Record";
    begin
        if not FindOpportunityRelatedCustomer(SourceRecordRef, ContactBusinessRelation) then
            exit;

        if not Customer.Get(ContactBusinessRelation."No.") then
            Error(RecordNotFoundErr, Customer.TableCaption(), ContactBusinessRelation."No.");

        CRMIntegrationRecord.FindIDFromRecordID(Customer.RecordId(), AccountId);
    end;

    local procedure IsTableMappedToCRMOption(NAVFieldRef: FieldRef; CRMFieldRef: FieldRef): Boolean
    var
        IntegrationTableMapping: Record "Integration Table Mapping";
    begin
        if CRMFieldRef.Type() = CRMFieldRef.Type::Option then begin
            IntegrationTableMapping.SetRange("Table ID", NAVFieldRef.Relation());
            IntegrationTableMapping.SetRange("Delete After Synchronization", false);
            exit(not IntegrationTableMapping.IsEmpty());
        end;
        exit(false);
    end;

    internal procedure IsContactBusinessRelationOptional(): Boolean
    var
        Optional: Boolean;
    begin
        OnGetIsContactBusinessRelationOptional(Optional);
        if Optional then
            Session.LogMessage('0000F1J', ContactBusinessRelationOptionalTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
        exit(Optional);
    end;

    internal procedure IsContactTypeCheckIgnored(): Boolean
    var
        Ignored: Boolean;
    begin
        OnGetIsContactTypeCheckIgnored(Ignored);
        if Ignored then
            Session.LogMessage('0000F2F', ContactTypeCheckIgnoredTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok);
        exit(Ignored);
    end;

    procedure TransformValue(SourceRecordRef: RecordRef; DestinationRecordRef: RecordRef; TransformationRule: Record "Transformation Rule"; SourceFieldNo: Integer; DestinationFieldNo: Integer)
    var
        SourceFieldRef: FieldRef;
        DestinationFieldRef: FieldRef;
        TransformedValue: Text;
    begin
        SourceFieldRef := SourceRecordRef.Field(SourceFieldNo);
        DestinationFieldRef := DestinationRecordRef.Field(DestinationFieldNo);
        TransformedValue := TransformationRule.TransformText(SourceFieldRef.Value());

        case DestinationFieldRef.Type() of
            FieldType::Date, FieldType::DateTime:
                SetDateField(TransformedValue, SourceFieldRef, DestinationFieldRef);
            else
                SourceFieldRef.Value := TransformedValue;
        end;
    end;

    local procedure SetDateField(ValueText: Text; var SourceFieldRef: FieldRef; DestinationFieldRef: FieldRef)
    var
        TypeHelper: Codeunit "Type Helper";
        Value: Variant;
    begin
        Value := DestinationFieldRef.Value();

        if not TypeHelper.Evaluate(Value, ValueText, '', '')
        then
            Error(IncorrectFormatOrTypeErr);

        SourceFieldRef.Value := Value;
    end;

    internal procedure FulfillSalesOrder(SalesOrderId: Guid)
    var
        CDSIntegrationImpl: Codeunit "CDS Integration Impl.";
        CrmHelper: Dotnet CrmHelper;
    begin
        OnBeforeFulfillSalesOrder(SalesOrderId);
        if IsNullGuid(SalesOrderId) then
            exit;

        if CDSIntegrationImpl.InitializeConnection(CrmHelper) then
            CrmHelper.FulfillSalesOrder(SalesOrderId);
    end;

    [IntegrationEvent(false, false)]
    local procedure OnAfterGetFieldRelation(RecRef: RecordRef; FldRef: FieldRef; var TableID: Integer)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetCDSBaseCurrencyId(var BaseCurrencyId: Guid; var handled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    procedure OnGetCDSOwnershipModel(var OwnershipModel: Option; var handled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetCDSBaseCurrencySymbol(var BaseCurrencySymbol: Text[5]; var handled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetCDSBaseCurrencyPrecision(var BaseCurrencyPrecision: Integer; var handled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetCDSCurrencyDecimalPrecision(var CurrencyDecimalPrecision: Integer; var handled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetVendorSyncEnabled(var Enabled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetIsContactBusinessRelationOptional(var Optional: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnGetIsContactTypeCheckIgnored(var Ignored: Boolean)
    begin
    end;

#pragma warning disable AS0077
    [IntegrationEvent(false, false)]
    local procedure OnFindAndSynchRecordIDFromIntegrationSystemId(IntegrationSystemId: Guid; TableId: Integer; var LocalRecordID: RecordID; var IsHandled: Boolean)
    begin
    end;
#pragma warning restore AS0077

    [IntegrationEvent(false, false)]
    local procedure OnUpdateCRMInvoiceStatusFromEntryOnBeforeModify(var CRMInvoice: Record "CRM Invoice"; var NewCRMInvoice: Record "CRM Invoice"; CustLedgerEntry: Record "Cust. Ledger Entry")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnUpdateCRMInvoiceStatusFromEntryOnBeforeCheckFieldsChanged(var CRMInvoice: Record "CRM Invoice"; var NewCRMInvoice: Record "CRM Invoice"; CustLedgerEntry: Record "Cust. Ledger Entry"; var ChangeNeeded: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeCalculateActualStatusCode(CustLedgerEntry: Record "Cust. Ledger Entry"; var CRMInvoice: Record "CRM Invoice"; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnConvertOptionToTableOnBeforeSetRangeForIntegrationFieldID(var CRMOptionMapping: Record "CRM Option Mapping"; SourceFieldRef: FieldRef; var IsHandled: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnCancelCRMInvoiceOnBeforeModifyCRMInvoice(var CRMInvoice: Record "CRM Invoice"; var NewCRMInvoice: Record "CRM Invoice")
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnCancelCRMInvoiceOnBeforeCheckFieldsChanged(var CRMInvoice: Record "CRM Invoice"; var NewCRMInvoice: Record "CRM Invoice"; var ChangeNeeded: Boolean)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnBeforeFulfillSalesOrder(var SalesOrderId: Guid)
    begin
    end;

    [IntegrationEvent(false, false)]
    local procedure OnFailedFindContactByAccountId(DestinationContactRecordRef: RecordRef; var CompanyContact: Record Contact; AccountID: Guid; var OutOfMapFilter: Boolean; var Result: Boolean)
    begin
    end;
}