diff --git a/src/System Application/App/Privacy Notice/README.md b/src/System Application/App/Privacy Notice/README.md index 47fd478cdd..fc5dc6954b 100644 --- a/src/System Application/App/Privacy Notice/README.md +++ b/src/System Application/App/Privacy Notice/README.md @@ -91,3 +91,10 @@ begin end; ``` +### How to have a privacy notice approved by default +In `PrivacyNoticeImpl` codeunit, there is a procedure called `ShouldApproveByDefault`. Return true from this method if the integration ID matches +the ID of the scenario you want to approve by default. In the following scenarios where the procedure returns true, it will have a default approval for the entire organization if: +1. `PrivacyNotice.GetPrivacyNoticeApprovalState` is called for an integration with no privacy notice record created or, +2. A privacy notice for the scenario is created and no approvals exist for it yet. + +An admin can later agree/disagree to this approval on the Privacy Notices page. \ No newline at end of file diff --git a/src/System Application/App/Privacy Notice/src/PrivacyNotice.Codeunit.al b/src/System Application/App/Privacy Notice/src/PrivacyNotice.Codeunit.al index d9246e8ae3..89cb758049 100644 --- a/src/System Application/App/Privacy Notice/src/PrivacyNotice.Codeunit.al +++ b/src/System Application/App/Privacy Notice/src/PrivacyNotice.Codeunit.al @@ -166,7 +166,19 @@ codeunit 1563 "Privacy Notice" var PrivacyNoticeImpl: Codeunit "Privacy Notice Impl."; begin - exit(PrivacyNoticeImpl.CheckPrivacyNoticeApprovalState(Id) = "Privacy Notice Approval State"::Disagreed); + exit(PrivacyNoticeImpl.IsApprovalStateDisagreed(Id)); + end; + + /// + /// Determines whether the admin or user has disagreed with the Privacy Notice. + /// + /// The approval state. + /// Whether the Privacy Notice was disagreed to. + procedure IsApprovalStateDisagreed(State: Enum "Privacy Notice Approval State"): Boolean + var + PrivacyNoticeImpl: Codeunit "Privacy Notice Impl."; + begin + exit(PrivacyNoticeImpl.IsApprovalStateDisagreed(State)); end; /// diff --git a/src/System Application/App/Privacy Notice/src/PrivacyNoticeImpl.Codeunit.al b/src/System Application/App/Privacy Notice/src/PrivacyNoticeImpl.Codeunit.al index 5f1dcb11c8..a825e98bac 100644 --- a/src/System Application/App/Privacy Notice/src/PrivacyNoticeImpl.Codeunit.al +++ b/src/System Application/App/Privacy Notice/src/PrivacyNoticeImpl.Codeunit.al @@ -130,8 +130,8 @@ codeunit 1565 "Privacy Notice Impl." if not PrivacyNotice.Get(PrivacyNoticeId) then begin Session.LogMessage('0000GN7', StrSubstNo(PrivacyNoticeDoesNotExistTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt); - 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 + 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; @@ -176,8 +176,8 @@ codeunit 1565 "Privacy Notice Impl." if CanCurrentUserApproveForOrganization() then PrivacyNoticeApproval.SetApprovalState(PrivacyNoticeId, EmptyGuid, PrivacyNoticeApprovalState) else - if PrivacyNoticeApprovalState <> "Privacy Notice Approval State"::Disagreed then // We do not store rejected user approvals - PrivacyNoticeApproval.SetApprovalState(PrivacyNoticeId, UserSecurityId(), PrivacyNoticeApprovalState); + 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" @@ -222,11 +222,27 @@ codeunit 1565 "Privacy Notice Impl." if PrivacyNotice.Link = '' then PrivacyNotice.Link := MicrosoftPrivacyLinkTxt; if not PrivacyNotice.Insert() then - Session.LogMessage('0000GMF', PrivacyNoticeNotCreatedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt); + Session.LogMessage('0000GMF', PrivacyNoticeNotCreatedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.TelemetryCategoryTxt) + else + TryCreateDefaultApproval(PrivacyNotice); end; until TempPrivacyNotice.Next() = 0; end; + /// + /// 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. + /// + /// The notice to save approval under. + 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 @@ -245,7 +261,14 @@ codeunit 1565 "Privacy Notice Impl." PrivacyNotice.Id := Id; PrivacyNotice."Integration Service Name" := IntegrationName; PrivacyNotice.Link := Link; - exit(PrivacyNotice.Insert()); + + if PrivacyNotice.Insert() then begin + TryCreateDefaultApproval(PrivacyNotice); + + exit(true); + end; + + exit(false); end; local procedure ShowPrivacyNotice(PrivacyNotice: Record "Privacy Notice"): Boolean @@ -303,6 +326,55 @@ codeunit 1565 "Privacy Notice Impl." IsApproved := false; end; + /// + /// Checks if the IDs are equal. + /// + /// The first ID. + /// The ID to check against the first ID parameter. + /// true if equal; otherwise false. + local procedure CheckIntegrationIDEquality(ID: Text; IDToCheck: Text): Boolean + begin + exit(CopyStr(UpperCase(ID), 1, 50) = CopyStr(UpperCase(IDToCheck), 1, 50)); + end; + + /// + /// Indicates if the integration should be enabled by default. + /// + /// The integration ID/ + /// true if it should be approved by default; otherwise false. + local procedure ShouldApproveByDefault(IntegrationID: Text): Boolean + var + SystemPrivacyNoticeReg: Codeunit "System Privacy Notice Reg."; + begin + if CheckIntegrationIDEquality(SystemPrivacyNoticeReg.GetMicrosoftLearnID(), IntegrationID) then + exit(true); + + exit(false); + end; + + /// + /// Determines whether the admin or user has disagreed with the Privacy Notice. + /// + /// Identification of an existing privacy notice. + /// Whether the Privacy Notice was disagreed to. + procedure IsApprovalStateDisagreed(Id: Code[50]): Boolean + var + State: Enum "Privacy Notice Approval State"; + begin + State := CheckPrivacyNoticeApprovalState(Id); + exit(IsApprovalStateDisagreed(State)); + end; + + /// + /// Determines whether the admin or user has disagreed with the Privacy Notice. + /// + /// The approval state. + /// Whether the Privacy Notice was disagreed to. + 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 diff --git a/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al b/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al index 290732a3df..a6ded490cf 100644 --- a/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al +++ b/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al @@ -45,7 +45,7 @@ page 1565 "Privacy Notices" field(Accepted; Accepted) { Caption = 'Agree for Everyone'; - ToolTip = 'Specifies whether an administrator has accepted the integration''s privacy notice on behalf of all users.'; + ToolTip = 'Specifies whether an administrator (or the system by default) has accepted the integration''s privacy notice on behalf of all users.'; ApplicationArea = All; trigger OnValidate() @@ -92,6 +92,7 @@ page 1565 "Privacy Notices" SetRecordApprovalState(); end; } + #pragma warning disable AA0218 field(Accepted2; Rec.Enabled) { @@ -175,6 +176,8 @@ page 1565 "Privacy Notices" else PrivacyNotice.SetApprovalState(Rec.ID, "Privacy Notice Approval State"::"Not set"); end; + + CurrPage.Update(); end; } diff --git a/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al b/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al index 7c7538f19e..71d654afe8 100644 --- a/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al +++ b/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al @@ -15,11 +15,20 @@ codeunit 1566 "System Privacy Notice Reg." MicrosoftTeamsTxt: Label 'Microsoft Teams', Locked = true; // Product names are not translated and it's important this entry exists. PowerAutomateIdTxt: Label 'Power Automate', Locked = true; // Product names are not translated and it's important this entry exists. PowerAutomateLabelTxt: Label 'Microsoft Power Automate', Locked = true; // Product names are not translated and it's important this entry exists. + MicrosoftLearnTxt: Label 'Microsoft Learn', Locked = true; // Product names are not translated and it's important this entry exists. + + procedure GetMicrosoftLearnID(): Text + begin + exit(MicrosoftLearnTxt); + end; [EventSubscriber(ObjectType::Codeunit, Codeunit::"Privacy Notice", OnRegisterPrivacyNotices, '', false, false)] local procedure CreatePrivacyNoticeRegistrations(var TempPrivacyNotice: Record "Privacy Notice" temporary) begin TempPrivacyNotice.Init(); + TempPrivacyNotice."ID" := MicrosoftLearnTxt; + TempPrivacyNotice."Integration Service Name" := MicrosoftLearnTxt; + if not TempPrivacyNotice.Insert() then; TempPrivacyNotice.ID := MicrosoftTeamsTxt; TempPrivacyNotice."Integration Service Name" := MicrosoftTeamsTxt; if not TempPrivacyNotice.Insert() then;