diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al index 4d3c8eadd0..f322325fb9 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapEarlyPreview.Page.al @@ -35,7 +35,7 @@ page 7770 "Copilot Cap. Early Preview" Editable = false; Width = 30; } - field(Status; Rec.Status) + field(Status; Rec.EvaluateStatus()) { ApplicationArea = All; Caption = 'Status'; @@ -90,6 +90,8 @@ page 7770 "Copilot Cap. Early Preview" trigger OnAction() begin if Dialog.Confirm(ActivateEarlyPreviewTxt, false) then begin + if not Rec.EnsurePrivacyNoticesApproved() then + exit; Rec.Status := Rec.Status::Active; Rec.Modify(true); @@ -169,17 +171,19 @@ page 7770 "Copilot Cap. Early Preview" local procedure SetStatusStyle() begin - if (Rec.Status = Rec.Status::Active) then + if (Rec.EvaluateStatus() = Rec.Status::Active) then StatusStyleExpr := 'Favorable' else StatusStyleExpr := ''; end; local procedure SetActionsEnabled() + var + CopilotCapability: Codeunit "Copilot Capability"; begin if CopilotCapabilityImpl.IsAdmin() then begin ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled; - CapabilityEnabled := Rec.Status = Rec.Status::Active; + CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id"); end else begin ActionsEnabled := false; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al index c0fbbc521a..b76b8e4350 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesGA.Page.al @@ -35,7 +35,7 @@ page 7774 "Copilot Capabilities GA" Editable = false; Width = 30; } - field(Status; Rec.Status) + field(Status; Rec.EvaluateStatus()) { ApplicationArea = All; Caption = 'Status'; @@ -89,6 +89,9 @@ page 7774 "Copilot Capabilities GA" trigger OnAction() begin + if not Rec.EnsurePrivacyNoticesApproved() then + exit; + Rec.Status := Rec.Status::Active; Rec.Modify(true); @@ -179,17 +182,19 @@ page 7774 "Copilot Capabilities GA" local procedure SetStatusStyle() begin - if (Rec.Status = Rec.Status::Active) then + if (Rec.EvaluateStatus() = Rec.Status::Active) then StatusStyleExpr := 'Favorable' else StatusStyleExpr := ''; end; local procedure SetActionsEnabled() + var + CopilotCapability: Codeunit "Copilot Capability"; begin if CopilotCapabilityImpl.IsAdmin() then begin ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled; - CapabilityEnabled := Rec.Status = Rec.Status::Active; + CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id"); end else begin ActionsEnabled := false; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al index dc3da9694c..38e90454e1 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilitiesPreview.Page.al @@ -35,7 +35,7 @@ page 7773 "Copilot Capabilities Preview" Editable = false; Width = 30; } - field(Status; Rec.Status) + field(Status; Rec.EvaluateStatus()) { ApplicationArea = All; Caption = 'Status'; @@ -89,6 +89,9 @@ page 7773 "Copilot Capabilities Preview" trigger OnAction() begin + if not Rec.EnsurePrivacyNoticesApproved() then + exit; + Rec.Status := Rec.Status::Active; Rec.Modify(true); @@ -166,17 +169,19 @@ page 7773 "Copilot Capabilities Preview" local procedure SetStatusStyle() begin - if (Rec.Status = Rec.Status::Active) then + if (Rec.EvaluateStatus() = Rec.Status::Active) then StatusStyleExpr := 'Favorable' else StatusStyleExpr := ''; end; local procedure SetActionsEnabled() + var + CopilotCapability: Codeunit "Copilot Capability"; begin if CopilotCapabilityImpl.IsAdmin() then begin ActionsEnabled := (Rec.Capability.AsInteger() <> 0) and DataMovementEnabled; - CapabilityEnabled := Rec.Status = Rec.Status::Active; + CapabilityEnabled := CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id"); end else begin ActionsEnabled := false; diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al b/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al index 5f4b18bec3..0f6215c940 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapability.Codeunit.al @@ -118,4 +118,15 @@ codeunit 7773 "Copilot Capability" begin exit(CopilotCapabilityImpl.IsCapabilityActive(CopilotCapability, AppId)); end; + + /// + /// Event that is raised to specify if a Copilot capability depends on any additional privacy notices. + /// + /// The capability. + /// The app id associated with the capability. + /// The list of required privacy notices for the capability to be enabled. + [IntegrationEvent(false, false)] + procedure OnGetRequiredPrivacyNotices(CopilotCapability: Enum "Copilot Capability"; AppId: Guid; var RequiredPrivacyNotices: List of [Code[50]]) + begin + end; } \ No newline at end of file diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al index 20f2117a1e..30504e7049 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilityImpl.Codeunit.al @@ -133,13 +133,27 @@ codeunit 7774 "Copilot Capability Impl" procedure IsCapabilityActive(CopilotCapability: Enum "Copilot Capability"; AppId: Guid): Boolean var CopilotSettings: Record "Copilot Settings"; + CopilotCapabilityCU: Codeunit "Copilot Capability"; + PrivacyNotice: Codeunit "Privacy Notice"; + RequiredPrivacyNotices: List of [Code[50]]; + RequiredPrivacyNotice: Code[50]; begin CopilotSettings.ReadIsolation(IsolationLevel::ReadCommitted); CopilotSettings.SetLoadFields(Status); if not CopilotSettings.Get(CopilotCapability, AppId) then exit(false); - exit(CopilotSettings.Status = Enum::"Copilot Status"::Active); + CopilotCapabilityCU.OnGetRequiredPrivacyNotices(CopilotCapability, AppId, RequiredPrivacyNotices); + + if (CopilotSettings.Status <> Enum::"Copilot Status"::Active) or (RequiredPrivacyNotices.Count() <= 0) then + exit(CopilotSettings.Status = Enum::"Copilot Status"::Active); + + // check privacy notices + foreach RequiredPrivacyNotice in RequiredPrivacyNotices do + if (PrivacyNotice.GetPrivacyNoticeApprovalState(RequiredPrivacyNotice, true) <> Enum::"Privacy Notice Approval State"::Agreed) then + exit(false); + + exit(true); end; procedure SendActivateTelemetry(CopilotCapability: Enum "Copilot Capability"; AppId: Guid) diff --git a/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al b/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al index 3e236a2e6e..3e8832d3c4 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al +++ b/src/System Application/App/AI/src/Copilot/CopilotCapabilityInstall.Codeunit.al @@ -5,6 +5,7 @@ namespace System.AI; using System.Environment; +using System.Privacy; codeunit 7760 "Copilot Capability Install" { @@ -53,4 +54,22 @@ codeunit 7760 "Copilot Capability Install" begin RegisterCapabilities(); end; + + [EventSubscriber(ObjectType::Codeunit, Codeunit::"Copilot Capability", 'OnGetRequiredPrivacyNotices', '', false, false)] + local procedure OnGetRequiredPrivacyNotices(CopilotCapability: Enum "Copilot Capability"; AppId: Guid; var RequiredPrivacyNotices: List of [Code[50]]) + var + SystemPrivacyNoticeReg: Codeunit "System Privacy Notice Reg."; + ModuleInfo: ModuleInfo; + begin + NavApp.GetCurrentModuleInfo(ModuleInfo); + + if AppId <> ModuleInfo.Id then + exit; + + if CopilotCapability <> Enum::"Copilot Capability"::Chat then + exit; + + if not RequiredPrivacyNotices.Contains(SystemPrivacyNoticeReg.GetMicrosoftLearnID()) then + RequiredPrivacyNotices.Add(SystemPrivacyNoticeReg.GetMicrosoftLearnID()); + end; } \ No newline at end of file diff --git a/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al b/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al index 2f2644e30b..9ee6c06bfb 100644 --- a/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al +++ b/src/System Application/App/AI/src/Copilot/CopilotSettings.Table.al @@ -4,6 +4,8 @@ // ------------------------------------------------------------------------------------------------ namespace System.AI; +using System.Privacy; + /// /// Table to keep track of each Copilot Capability settings. /// @@ -51,4 +53,36 @@ table 7775 "Copilot Settings" Clustered = true; } } + + procedure EvaluateStatus(): Enum "Copilot Status" + var + CopilotCapability: Codeunit "Copilot Capability"; + begin + if Rec.Status <> Rec.Status::Active then + exit(Rec.Status); + + if CopilotCapability.IsCapabilityActive(Rec.Capability, Rec."App Id") then + exit(Rec.Status::Active) + else + exit(Rec.Status::Inactive); + end; + + procedure EnsurePrivacyNoticesApproved(): Boolean + var + CopilotCapability: Codeunit "Copilot Capability"; + PrivacyNotice: Codeunit "Privacy Notice"; + RequiredPrivacyNotices: List of [Code[50]]; + RequiredPrivacyNotice: Code[50]; + begin + CopilotCapability.OnGetRequiredPrivacyNotices(Rec.Capability, Rec."App Id", RequiredPrivacyNotices); + + if RequiredPrivacyNotices.Count() <= 0 then + exit(true); + + foreach RequiredPrivacyNotice in RequiredPrivacyNotices do + if not PrivacyNotice.ConfirmPrivacyNoticeApproval(RequiredPrivacyNotice, true) then + exit(false); + + exit(true); + end; } \ No newline at end of file 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 a6ded490cf..c951c4e682 100644 --- a/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al +++ b/src/System Application/App/Privacy Notice/src/PrivacyNotices.Page.al @@ -159,6 +159,7 @@ page 1565 "Privacy Notices" trigger OnAfterGetRecord() begin + Rec.CalcFields(Rec.Enabled, Rec.Disabled); Accepted := Rec.Enabled; Rejected := Rec.Disabled; UserDecides := not (Accepted or Rejected); 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 71d654afe8..7445673304 100644 --- a/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al +++ b/src/System Application/App/Privacy Notice/src/SystemPrivacyNoticeReg.Codeunit.al @@ -5,9 +5,11 @@ namespace System.Privacy; +/// +/// This codeunit registers platform level privacy notices and provides procedures to consistently reference the privacy notices. +/// codeunit 1566 "System Privacy Notice Reg." { - Access = Internal; InherentEntitlements = X; InherentPermissions = X; @@ -17,11 +19,6 @@ codeunit 1566 "System Privacy Notice Reg." 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 @@ -37,16 +34,37 @@ codeunit 1566 "System Privacy Notice Reg." if not TempPrivacyNotice.Insert() then; end; + /// + /// Gets the Microsoft Learn privacy notice identifier. + /// + /// The privacy notice id for Microsoft Learn. + procedure GetMicrosoftLearnID(): Code[50] + begin + exit(MicrosoftLearnTxt); + end; + + /// + /// Gets the Microsoft Teams privacy notice identifier. + /// + /// The privacy notice id for Microsoft Teams. procedure GetTeamsPrivacyNoticeId(): Code[50] begin exit(MicrosoftTeamsTxt); end; + /// + /// Gets the Power Automate privacy notice identifier. + /// + /// The privacy notice id for Power Automate. procedure GetPowerAutomatePrivacyNoticeId(): Code[50] begin exit(PowerAutomateIdTxt); end; + /// + /// Gets the Power Automate privacy notice name. + /// + /// The privacy notice name for Power Automate. procedure GetPowerAutomatePrivacyNoticeName(): Code[250] begin exit(PowerAutomateLabelTxt);