Codeunit 1565 Privacy Notice Impl.

App
System Application
Namespace
System.Privacy
Versions
20-28

Procedures, 15

Versions171819202122232425262728

Source242526272829

Source in 29

src/System Application/App/Privacy Notice/src/PrivacyNoticeImpl.Codeunit.al395 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 System.Privacy;

using System.Environment;

codeunit 1565 "Privacy Notice Impl."
{
    Access = Internal;
    InherentEntitlements = X;
    InherentPermissions = X;
    Permissions = tabledata Company = r,
                  tabledata "Privacy Notice" = rim,
                  tabledata "Privacy Notice Approval" = r;

    var
        EmptyGuid: Guid;
        PrivacyAgreementTxt: Label 'By using %1, you consent to your data being shared with Microsoft services that might be outside of your organization''s selected geographic boundaries and might have different compliance and security standards than %2. Your privacy is important to us, and you can choose whether to share data with the service. To learn more, follow the link below.', Comment = '%1 = the integration service name, ex. Microsoft Sharepoint, %2 = the full marketing name, such as Microsoft Dynamics 365 Business Central.';
        MicrosoftPrivacyLinkTxt: Label 'https://go.microsoft.com/fwlink/?linkid=521839';
        AdminDisabledIntegrationMsg: Label 'Your admin has disabled the integration with %1, please contact your administrator to approve this integration.', Comment = '%1 = a service name such as Microsoft Teams';
        MissingLinkErr: Label 'No privacy notice link was specified';
        PrivacyNoticeDoesNotExistErr: Label 'The privacy notice %1 does not exist.', Comment = '%1 = Identifier of a privacy notice';
        TelemetryCategoryTxt: Label 'Privacy Notice', Locked = true;
        CreatePrivacyNoticeTelemetryTxt: Label 'Creating privacy notice %1', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        ConfirmPrivacyNoticeTelemetryTxt: Label 'Confirming privacy notice %1', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        PrivacyNoticeAutoApprovedByAdminTelemetryTxt: Label 'The privacy notice %1 was auto-approved by the admin', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        PrivacyNoticeAutoRejectedByAdminTelemetryTxt: Label 'The privacy notice %1 was auto-rejected by the admin', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        PrivacyNoticeAutoApprovedByUserTelemetryTxt: Label 'The privacy notice %1 was auto-approved by the user', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        ShowingPrivacyNoticeTelemetryTxt: Label 'Showing privacy notice %1', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        PrivacyNoticeApprovalResultTelemetryTxt: Label 'Approval State after showing privacy notice %1: %2', Comment = '%1 = Identifier of a privacy notice, %2 = Approval state of a privacy notice', Locked = true;
        CheckPrivacyNoticeApprovalStateTelemetryTxt: Label 'Checking privacy approval state for privacy notice %1', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        AdminPrivacyApprovalStateTelemetryTxt: Label 'Admin privacy approval state for privacy notice %1: %2', Comment = '%1 = Identifier of a privacy notice, %2 = Approval state of a privacy notice', Locked = true;
        UserPrivacyApprovalStateTelemetryTxt: Label 'User privacy approval state for privacy notice %1: %2', Comment = '%1 = Identifier of a privacy notice, %2 = Approval state of a privacy notice', Locked = true;
        RegisteringPrivacyNoticesFailedTelemetryErr: Label 'Privacy notices could not be registered', Locked = true;
        PrivacyNoticeNotCreatedTelemetryErr: Label 'A privacy notice %1 could not be created', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        PrivacyNoticeDoesNotExistTelemetryTxt: Label 'The Privacy Notice %1 does not exist.', Comment = '%1 = Identifier of a privacy notice', Locked = true;
        SystemEventPrivacyNoticeNotCreatedTelemetryErr: Label 'System event privacy notice %1 could not be created.', Comment = '%1 = Identifier of a privacy notice', Locked = true;

    trigger OnRun()
    begin
        CreateDefaultPrivacyNotices();
    end;

    procedure CreatePrivacyNotice(Id: Code[50]; IntegrationName: Text[250]; Link: Text[2048]): Boolean
    var
        PrivacyNotice: Record "Privacy Notice";
    begin
        exit(CreatePrivacyNotice(PrivacyNotice, Id, IntegrationName, Link));
    end;

    procedure CreatePrivacyNotice(Id: Code[50]; IntegrationName: Text[250]): Boolean
    begin
        exit(CreatePrivacyNotice(Id, IntegrationName, MicrosoftPrivacyLinkTxt));
    end;

    procedure GetDefaultPrivacyAgreementTxt(): Text
    begin
        exit(PrivacyAgreementTxt);
    end;

    procedure ConfirmPrivacyNoticeApproval(PrivacyNoticeId: Code[50]): Boolean
    begin
        exit(ConfirmPrivacyNoticeApproval(PrivacyNoticeId, true));
    end;

    procedure ConfirmPrivacyNoticeApproval(PrivacyNoticeId: Code[50]; SkipCheckInEval: Boolean): Boolean
    var
        Company: Record Company;
        PrivacyNotice: Record "Privacy Notice";
    begin
        Session.LogMessage('0000GK8', StrSubstNo(ConfirmPrivacyNoticeTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);

        PrivacyNotice.SetAutoCalcFields(Enabled, Disabled);
        PrivacyNotice.SetRange("User SID Filter", EmptyGuid);

        // If the Privacy Notice does not exist then re-initialize all Privacy Notices
        if not PrivacyNotice.Get(PrivacyNoticeId) then begin
            CreateDefaultPrivacyNoticesInSeparateThread();
            if not PrivacyNotice.Get(PrivacyNoticeId) then
                Error(PrivacyNoticeDoesNotExistErr, PrivacyNoticeId);
        end;

        // First check if admin has made decision on this privacy notice and return that
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GK9', StrSubstNo(PrivacyNoticeAutoApprovedByAdminTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit(true);
        end;
        if PrivacyNotice.Disabled then begin
            Session.LogMessage('0000GKA', StrSubstNo(PrivacyNoticeAutoRejectedByAdminTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            if CanCurrentUserApproveForOrganization() then
                exit(ShowPrivacyNotice(PrivacyNotice)); // User is admin so show the privacy notice again for them to re-approve
            Message(AdminDisabledIntegrationMsg, PrivacyNotice."Integration Service Name");
            exit(false);
        end;

        // Admin did not make any decision
        if SkipCheckInEval and Company.Get(CompanyName()) and Company."Evaluation Company" then
            exit(true); // Auto-agree for evaluation companies if admin has not explicitly disagreed

        // Check if user made a decision and if so, return that
        PrivacyNotice.SetRange("User SID Filter", UserSecurityId());
        PrivacyNotice.CalcFields(Enabled, Disabled);
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GKB', StrSubstNo(PrivacyNoticeAutoApprovedByUserTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit(true); // If user clicked no, they will still be notified until admin makes a decision
        end;

        // Show privacy notice and store user decision
        // if the user is admin then show an approval message for everyone
        // if the user is not admin then show an approval message for this specific user
        exit(ShowPrivacyNotice(PrivacyNotice));
    end;

    procedure CheckPrivacyNoticeApprovalState(PrivacyNoticeId: Code[50]): Enum "Privacy Notice Approval State"
    begin
        exit(CheckPrivacyNoticeApprovalState(PrivacyNoticeId, true));
    end;

    procedure CheckPrivacyNoticeApprovalState(PrivacyNoticeId: Code[50]; SkipCheckInEval: Boolean): Enum "Privacy Notice Approval State"
    var
        Company: Record Company;
        PrivacyNotice: Record "Privacy Notice";
    begin
        Session.LogMessage('0000GKC', StrSubstNo(CheckPrivacyNoticeApprovalStateTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);

        PrivacyNotice.SetAutoCalcFields(Enabled, Disabled);
        PrivacyNotice.SetRange("User SID Filter", EmptyGuid);

        if not PrivacyNotice.Get(PrivacyNoticeId) then begin
            Session.LogMessage('0000GN7', StrSubstNo(PrivacyNoticeDoesNotExistTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            if ShouldApproveByDefault(PrivacyNoticeId) or (SkipCheckInEval and Company.Get(CompanyName()) and Company."Evaluation Company") then
                exit("Privacy Notice Approval State"::Agreed); // Auto-agree for evaluation companies if admin has not explicitly disagreed or approve by default
            exit("Privacy Notice Approval State"::"Not set"); // If there are no Privacy Notice then it is by default "Not set".
        end;

        // First check if admin has made decision on this privacy notice and return that
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GKD', StrSubstNo(AdminPrivacyApprovalStateTelemetryTxt, PrivacyNoticeId, "Privacy Notice Approval State"::Agreed), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit("Privacy Notice Approval State"::Agreed);
        end;
        if PrivacyNotice.Disabled then begin
            Session.LogMessage('0000GKE', StrSubstNo(AdminPrivacyApprovalStateTelemetryTxt, PrivacyNoticeId, "Privacy Notice Approval State"::Disagreed), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit("Privacy Notice Approval State"::Disagreed);
        end;

        // Admin did not make any decision
        if SkipCheckInEval and Company.Get(CompanyName()) and Company."Evaluation Company" then
            exit("Privacy Notice Approval State"::Agreed); // Auto-agree for evaluation companies if admin has not explicitly disagreed

        // Check if user made a decision and if so, return that
        PrivacyNotice.SetRange("User SID Filter", UserSecurityId());
        PrivacyNotice.CalcFields(Enabled);
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GKF', StrSubstNo(UserPrivacyApprovalStateTelemetryTxt, "Privacy Notice Approval State"::Agreed, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit("Privacy Notice Approval State"::Agreed); // If user clicked no, they will still be notified until admin makes a decision
        end;
        Session.LogMessage('0000GKG', StrSubstNo(UserPrivacyApprovalStateTelemetryTxt, "Privacy Notice Approval State"::"Not set", PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        exit("Privacy Notice Approval State"::"Not set");
    end;

    procedure CanCurrentUserApproveForOrganization(): Boolean
    var
        [SecurityFiltering(SecurityFilter::Ignored)]
        PrivacyNoticeApproval: Record "Privacy Notice Approval";
    begin
        exit(PrivacyNoticeApproval.WritePermission());
    end;

    procedure SetApprovalState(PrivacyNoticeId: Code[50]; PrivacyNoticeApprovalState: Enum "Privacy Notice Approval State")
    var
        PrivacyNoticeApproval: Codeunit "Privacy Notice Approval";
    begin
        CreateDefaultPrivacyNotices(); // Ensure all default Privacy Notices are created
        if CanCurrentUserApproveForOrganization() then
            PrivacyNoticeApproval.SetApprovalState(PrivacyNoticeId, EmptyGuid, PrivacyNoticeApprovalState)
        else
            if not IsApprovalStateDisagreed(PrivacyNoticeApprovalState) then // We do not store rejected user approvals
                PrivacyNoticeApproval.SetApprovalState(PrivacyNoticeId, UserSecurityId(), PrivacyNoticeApprovalState)
    end;

    procedure ShowOneTimePrivacyNotice(IntegrationName: Text[250]): Enum "Privacy Notice Approval State"
    begin
        exit(ShowOneTimePrivacyNotice(IntegrationName, MicrosoftPrivacyLinkTxt));
    end;

    procedure ShowOneTimePrivacyNotice(IntegrationName: Text[250]; Link: Text[2048]): Enum "Privacy Notice Approval State"
    var
        TempPrivacyNotice: Record "Privacy Notice" temporary;
        PrivacyNoticePage: Page "Privacy Notice";
    begin
        CreatePrivacyNotice(TempPrivacyNotice, '', IntegrationName, Link);

        PrivacyNoticePage.SetRecord(TempPrivacyNotice);
        PrivacyNoticePage.RunModal();
        PrivacyNoticePage.GetRecord(TempPrivacyNotice);
        exit(PrivacyNoticePage.GetUserApprovalState());
    end;

    procedure CreateDefaultPrivacyNoticesInSeparateThread()
    begin
        if Codeunit.Run(Codeunit::"Privacy Notice Impl.") then;
    end;

    procedure CreateDefaultPrivacyNotices()
    var
        TempPrivacyNotice: Record "Privacy Notice" temporary;
        PrivacyNotice: Record "Privacy Notice";
    begin
        if not TryGetAllPrivacyNotices(TempPrivacyNotice) then begin
            Session.LogMessage('0000GME', RegisteringPrivacyNoticesFailedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit;
        end;

        TempPrivacyNotice.Reset();
        if TempPrivacyNotice.FindSet() then
            repeat
                PrivacyNotice.SetRange(ID, TempPrivacyNotice.ID);
                if PrivacyNotice.IsEmpty() then begin
                    PrivacyNotice := TempPrivacyNotice;
                    if PrivacyNotice.Link = '' then
                        PrivacyNotice.Link := MicrosoftPrivacyLinkTxt;
                    if not PrivacyNotice.Insert() then
                        Session.LogMessage('0000GMF', StrSubstNo(PrivacyNoticeNotCreatedTelemetryErr, TempPrivacyNotice.ID), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.TelemetryCategoryTxt)
                    else
                        TryCreateDefaultApproval(PrivacyNotice);
                end;
            until TempPrivacyNotice.Next() = 0;
    end;

    /// <summary>
    /// Creates a default approval for the given privacy notice if it can be approved by default and there is not already an approval record for it.
    /// </summary>
    /// <param name="PrivacyNotice">The notice to save approval under.</param>
    local procedure TryCreateDefaultApproval(PrivacyNotice: Record "Privacy Notice")
    var
        PrivacyNoticeApproval: Codeunit "Privacy Notice Approval";
    begin
        if ShouldApproveByDefault(PrivacyNotice.ID) then begin
            PrivacyNoticeApproval.SetApprovalState(PrivacyNotice.ID, EmptyGuid, "Privacy Notice Approval State"::Agreed);
            PrivacyNotice.CalcFields(Enabled);
        end;
    end;

    [TryFunction]
    local procedure TryGetAllPrivacyNotices(var PrivacyNotice: Record "Privacy Notice" temporary)
    var
        PrivacyNoticeInterface: Codeunit "Privacy Notice";
    begin
        PrivacyNoticeInterface.OnRegisterPrivacyNotices(PrivacyNotice);
    end;

    local procedure CreatePrivacyNotice(var PrivacyNotice: Record "Privacy Notice"; Id: Code[50]; IntegrationName: Text[250]; Link: Text[2048]): Boolean
    begin
        Session.LogMessage('0000GK7', StrSubstNo(CreatePrivacyNoticeTelemetryTxt, Id), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);

        if Link = '' then
            Error(MissingLinkErr);

        PrivacyNotice.Id := Id;
        PrivacyNotice."Integration Service Name" := IntegrationName;
        PrivacyNotice.Link := Link;

        if PrivacyNotice.Insert() then begin
            TryCreateDefaultApproval(PrivacyNotice);

            exit(true);
        end;

        exit(false);
    end;

    internal procedure ShowPrivacyNotice(PrivacyNotice: Record "Privacy Notice"): Boolean
    var
        PrivacyNoticeCodeunit: Codeunit "Privacy Notice";
        PrivacyNoticePage: Page "Privacy Notice";
        Handled: Boolean;
    begin
        Session.LogMessage('0000GKH', StrSubstNo(ShowingPrivacyNoticeTelemetryTxt, PrivacyNotice.Id), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        // Allow overriding of the privacy notice
        PrivacyNoticeCodeunit.OnBeforeShowPrivacyNotice(PrivacyNotice, Handled);
        if Handled then begin
            PrivacyNotice.CalcFields(Enabled); // Refresh the enabled field from the database
            exit(PrivacyNotice.Enabled); // The user either accepted, rejected or cancelled the privacy notice. No matter the case we only return true if the privacy notice was accepted.
        end;

        PrivacyNoticePage.SetRecord(PrivacyNotice);
        PrivacyNoticePage.RunModal();
        PrivacyNoticePage.GetRecord(PrivacyNotice);
        Session.LogMessage('0000GKI', StrSubstNo(PrivacyNoticeApprovalResultTelemetryTxt, PrivacyNotice.ID, PrivacyNoticePage.GetUserApprovalState()), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        exit(PrivacyNoticePage.GetUserApprovalState() = "Privacy Notice Approval State"::Agreed); // The user either accepted, rejected or cancelled the privacy notice. No matter the case we only return true if the privacy notice was accepted.
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", ConfirmPrivacyNoticeApproval, '', false, false)]
    local procedure ConfirmSystemPrivacyNoticeApproval(PrivacyNoticeIntegrationName: Text; var IsApproved: Boolean)
    var
        PrivacyNotice: Record "Privacy Notice";
        PrivacyNoticeId: Code[50];
        PrivacyNoticeName: Text[250];
    begin
        if IsApproved then
            exit;

        PrivacyNoticeId := CopyStr(PrivacyNoticeIntegrationName, 1, 50);
        PrivacyNoticeName := CopyStr(PrivacyNoticeIntegrationName, 1, 250);
        PrivacyNotice.SetRange(ID, PrivacyNoticeId);
        if not PrivacyNotice.IsEmpty() then begin
            IsApproved := ConfirmPrivacyNoticeApproval(PrivacyNoticeId);
            exit;
        end;

        CreateDefaultPrivacyNoticesInSeparateThread(); // First attempt creating the system privacy notice by creating default privacy notices
        if not PrivacyNotice.IsEmpty() then begin
            IsApproved := ConfirmPrivacyNoticeApproval(PrivacyNoticeId);
            exit;
        end;

        if CreatePrivacyNotice(PrivacyNoticeId, PrivacyNoticeName) then begin // Manually create the privacy notice.
            Commit(); // Below may show a privacy notice, so make sure we are not in a write transaction.
            IsApproved := ConfirmPrivacyNoticeApproval(PrivacyNoticeId);
            exit;
        end;

        Session.LogMessage('0000GP9', StrSubstNo(SystemEventPrivacyNoticeNotCreatedTelemetryErr, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        IsApproved := false;
    end;

    /// <summary>
    /// Checks if the IDs are equal.
    /// </summary>
    /// <param name="ID">The first ID.</param>
    /// <param name="IDToCheck">The ID to check against the first ID parameter.</param>
    /// <returns>true if equal; otherwise false.</returns>
    local procedure CheckIntegrationIDEquality(ID: Text; IDToCheck: Text): Boolean
    begin
        exit(CopyStr(UpperCase(ID), 1, 50) = CopyStr(UpperCase(IDToCheck), 1, 50));
    end;

    /// <summary>
    /// Indicates if the integration should be enabled by default.
    /// </summary>
    /// <param name="IntegrationID">The integration ID/</param>
    /// <returns>true if it should be approved by default; otherwise false.</returns>
    local procedure ShouldApproveByDefault(IntegrationID: Text): Boolean
    var
        SystemPrivacyNoticeReg: Codeunit "System Privacy Notice Reg.";
        MicrosoftLearnServiceInGeo: Boolean;
        MicrosoftCopilotShouldApprove: Boolean;
    begin
        if CheckIntegrationIDEquality(SystemPrivacyNoticeReg.GetMicrosoftLearnID(), IntegrationID) then
            if (SystemPrivacyNoticeReg.TryGetMicrosoftLearnInGeoSupport(MicrosoftLearnServiceInGeo)) then
                exit(MicrosoftLearnServiceInGeo);

        if CheckIntegrationIDEquality(SystemPrivacyNoticeReg.GetMicrosoftCopilotID(), IntegrationID) then
            if (SystemPrivacyNoticeReg.TryGetMicrosoftCopilotDefaultApproval(MicrosoftCopilotShouldApprove)) then
                exit(MicrosoftCopilotShouldApprove);

        exit(false);
    end;

    /// <summary>
    /// Determines whether the admin or user has disagreed with the Privacy Notice.
    /// </summary>
    /// <param name="Id">Identification of an existing privacy notice.</param>
    /// <returns>Whether the Privacy Notice was disagreed to.</returns>
    procedure IsApprovalStateDisagreed(Id: Code[50]): Boolean
    var
        State: Enum "Privacy Notice Approval State";
    begin
        State := CheckPrivacyNoticeApprovalState(Id);
        exit(IsApprovalStateDisagreed(State));
    end;

    /// <summary>
    /// Determines whether the admin or user has disagreed with the Privacy Notice.
    /// </summary>
    /// <param name="State">The approval state.</param>
    /// <returns>Whether the Privacy Notice was disagreed to.</returns>
    procedure IsApprovalStateDisagreed(State: Enum "Privacy Notice Approval State"): Boolean
    begin
        exit(State = "Privacy Notice Approval State"::Disagreed);
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", GetPrivacyNoticeApprovalState, '', true, true)]
    local procedure GetPrivacyNoticeApprovalState(PrivacyNoticeIntegrationName: Text; var PrivacyNoticeApprovalState: Integer)
    var
        PrivacyNoticeId: Code[50];
    begin
        PrivacyNoticeId := CopyStr(PrivacyNoticeIntegrationName, 1, 50);
        PrivacyNoticeApprovalState := CheckPrivacyNoticeApprovalState(PrivacyNoticeId).AsInteger();
    end;
}