diff --git a/.github/workflows/PowerShell.yaml b/.github/workflows/PowerShell.yaml index 1b2b1c8909..c2585c3de8 100644 --- a/.github/workflows/PowerShell.yaml +++ b/.github/workflows/PowerShell.yaml @@ -22,7 +22,7 @@ jobs: security-events: write # for github/codeql-action/upload-sarif to upload SARIF results steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit diff --git a/.github/workflows/UpdateALGoProjects.yaml b/.github/workflows/UpdateALGoProjects.yaml index ac9c94ce87..da4ef15a54 100644 --- a/.github/workflows/UpdateALGoProjects.yaml +++ b/.github/workflows/UpdateALGoProjects.yaml @@ -20,7 +20,7 @@ jobs: updateBranches: ${{ steps.getOfficialBranches.outputs.branchesJson }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit diff --git a/.github/workflows/UpdateBCArtifactVersion.yaml b/.github/workflows/UpdateBCArtifactVersion.yaml index 56ddb07ffc..4db06bae77 100644 --- a/.github/workflows/UpdateBCArtifactVersion.yaml +++ b/.github/workflows/UpdateBCArtifactVersion.yaml @@ -20,7 +20,7 @@ jobs: updateBranches: ${{ steps.getOfficialBranches.outputs.branchesJson }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit diff --git a/.github/workflows/UpdatePackageVersions.yaml b/.github/workflows/UpdatePackageVersions.yaml index 96de1a9085..1356aac60d 100644 --- a/.github/workflows/UpdatePackageVersions.yaml +++ b/.github/workflows/UpdatePackageVersions.yaml @@ -20,7 +20,7 @@ jobs: updateBranches: ${{ steps.getOfficialBranches.outputs.branchesJson }} steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit diff --git a/.github/workflows/WorkitemValidation.yaml b/.github/workflows/WorkitemValidation.yaml index 518c84279f..85bf803dc6 100644 --- a/.github/workflows/WorkitemValidation.yaml +++ b/.github/workflows/WorkitemValidation.yaml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit @@ -40,7 +40,7 @@ jobs: needs: GitHubIssueValidation steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit diff --git a/.github/workflows/scorecard-analysis.yml b/.github/workflows/scorecard-analysis.yml index d446560469..9e2430bd54 100644 --- a/.github/workflows/scorecard-analysis.yml +++ b/.github/workflows/scorecard-analysis.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit diff --git a/src/Apps/W1/EDocument/App/Permissions/EDocCoreBasic.PermissionSet.al b/src/Apps/W1/EDocument/App/Permissions/EDocCoreBasic.PermissionSet.al index d2a5c81df4..694bf4c6d0 100644 --- a/src/Apps/W1/EDocument/App/Permissions/EDocCoreBasic.PermissionSet.al +++ b/src/Apps/W1/EDocument/App/Permissions/EDocCoreBasic.PermissionSet.al @@ -7,8 +7,8 @@ namespace Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.IO.Peppol; using Microsoft.EServices.EDocument.OrderMatch; -using Microsoft.eServices.EDocument.Service.Participant; using Microsoft.eServices.EDocument.Processing.Import.Purchase; +using Microsoft.eServices.EDocument.Service.Participant; permissionset 6103 "E-Doc. Core - Basic" { diff --git a/src/Apps/W1/EDocument/App/src/Document/EDocument.Table.al b/src/Apps/W1/EDocument/App/src/Document/EDocument.Table.al index 23b54b5e24..f985d5ae59 100644 --- a/src/Apps/W1/EDocument/App/src/Document/EDocument.Table.al +++ b/src/Apps/W1/EDocument/App/src/Document/EDocument.Table.al @@ -296,7 +296,6 @@ table 6121 "E-Document" Caption = 'Last Clearance Request Time'; DataClassification = SystemMetadata; } - #endregion } keys @@ -314,6 +313,9 @@ table 6121 "E-Document" key(Key4; SystemCreatedAt) { } + key(DueDate; "Due Date") + { + } } trigger OnDelete() diff --git a/src/Apps/W1/EDocument/App/src/Document/Inbound/InboundEDocuments.Page.al b/src/Apps/W1/EDocument/App/src/Document/Inbound/InboundEDocuments.Page.al index 950849299f..d49432b49f 100644 --- a/src/Apps/W1/EDocument/App/src/Document/Inbound/InboundEDocuments.Page.al +++ b/src/Apps/W1/EDocument/App/src/Document/Inbound/InboundEDocuments.Page.al @@ -5,7 +5,11 @@ namespace Microsoft.eServices.EDocument; using Microsoft.eServices.EDocument.Processing.Import; +using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.Foundation.Attachment; +using Microsoft.Purchases.Vendor; +using System.Agents; +using System.Agents.TaskPane; page 6105 "Inbound E-Documents" { @@ -19,7 +23,7 @@ page 6105 "Inbound E-Documents" DeleteAllowed = true; InsertAllowed = false; ModifyAllowed = false; - SourceTableView = sorting(SystemCreatedAt) order(descending) where(Direction = const("E-Document Direction"::Incoming)); + SourceTableView = sorting("Due Date") order(ascending) where(Direction = const("E-Document Direction"::Incoming)); layout { @@ -45,6 +49,68 @@ page 6105 "Inbound E-Documents" EDocumentHelper.OpenDraftPage(Rec); end; } + field(ConfirmedVendorName; ConfirmedVendorTxt) + { + Caption = 'Vendor Name'; + ToolTip = 'Specifies the bill-to/pay-to name of the document that was confirmed by the user during processing.'; + trigger OnDrillDown() + var + Vendor: Record Vendor; + VendorCardPage: Page "Vendor Card"; + begin + if Rec."Bill-to/Pay-to No." = '' then + exit; + Vendor.Get(Rec."Bill-to/Pay-to No."); + Vendor.SetRecFilter(); + VendorCardPage.SetRecord(Vendor); + VendorCardPage.RunModal(); + end; + } + field("Import Processing Status"; Rec."Import Processing Status") + { + Caption = 'Processing Status'; + ToolTip = 'Specifies the stage in which the processing of this document is in.'; + } + field("Document Date"; Rec."Document Date") + { + Caption = 'Document Date'; + ToolTip = 'Specifies the date of the document.'; + } + field("Due Date"; Rec."Due Date") + { + Caption = 'Due Date'; + ToolTip = 'Specifies the due date of the document.'; + } + field(TaskID; AgentTask.ID) + { + Caption = 'Agent Task No.'; + ToolTip = 'Specifies the task number for the document.'; + Editable = false; + ExtendedDatatype = Task; + BlankNumbers = BlankZero; + + trigger OnDrillDown() + var + Task: Record "Agent Task"; + TaskPane: Codeunit "Task Pane"; + begin + if AgentTask.ID = 0 then + exit; + Task.Get(AgentTask.ID); + TaskPane.ShowTask(Task); + end; + } + field(TaskStatus; AgentTask.Status) + { + Caption = 'Task Status'; + ToolTip = 'Specifies the status of the agent task for this document.'; + Editable = false; + } + field("Vendor Name"; EDocumentPurchaseHeader."Vendor Company Name") + { + Caption = 'Sender'; + ToolTip = 'Specifies the vendor name of the document.'; + } field(SystemCreatedAt; Rec.SystemCreatedAt) { Caption = 'Received At'; @@ -66,16 +132,6 @@ page 6105 "Inbound E-Documents" Caption = 'Source Details'; ToolTip = 'Specifies the details about the source of the document.'; } - field("Vendor Name"; VendorNameTxt) - { - Caption = 'Sender'; - ToolTip = 'Specifies the vendor name of the document.'; - } - field("Import Processing Status"; Rec."Import Processing Status") - { - Caption = 'Processing Status'; - ToolTip = 'Specifies the stage in which the processing of this document is in.'; - } field("Document Type"; Rec."Document Type") { Caption = 'Document Type'; @@ -342,9 +398,11 @@ page 6105 "Inbound E-Documents" var EDocumentProcessing: Codeunit "E-Document Processing"; begin + if EDocumentPurchaseHeader.Get(Rec."Entry No") then; RecordLinkTxt := EDocumentProcessing.GetRecordLinkText(Rec); PopulateDocumentNameTxt(); - PopulateVendorNameTxt(); + PopulateConfirmedVendorNameTxt(); + PopulateTaskInfo(); SetDocumentTypeStyleExpression(); HasPdf := false; @@ -368,9 +426,17 @@ page 6105 "Inbound E-Documents" DocumentNameTxt := CaptionBuilder.ToText(); end; - local procedure PopulateVendorNameTxt() + local procedure PopulateConfirmedVendorNameTxt() + begin + ConfirmedVendorTxt := Rec."Bill-to/Pay-to Name" + end; + + local procedure PopulateTaskInfo() begin - VendorNameTxt := Rec."Bill-to/Pay-to Name"; + AgentTask.SetRange("Company Name", CompanyName()); + AgentTask.SetRange("External ID", Format(Rec."Entry No")); + if not AgentTask.IsEmpty() then + Clear(AgentTask); end; trigger OnOpenPage() @@ -494,8 +560,10 @@ page 6105 "Inbound E-Documents" var EDocDataStorage: Record "E-Doc. Data Storage"; + EDocumentPurchaseHeader: Record "E-Document Purchase Header"; + AgentTask: Record "Agent Task"; EDocumentHelper: Codeunit "E-Document Helper"; - RecordLinkTxt, VendorNameTxt, DocumentNameTxt, DocumentTypeStyleTxt : Text; + RecordLinkTxt, DocumentNameTxt, DocumentTypeStyleTxt, ConfirmedVendorTxt : Text; HasPdf: Boolean; #if not CLEAN27 EmailVisibilityFlag: Boolean; diff --git a/src/Apps/W1/EDocument/App/src/Processing/AI/Tools/EDocHistoricalMatching.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/AI/Tools/EDocHistoricalMatching.Codeunit.al index 5d0a10a0d2..0fe5c617bf 100644 --- a/src/Apps/W1/EDocument/App/src/Processing/AI/Tools/EDocHistoricalMatching.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/Processing/AI/Tools/EDocHistoricalMatching.Codeunit.al @@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument.Processing.AI; using Microsoft.eServices.EDocument.Processing.Import; using Microsoft.eServices.EDocument.Processing.Import.Purchase; using Microsoft.Purchases.Document; +using Microsoft.Finance.AllocationAccount; using Microsoft.Purchases.History; using Microsoft.Purchases.Vendor; using System.AI; @@ -177,6 +178,7 @@ codeunit 6177 "E-Doc. Historical Matching" implements "AOAI Function", IEDocAISy local procedure LoadHistoricalDataIntoTempTable(var TempPurchInvLine: Record "Purch. Inv. Line" temporary; VendorNo: Code[20]; HistoricalMatchingConfig: Text) var PurchInvLine: Record "Purch. Inv. Line"; + AllocationAccount: Record "Allocation Account"; FeatureTelemetry: Codeunit "Feature Telemetry"; OneYearAgoDate: Date; RecordCount: Integer; @@ -202,6 +204,9 @@ codeunit 6177 "E-Doc. Historical Matching" implements "AOAI Function", IEDocAISy TempPurchInvLine.Copy(PurchInvLine); repeat TempPurchInvLine := PurchInvLine; + if TempPurchInvLine."Allocation Account No." <> '' then + if AllocationAccount.Get(TempPurchInvLine."Allocation Account No.") then + TempPurchInvLine.Description := AllocationAccount.Name; TempPurchInvLine.Insert(); RecordCount += 1; until (PurchInvLine.Next() = 0) or (RecordCount >= MaxHistoricalRecords); diff --git a/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchInvPDF.Codeunit.al b/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchInvPDF.Codeunit.al index bf9a55282b..1052ee915a 100644 --- a/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchInvPDF.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchInvPDF.Codeunit.al @@ -4,8 +4,8 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.EServices.EDocument.Processing.Import.Purchase; -using System.Utilities; using System.IO; +using System.Utilities; /// /// Facade codeunit for managing sample purchase invoice PDF generation. diff --git a/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Codeunit.al b/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Codeunit.al index f2459375dd..e4dce2bee3 100644 --- a/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Codeunit.al +++ b/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Codeunit.al @@ -4,16 +4,16 @@ // ------------------------------------------------------------------------------------------------ namespace Microsoft.EServices.EDocument.Processing.Import.Purchase; -using Microsoft.Purchases.Document; -using System.Utilities; -using Microsoft.Purchases.Vendor; -using Microsoft.Inventory.Item; using Microsoft.Finance.GeneralLedger.Account; -using Microsoft.Foundation.Period; -using Microsoft.Foundation.UOM; -using Microsoft.Foundation.Company; using Microsoft.Finance.GeneralLedger.Setup; using Microsoft.Foundation.Address; +using Microsoft.Foundation.Company; +using Microsoft.Foundation.Period; +using Microsoft.Foundation.UOM; +using Microsoft.Inventory.Item; +using Microsoft.Purchases.Document; +using Microsoft.Purchases.Vendor; +using System.Utilities; /// /// The purpose of the codeunit is to generate sample purchase invoices in PDF format. @@ -119,23 +119,7 @@ codeunit 6209 "E-Doc Sample Purchase Invoice" /// Adds a sample purchase invoice line to the current temporary header. /// procedure AddLine(LineType: Enum "Purchase Line Type"; No: Code[20]; Description: Text[100]; Quantity: Decimal; DirectUnitCost: Decimal; DeferralCode: Code[10]; UnitOfMeasureCode: Code[10]) - var - Item: Record Item; - GLAccount: Record "G/L Account"; begin - if Description = '' then - case LineType of - Enum::"Purchase Line Type"::Item: - begin - Item.Get(No); - Description := Item.Description; - end; - Enum::"Purchase Line Type"::"G/L Account": - begin - GLAccount.Get(No); - Description := GLAccount.Name; - end; - end; AddLine(TempEDocPurchLine, TempEDocPurchHeader, LineType, No, Description, Quantity, DirectUnitCost, DeferralCode, UnitOfMeasureCode); end; @@ -166,7 +150,7 @@ codeunit 6209 "E-Doc Sample Purchase Invoice" EDocPurchaseLine."[BC] Purchase Line Type" := LineType; EDocPurchaseLine."[BC] Purchase Type No." := No; EDocPurchaseLine."Product Code" := No; - EDocPurchaseLine.Description := Description; + EDocPurchaseLine.Description := GetLineDescription(LineType, No, Description); EDocPurchaseLine.Quantity := Quantity; if UnitOfMeasureCode <> '' then UnitOfMeasure.Get(UnitOfMeasureCode) @@ -210,4 +194,26 @@ codeunit 6209 "E-Doc Sample Purchase Invoice" begin exit(CopyStr(TempEDocPurchHeader."Sales Invoice No.", 1, MaxStrLen(SamplePurchInvFile."File Name"))) end; + + local procedure GetLineDescription(LineType: Enum "Purchase Line Type"; No: Code[20]; Description: Text[100]): Text[100] + var + Item: Record Item; + GLAccount: Record "G/L Account"; + begin + if Description <> '' then + exit(Description); + case LineType of + Enum::"Purchase Line Type"::Item: + begin + Item.Get(No); + exit(Item.Description); + end; + Enum::"Purchase Line Type"::"G/L Account": + begin + GLAccount.Get(No); + exit(GLAccount.Name); + end; + end; + exit(''); + end; } \ No newline at end of file diff --git a/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Report.al b/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Report.al index f19f3b2e80..1aaf9ac09e 100644 --- a/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Report.al +++ b/src/Apps/W1/EDocument/App/src/SampleInvoice/EDocSamplePurchaseInvoice.Report.al @@ -7,8 +7,8 @@ namespace Microsoft.EServices.EDocument.Processing.Import.Purchase; using Microsoft.Foundation.Address; using Microsoft.Foundation.Company; using Microsoft.Inventory.Location; -using System.Utilities; using Microsoft.Purchases.Vendor; +using System.Utilities; /// /// Report for generating sample purchase invoice PDFs diff --git a/src/Apps/W1/EDocument/Demo Data/app.json b/src/Apps/W1/EDocument/Demo Data/app.json index e67e300815..4298ce469a 100644 --- a/src/Apps/W1/EDocument/Demo Data/app.json +++ b/src/Apps/W1/EDocument/Demo Data/app.json @@ -31,11 +31,26 @@ "name": "E-Document Core Demo Data (US)", "publisher": "Microsoft" }, + { + "id": "d67591a4-895c-4d2e-8e94-402badd1a5c7", + "name": "E-Document Core Demo Data (CA)", + "publisher": "Microsoft" + }, { "id": "f3e4e6f8-2ba7-4202-834d-141ed9b89191", "name": "E-Document Core Demo Data (GB)", "publisher": "Microsoft" }, + { + "id": "f3e4e6f8-2ba7-4202-834d-141ed9b89192", + "name": "E-Document Core Demo Data (DK)", + "publisher": "Microsoft" + }, + { + "id": "f3e4e6f8-2ba7-4202-834d-141ed9b89192", + "name": "E-Document Core Demo Data (ES)", + "publisher": "Microsoft" + }, { "id": "444799e3-0d2d-4f83-b869-6ba642eb54a3", "name": "E-Document Core Demo Data (AU)", @@ -45,6 +60,21 @@ "id": "0b450a33-3524-488d-99ca-1ce1beb37bd3", "name": "E-Document Core Demo Data (NZ)", "publisher": "Microsoft" + }, + { + "id": "f3e4e6f8-2ba7-4202-834d-141ed9b89193", + "name": "E-Document Core Demo Data (FR)", + "publisher": "Microsoft" + }, + { + "id": "de0dddf3-9917-430d-8d20-6e7679a08501", + "name": "E-Document Core Demo Data IT", + "publisher": "Microsoft" + }, + { + "id": "f3e4e6f8-2ba7-4202-834d-141ed9b89194", + "name": "E-Document Core Demo Data (DE)", + "publisher": "Microsoft" } ], "screenshots": [], diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRule.Table.al b/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRule.Table.al index 0fdf415c62..07ce555105 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRule.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRule.Table.al @@ -32,8 +32,8 @@ using System.Reflection; table 20404 "Qlty. Inspection Gen. Rule" { Caption = 'Quality Inspection Generation Rule'; - DrillDownPageID = "Qlty. Inspection Gen. Rules"; - LookupPageID = "Qlty. Inspection Gen. Rules"; + DrillDownPageId = "Qlty. Inspection Gen. Rules"; + LookupPageId = "Qlty. Inspection Gen. Rules"; DataClassification = CustomerContent; fields @@ -70,20 +70,6 @@ table 20404 "Qlty. Inspection Gen. Rule" end else QltyJobQueueManagement.DeleteJobQueueIfNothingElseIsUsingThisGroup(Rec, xRec."Schedule Group"); end; - - trigger OnLookup() - var - QltyJobQueueManagement: Codeunit "Qlty. Job Queue Management"; - begin - QltyJobQueueManagement.CheckIfGenerationRuleCanBeScheduled(Rec); - if GuiAllowed() then - if Rec."Schedule Group" = '' then begin - Rec."Schedule Group" := DefaultScheduleGroupLbl; - Rec.Modify(false); - QltyJobQueueManagement.PromptCreateJobQueueEntryIfMissing(Rec."Schedule Group"); - end else - QltyJobQueueManagement.RunPageLookupJobQueueEntriesForScheduleGroup(Rec."Schedule Group") - end; } field(10; "Template Code"; Code[20]) { @@ -286,7 +272,6 @@ table 20404 "Qlty. Inspection Gen. Rule" var TriggerNotActiveConfirmQst: Label 'You have set an automatic trigger but the inspection generation rule activation is set to "%1". Do you want to update the activation trigger to "%2?"', Comment = '%1=current activation trigger,%2=proposed activation trigger'; RuleCurrentlyDisabledLbl: Label 'The generation rule Sort Order %1, Template Code %2 is currently disabled. It will need to have an activation trigger of "Automatic Only" or "Manual or Automatic" before it will be triggered by "%3"', Comment = '%1=generation rule sort order,%2=generation rule template code,%3=auto trigger'; - DefaultScheduleGroupLbl: Label 'QM', Locked = true; ChooseTemplateFirstErr: Label 'Please choose the template first.'; FilterLengthErr: Label 'This filter is too long and must be less than %1 characters.', Comment = '%1=filter string maximum length'; @@ -328,7 +313,7 @@ table 20404 "Qlty. Inspection Gen. Rule" end; end; - internal procedure HandleOnLookupSourceTable() + internal procedure HandleOnAssistEditSourceTable() var QltyFilterHelpers: Codeunit "Qlty. Filter Helpers"; QltyInspecGenRuleMgmt: Codeunit "Qlty. Inspec. Gen. Rule Mgmt."; diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRules.Page.al b/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRules.Page.al index c6f6c12312..27f6fc56e6 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRules.Page.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/GenerationRule/QltyInspectionGenRules.Page.al @@ -57,7 +57,7 @@ page 20405 "Qlty. Inspection Gen. Rules" trigger OnAssistEdit() begin - Rec.HandleOnLookupSourceTable(); + Rec.HandleOnAssistEditSourceTable(); CurrPage.Update(); end; } @@ -72,7 +72,7 @@ page 20405 "Qlty. Inspection Gen. Rules" if Rec.Insert(true) then; Commit(); end; - Rec.HandleOnLookupSourceTable(); + Rec.HandleOnAssistEditSourceTable(); if xRec."Entry No." = Rec."Entry No." then CurrPage.Update(true); end; @@ -148,6 +148,19 @@ page 20405 "Qlty. Inspection Gen. Rules" } field("Schedule Group"; Rec."Schedule Group") { + trigger OnDrillDown() + var + QltyJobQueueManagement: Codeunit "Qlty. Job Queue Management"; + begin + QltyJobQueueManagement.CheckIfGenerationRuleCanBeScheduled(Rec); + if GuiAllowed() then + if Rec."Schedule Group" = '' then begin + Rec."Schedule Group" := DefaultScheduleGroupLbl; + Rec.Modify(false); + QltyJobQueueManagement.PromptCreateJobQueueEntryIfMissing(Rec."Schedule Group"); + end else + QltyJobQueueManagement.RunPageLookupJobQueueEntriesForScheduleGroup(Rec."Schedule Group") + end; } } } @@ -379,6 +392,7 @@ page 20405 "Qlty. Inspection Gen. Rules" RowStyle: Option None,Standard,StandardAccent,Strong,StrongAccent,Attention,AttentionAccent,Favorable,Unfavorable,Ambiguous,Subordinate; GenerationRulesCaptionLbl: Label 'Quality Inspection Generation Rules'; GenerationRulesCaptionForTemplateLbl: Label 'Quality Inspection Generation Rules for %1', Comment = '%1=the template'; + DefaultScheduleGroupLbl: Label 'QM', Locked = true; trigger OnInit() begin diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyInspectionResult.Table.al b/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyInspectionResult.Table.al index 69da278bc1..9ee380c2ff 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyInspectionResult.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyInspectionResult.Table.al @@ -18,7 +18,8 @@ using Microsoft.QualityManagement.Document; table 20411 "Qlty. Inspection Result" { Caption = 'Quality Inspection Result'; - DrillDownPageID = "Qlty. Inspection Result List"; + LookupPageId = "Qlty. Inspection Result List"; + DrillDownPageId = "Qlty. Inspection Result List"; DataClassification = CustomerContent; fields diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInsSourceConfigList.Page.al b/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInsSourceConfigList.Page.al index 2e19f88cd4..60958f4465 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInsSourceConfigList.Page.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInsSourceConfigList.Page.al @@ -12,7 +12,7 @@ using Microsoft.QualityManagement.Configuration; page 20412 "Qlty. Ins. Source Config. List" { Caption = 'Quality Inspection Source Configurations'; - CardPageID = "Qlty. Inspect. Source Config."; + CardPageId = "Qlty. Inspect. Source Config."; DeleteAllowed = false; Editable = false; InsertAllowed = false; diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInspectSourceConfig.Table.al b/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInspectSourceConfig.Table.al index b6ffb50939..b9c81be84d 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInspectSourceConfig.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/SourceConfiguration/QltyInspectSourceConfig.Table.al @@ -26,8 +26,8 @@ using System.Reflection; table 20407 "Qlty. Inspect. Source Config." { Caption = 'Quality Inspection Source Configuration'; - DrillDownPageID = "Qlty. Ins. Source Config. List"; - LookupPageID = "Qlty. Ins. Source Config. List"; + DrillDownPageId = "Qlty. Ins. Source Config. List"; + LookupPageId = "Qlty. Ins. Source Config. List"; DataClassification = CustomerContent; Description = 'Use this page to configure what will automatically populate from other tables into your quality inspections. This is also used to tell Business Central how to find one record from another, by setting which field in the ''From'' table connects to which field in the ''To'' table.'; diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateHdr.Table.al b/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateHdr.Table.al index 8ec52d71d0..a1b3f40662 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateHdr.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateHdr.Table.al @@ -15,8 +15,8 @@ using Microsoft.QualityManagement.Document; table 20402 "Qlty. Inspection Template Hdr." { Caption = 'Quality Inspection Template Header'; - DrillDownPageID = "Qlty. Inspection Template List"; - LookupPageID = "Qlty. Inspection Template List"; + DrillDownPageId = "Qlty. Inspection Template List"; + LookupPageId = "Qlty. Inspection Template List"; DataClassification = CustomerContent; Permissions = tabledata "Qlty. Inspection Template Line" = d; diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateList.Page.al b/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateList.Page.al index d0aae21586..7436ab6f20 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateList.Page.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/Template/QltyInspectionTemplateList.Page.al @@ -15,7 +15,7 @@ using Microsoft.QualityManagement.Document; page 20404 "Qlty. Inspection Template List" { Caption = 'Quality Inspection Templates'; - CardPageID = "Qlty. Inspection Template"; + CardPageId = "Qlty. Inspection Template"; DeleteAllowed = false; Editable = false; PageType = List; diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyLookupCode.Table.al b/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyLookupCode.Table.al index 8ff4682970..bf00b7f51c 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyLookupCode.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyLookupCode.Table.al @@ -10,8 +10,8 @@ namespace Microsoft.QualityManagement.Configuration.Template.Test; table 20408 "Qlty. Lookup Code" { Caption = 'Quality Lookup Code'; - DrillDownPageID = "Qlty. Lookup Code List"; - LookupPageID = "Qlty. Lookup Code List"; + DrillDownPageId = "Qlty. Lookup Code List"; + LookupPageId = "Qlty. Lookup Code List"; DataClassification = CustomerContent; fields diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyTest.Table.al b/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyTest.Table.al index b45b5b8985..3e8ab54833 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyTest.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/Template/Test/QltyTest.Table.al @@ -18,8 +18,8 @@ using System.Reflection; table 20401 "Qlty. Test" { Caption = 'Quality Test'; - DrillDownPageID = "Qlty. Tests"; - LookupPageID = "Qlty. Test Lookup"; + DrillDownPageId = "Qlty. Tests"; + LookupPageId = "Qlty. Test Lookup"; DataClassification = CustomerContent; fields diff --git a/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionHeader.Table.al b/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionHeader.Table.al index c7a4f84d4c..be88fcdb4a 100644 --- a/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionHeader.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionHeader.Table.al @@ -33,8 +33,8 @@ using System.Utilities; table 20405 "Qlty. Inspection Header" { Caption = 'Quality Inspection Header'; - DrillDownPageID = "Qlty. Inspection List"; - LookupPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; + LookupPageId = "Qlty. Inspection List"; DataClassification = CustomerContent; Permissions = tabledata "Qlty. Inspection Line" = d; diff --git a/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionLine.Table.al b/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionLine.Table.al index 0f686bd2bf..9151844715 100644 --- a/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionLine.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionLine.Table.al @@ -20,6 +20,7 @@ table 20406 "Qlty. Inspection Line" { Caption = 'Quality Inspection Line'; LookupPageId = "Qlty. Inspection Lines"; + DrillDownPageId = "Qlty. Inspection Lines"; DataClassification = CustomerContent; fields diff --git a/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionList.Page.al b/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionList.Page.al index bf71de9b97..be541ef7c8 100644 --- a/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionList.Page.al +++ b/src/Apps/W1/Quality Management/app/src/Document/QltyInspectionList.Page.al @@ -24,7 +24,7 @@ using Microsoft.Warehouse.Structure; page 20408 "Qlty. Inspection List" { Caption = 'Quality Inspections'; - CardPageID = "Qlty. Inspection"; + CardPageId = "Qlty. Inspection"; DeleteAllowed = false; Editable = false; PageType = List; diff --git a/src/Apps/W1/Quality Management/app/src/Reports/QltyReportSelectionQM.Page.al b/src/Apps/W1/Quality Management/app/src/Reports/QltyReportSelectionQM.Page.al index 6489eec60e..a9f5317130 100644 --- a/src/Apps/W1/Quality Management/app/src/Reports/QltyReportSelectionQM.Page.al +++ b/src/Apps/W1/Quality Management/app/src/Reports/QltyReportSelectionQM.Page.al @@ -40,7 +40,7 @@ page 20442 "Qlty. Report Selection - QM" } field("Report ID"; Rec."Report ID") { - LookupPageID = Objects; + LookupPageId = Objects; ToolTip = 'Specifies the object ID of the report.'; } field("Report Caption"; Rec."Report Caption") diff --git a/src/Apps/W1/Quality Management/app/src/RoleCenters/QltyInspectionActivities.Page.al b/src/Apps/W1/Quality Management/app/src/RoleCenters/QltyInspectionActivities.Page.al index 0ff174da8c..83854b9c48 100644 --- a/src/Apps/W1/Quality Management/app/src/RoleCenters/QltyInspectionActivities.Page.al +++ b/src/Apps/W1/Quality Management/app/src/RoleCenters/QltyInspectionActivities.Page.al @@ -24,23 +24,23 @@ page 20425 "Qlty. Inspection Activities" field("Unassigned Inspections"; Rec."Unassigned Inspections") { - DrillDownPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; } field("My Open Inspections"; Rec."My Open Inspections") { - DrillDownPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; } field("My Open and Due Inspections"; Rec."My Open and Due Inspections") { - DrillDownPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; } field("All Open Inspections"; Rec."All Open Inspections") { - DrillDownPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; } field("All Open and Due Inspections"; Rec."All Open and Due Inspections") { - DrillDownPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; } } cuegroup("Finished Inspections") @@ -49,11 +49,11 @@ page 20425 "Qlty. Inspection Activities" field("My Finished Inspections"; Rec."My Finished Inspections") { - DrillDownPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; } field("All Finished Inspections"; Rec."All Finished Inspections") { - DrillDownPageID = "Qlty. Inspection List"; + DrillDownPageId = "Qlty. Inspection List"; } } } diff --git a/src/Apps/W1/Quality Management/app/src/Setup/QltyManagementSetup.Table.al b/src/Apps/W1/Quality Management/app/src/Setup/QltyManagementSetup.Table.al index e4e0925cb6..29eeed3f06 100644 --- a/src/Apps/W1/Quality Management/app/src/Setup/QltyManagementSetup.Table.al +++ b/src/Apps/W1/Quality Management/app/src/Setup/QltyManagementSetup.Table.al @@ -27,7 +27,7 @@ using System.Environment.Configuration; table 20400 "Qlty. Management Setup" { Caption = 'Quality Management Setup'; - DrillDownPageID = "Qlty. Management Setup"; + DrillDownPageId = "Qlty. Management Setup"; DataClassification = CustomerContent; fields @@ -725,7 +725,6 @@ table 20400 "Qlty. Management Setup" /// /// Gets the first page template for the supplied page. /// - /// /// procedure GetItemReclassJournalTemplate(): Code[10] var diff --git a/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al b/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al index 4d6be95342..f8a7a584c4 100644 --- a/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Companies/Codeunits/ShpfyCompanyExport.Codeunit.al @@ -41,7 +41,6 @@ codeunit 30284 "Shpfy Company Export" Shop: Record "Shpfy Shop"; CompanyAPI: Codeunit "Shpfy Company API"; CatalogAPI: Codeunit "Shpfy Catalog API"; - MetafieldAPI: Codeunit "Shpfy Metafield API"; SkippedRecord: Codeunit "Shpfy Skipped Record"; CreateCustomers: Boolean; CountyCodeTooLongLbl: Label 'Can not export customer %1 %2. The length of the string is %3, but it must be less than or equal to %4 characters. Value: %5, field: %6', Comment = '%1 - Customer No., %2 - Customer Name, %3 - Length, %4 - Max Length, %5 - Value, %6 - Field Name'; @@ -213,7 +212,6 @@ codeunit 30284 "Shpfy Company Export" Shop := ShopifyShop; CompanyAPI.SetShop(Shop); CatalogAPI.SetShop(Shop); - MetafieldAPI.SetShop(Shop); end; local procedure UpdateShopifyCompany(Customer: Record Customer; CompanyId: BigInteger) @@ -252,8 +250,10 @@ codeunit 30284 "Shpfy Company Export" end; local procedure UpdateMetafields(ComppanyId: BigInteger) + var + Metafields: Codeunit "Shpfy Metafields"; begin - MetafieldAPI.CreateOrUpdateMetafieldsInShopify(Database::"Shpfy Company", ComppanyId); + Metafields.SyncMetafieldsToShopify(Database::"Shpfy Company", ComppanyId, Shop.Code); end; internal procedure SetCreateCompanies(NewCustomers: Boolean) diff --git a/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al b/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al index 6153e9a04b..5043f72eb0 100644 --- a/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Customers/Codeunits/ShpfyCustomerExport.Codeunit.al @@ -49,7 +49,6 @@ codeunit 30116 "Shpfy Customer Export" var Shop: Record "Shpfy Shop"; CustomerApi: Codeunit "Shpfy Customer API"; - MetafieldAPI: Codeunit "Shpfy Metafield API"; SkippedRecord: Codeunit "Shpfy Skipped Record"; CreateCustomers: Boolean; CountyCodeTooLongLbl: Label 'Can not export customer %1 %2. The length of the string is %3, but it must be less than or equal to %4 characters. Value: %5, field: %6', Comment = '%1 - Customer No., %2 - Customer Name, %3 - Length, %4 - Max Length, %5 - Value, %6 - Field Name'; @@ -228,7 +227,6 @@ codeunit 30116 "Shpfy Customer Export" begin Shop := ShopifyShop; CustomerApi.SetShop(Shop); - MetafieldAPI.SetShop(Shop) end; /// @@ -286,7 +284,9 @@ codeunit 30116 "Shpfy Customer Export" end; local procedure UpdateMetafields(CustomerId: BigInteger) + var + Metafields: Codeunit "Shpfy Metafields"; begin - MetafieldAPI.CreateOrUpdateMetafieldsInShopify(Database::"Shpfy Customer", CustomerId); + Metafields.SyncMetafieldsToShopify(Database::"Shpfy Customer", CustomerId, Shop.Code); end; } diff --git a/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al b/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al index 36d34992da..b9d790a07c 100644 --- a/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/ShpfyMetafieldAPI.Codeunit.al @@ -21,7 +21,42 @@ codeunit 30316 "Shpfy Metafield API" CommunicationMgt.SetShop(Shop); end; + local procedure SetShop(ShopCode: Code[20]) + begin + Shop.Get(ShopCode); + SetShop(Shop); + end; + #region To Shopify + internal procedure SyncMetafieldToShopify(var Metafield: Record "Shpfy Metafield"; ShopCode: Code[20]): BigInteger + var + UserErrorOnShopifyErr: Label 'Something went wrong while sending the metafield to Shopify. Check Shopify Log Entries for more details.'; + GraphQuery: TextBuilder; + JResponse: JsonToken; + JMetafields: JsonArray; + JUserErrors: JsonArray; + JItem: JsonToken; + begin + SetShop(ShopCode); + CreateMetafieldQuery(Metafield, GraphQuery); + JResponse := UpdateMetafields(GraphQuery.ToText()); + + JsonHelper.GetJsonArray(JResponse, JUserErrors, 'data.metafieldsSet.userErrors'); + + if JUserErrors.Count() = 0 then begin + JsonHelper.GetJsonArray(JResponse, JMetafields, 'data.metafieldsSet.metafields'); + JMetafields.Get(0, JItem); + exit(JsonHelper.GetValueAsBigInteger(JItem, 'legacyResourceId')); + end else + Error(UserErrorOnShopifyErr); + end; + + internal procedure SyncMetafieldsToShopify(ParentTableNo: Integer; OwnerId: BigInteger; ShopCode: Code[20]) + begin + SetShop(ShopCode); + CreateOrUpdateMetafieldsInShopify(ParentTableNo, OwnerId); + end; + /// /// Creates or updates the metafields in Shopify. /// @@ -177,6 +212,12 @@ codeunit 30316 "Shpfy Metafield API" DeleteUnusedMetafields(MetafieldIds); end; + internal procedure GetMetafieldDefinitions(ParentTableNo: Integer; OwnerId: BigInteger; ShopCode: Code[20]) + begin + SetShop(ShopCode); + GetMetafieldDefinitions(ParentTableNo, OwnerId); + end; + /// /// Retrieves the metafield definitions from Shopify. /// @@ -186,7 +227,7 @@ codeunit 30316 "Shpfy Metafield API" /// /// Table id of the parent resource. /// Id of the parent resource. - internal procedure GetMetafieldDefinitions(ParentTableNo: Integer; OwnerId: BigInteger) + local procedure GetMetafieldDefinitions(ParentTableNo: Integer; OwnerId: BigInteger) var Metafield: Record "Shpfy Metafield"; OwnerType: Enum "Shpfy Metafield Owner Type"; diff --git a/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/ShpfyMetafields.Codeunit.al b/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/ShpfyMetafields.Codeunit.al new file mode 100644 index 0000000000..e96ffd8aae --- /dev/null +++ b/src/Apps/W1/Shopify/App/src/Metafields/Codeunits/ShpfyMetafields.Codeunit.al @@ -0,0 +1,51 @@ +// ------------------------------------------------------------------------------------------------ +// 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.Shopify; + +/// +/// Provides functionality for managing Shopify metafields. +/// +codeunit 30418 "Shpfy Metafields" +{ + Access = Public; + + var + MetafieldAPI: Codeunit "Shpfy Metafield API"; + + /// + /// Retrieves the metafield definitions from Shopify for the specified resource. + /// + /// Table id of the parent resource (e.g., Database::"Shpfy Product"). + /// Id of the parent resource in Shopify. + /// The code of the Shopify shop. + procedure GetMetafieldDefinitions(ParentTableNo: Integer; OwnerId: BigInteger; ShopCode: Code[20]) + begin + MetafieldAPI.GetMetafieldDefinitions(ParentTableNo, OwnerId, ShopCode); + end; + + /// + /// Synchronizes a single metafield to Shopify, creating or updating it as needed. + /// + /// The metafield record to synchronize to Shopify. + /// The code of the Shopify shop. + /// The Shopify Id of the metafield. + procedure SyncMetafieldToShopify(var Metafield: Record "Shpfy Metafield"; ShopCode: Code[20]): BigInteger + begin + exit(MetafieldAPI.SyncMetafieldToShopify(Metafield, ShopCode)); + end; + + /// + /// Synchronizes all metafields for the specified resource to Shopify. + /// Only metafields that have been updated in BC since the last sync will be sent. + /// + /// Table id of the parent resource (e.g., Database::"Shpfy Product"). + /// Id of the parent resource in Shopify. + /// The code of the Shopify shop. + procedure SyncMetafieldsToShopify(ParentTableNo: Integer; OwnerId: BigInteger; ShopCode: Code[20]) + begin + MetafieldAPI.SyncMetafieldsToShopify(ParentTableNo, OwnerId, ShopCode); + end; +} diff --git a/src/Apps/W1/Shopify/App/src/Metafields/Pages/ShpfyMetafields.Page.al b/src/Apps/W1/Shopify/App/src/Metafields/Pages/ShpfyMetafields.Page.al index b19309285b..4b2d5d5ff9 100644 --- a/src/Apps/W1/Shopify/App/src/Metafields/Pages/ShpfyMetafields.Page.al +++ b/src/Apps/W1/Shopify/App/src/Metafields/Pages/ShpfyMetafields.Page.al @@ -78,14 +78,36 @@ page 30163 "Shpfy Metafields" trigger OnAction() var - MetafieldAPI: Codeunit "Shpfy Metafield API"; + Metafields: Codeunit "Shpfy Metafields"; ParentTableNo: Integer; OwnerId: BigInteger; begin Evaluate(ParentTableNo, Rec.GetFilter("Parent Table No.")); Evaluate(OwnerId, Rec.GetFilter("Owner Id")); - MetafieldAPI.SetShop(Shop); - MetafieldAPI.GetMetafieldDefinitions(ParentTableNo, OwnerId); + Metafields.GetMetafieldDefinitions(ParentTableNo, OwnerId, Shop.Code); + end; + } + action(SyncToShopify) + { + ApplicationArea = All; + Caption = 'Sync to Shopify'; + Image = Export; + ToolTip = 'Send the selected metafields to Shopify.'; + Visible = IsPageEditable; + Promoted = true; + PromotedOnly = true; + PromotedCategory = Process; + + trigger OnAction() + var + Metafield: Record "Shpfy Metafield"; + Metafields: Codeunit "Shpfy Metafields"; + begin + CurrPage.SetSelectionFilter(Metafield); + if Metafield.FindSet() then + repeat + Metafields.SyncMetafieldToShopify(Metafield, Shop.Code); + until Metafield.Next() = 0; end; } } @@ -113,18 +135,19 @@ page 30163 "Shpfy Metafields" Rec.TestField(Name); Rec.Validate(Value); - Rec.Id := SendMetafieldToShopify(); + Rec.Id := Metafields.SyncMetafieldToShopify(Rec, Shop.Code); end; trigger OnModifyRecord(): Boolean begin if Rec.Id < 0 then if xRec.Value <> Rec.Value then - Rec.Rename(SendMetafieldToShopify()); + Rec.Rename(Metafields.SyncMetafieldToShopify(Rec, Shop.Code)); end; var Shop: Record "Shpfy Shop"; + Metafields: Codeunit "Shpfy Metafields"; IsPageEditable: Boolean; IsValueEditable: Boolean; @@ -149,29 +172,4 @@ page 30163 "Shpfy Metafields" CurrPage.SetTableView(Metafield); CurrPage.RunModal(); end; - - local procedure SendMetafieldToShopify(): BigInteger - var - JsonHelper: Codeunit "Shpfy Json Helper"; - MetafieldAPI: Codeunit "Shpfy Metafield API"; - UserErrorOnShopifyErr: Label 'Something went wrong while sending the metafield to Shopify. Check Shopify Log Entries for more details.'; - GraphQuery: TextBuilder; - JResponse: JsonToken; - JMetafields: JsonArray; - JUserErrors: JsonArray; - JItem: JsonToken; - begin - MetafieldAPI.SetShop(Shop); - MetafieldAPI.CreateMetafieldQuery(Rec, GraphQuery); - JResponse := MetafieldAPI.UpdateMetafields(GraphQuery.ToText()); - - JsonHelper.GetJsonArray(JResponse, JUserErrors, 'data.metafieldsSet.userErrors'); - - if JUserErrors.Count() = 0 then begin - JsonHelper.GetJsonArray(JResponse, JMetafields, 'data.metafieldsSet.metafields'); - JMetafields.Get(0, JItem); - exit(JsonHelper.GetValueAsBigInteger(JItem, 'legacyResourceId')); - end else - Error(UserErrorOnShopifyErr); - end; } \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Codeunits/ShpfyFulfillmentOrdersAPI.Codeunit.al b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Codeunits/ShpfyFulfillmentOrdersAPI.Codeunit.al index 6982cf5217..4eaf787e78 100644 --- a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Codeunits/ShpfyFulfillmentOrdersAPI.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Codeunits/ShpfyFulfillmentOrdersAPI.Codeunit.al @@ -72,6 +72,7 @@ codeunit 30238 "Shpfy Fulfillment Orders API" internal procedure ExtractFulfillmentOrder(var ShopifyShop: Record "Shpfy Shop"; JFulfillmentOrder: JsonToken; var Cursor: Text) var + DataCapture: Record "Shpfy Data Capture"; FulfillmentOrderHeader: Record "Shpfy FulFillment Order Header"; Id: BigInteger; JNode: JsonObject; @@ -98,6 +99,7 @@ codeunit 30238 "Shpfy Fulfillment Orders API" FulfillmentOrderHeader."Delivery Method Type" := ConvertToDeliveryMethodType(JsonHelper.GetValueAsText(JNode, 'deliveryMethod.methodType')); if not FulfillmentOrderHeader.Insert() then FulfillmentOrderHeader.Modify(); + DataCapture.Add(Database::"Shpfy FulFillment Order Header", FulfillmentOrderHeader.SystemId, JNode); GetFulfillmentOrderLines(ShopifyShop, FulfillmentOrderHeader); end; end; @@ -105,6 +107,7 @@ codeunit 30238 "Shpfy Fulfillment Orders API" internal procedure ExtractFulfillmentOrderLines(var ShopifyShop: Record "Shpfy Shop"; var FulfillmentOrderHeader: Record "Shpfy FulFillment Order Header"; JResponse: JsonObject; var Cursor: Text): Boolean var FulfillmentOrderLine: Record "Shpfy FulFillment Order Line"; + DataCapture: Record "Shpfy Data Capture"; Modified: Boolean; Id: BigInteger; JFulfillmentOrderLines: JsonArray; @@ -165,6 +168,7 @@ codeunit 30238 "Shpfy Fulfillment Orders API" end; end; end; + DataCapture.Add(Database::"Shpfy FulFillment Order Header", FulfillmentOrderHeader.SystemId, JFulfillmentOrderLines.AsToken()); exit(true); end; end; diff --git a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulFillmentOrders.Page.al b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulFillmentOrders.Page.al index 1e3ca5ca29..db17b9495f 100644 --- a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulFillmentOrders.Page.al +++ b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulFillmentOrders.Page.al @@ -59,4 +59,39 @@ page 30141 "Shpfy Fulfillment Orders" } } } + + actions + { + area(navigation) + { + action("Retrieved Shopify Data") + { + ApplicationArea = All; + Caption = 'Retrieved Shopify Data'; + Image = Entry; + ToolTip = 'View the data retrieved from Shopify.'; + + trigger OnAction(); + var + DataCapture: Record "Shpfy Data Capture"; + begin + DataCapture.SetCurrentKey("Linked To Table", "Linked To Id"); + DataCapture.SetRange("Linked To Table", Database::"Shpfy FulFillment Order Header"); + DataCapture.SetRange("Linked To Id", Rec.SystemId); + Page.Run(Page::"Shpfy Data Capture List", DataCapture); + end; + } + } + area(promoted) + { + group(Category_Inspect) + { + Caption = 'Inspect'; + + actionref("Retrieved Shopify Data_Promoted"; "Retrieved Shopify Data") + { + } + } + } + } } \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulfillmentOrderCard.Page.al b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulfillmentOrderCard.Page.al index 6aff17021e..be890e0635 100644 --- a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulfillmentOrderCard.Page.al +++ b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Pages/ShpfyFulfillmentOrderCard.Page.al @@ -64,4 +64,39 @@ page 30140 "Shpfy Fulfillment Order Card" } } } + + actions + { + area(navigation) + { + action("Retrieved Shopify Data") + { + ApplicationArea = All; + Caption = 'Retrieved Shopify Data'; + Image = Entry; + ToolTip = 'View the data retrieved from Shopify.'; + + trigger OnAction(); + var + DataCapture: Record "Shpfy Data Capture"; + begin + DataCapture.SetCurrentKey("Linked To Table", "Linked To Id"); + DataCapture.SetRange("Linked To Table", Database::"Shpfy FulFillment Order Header"); + DataCapture.SetRange("Linked To Id", Rec.SystemId); + Page.Run(Page::"Shpfy Data Capture List", DataCapture); + end; + } + } + area(promoted) + { + group(Category_Inspect) + { + Caption = 'Inspect'; + + actionref("Retrieved Shopify Data_Promoted"; "Retrieved Shopify Data") + { + } + } + } + } } \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Tables/ShpfyFulFillmentOrderHeader.Table.al b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Tables/ShpfyFulFillmentOrderHeader.Table.al index 016bc975b4..5984f0c50c 100644 --- a/src/Apps/W1/Shopify/App/src/Order Fulfillments/Tables/ShpfyFulFillmentOrderHeader.Table.al +++ b/src/Apps/W1/Shopify/App/src/Order Fulfillments/Tables/ShpfyFulFillmentOrderHeader.Table.al @@ -81,9 +81,16 @@ table 30143 "Shpfy FulFillment Order Header" trigger OnDelete() var FulfillmentOrderLine: Record "Shpfy FulFillment Order Line"; + DataCapture: Record "Shpfy Data Capture"; begin FulfillmentOrderLine.Reset(); FulfillmentOrderLine.SetRange("Shopify Fulfillment Order Id", Rec."Shopify Fulfillment Order Id"); FulfillmentOrderLine.DeleteAll(true); + + DataCapture.SetCurrentKey("Linked To Table", "Linked To Id"); + DataCapture.SetRange("Linked To Table", Database::"Shpfy FulFillment Order Header"); + DataCapture.SetRange("Linked To Id", Rec.SystemId); + if not DataCapture.IsEmpty then + DataCapture.DeleteAll(false); end; } \ No newline at end of file diff --git a/src/Apps/W1/Shopify/App/src/Order handling/Pages/ShpfyOrderSubform.Page.al b/src/Apps/W1/Shopify/App/src/Order handling/Pages/ShpfyOrderSubform.Page.al index 072c4e66a1..17360472d0 100644 --- a/src/Apps/W1/Shopify/App/src/Order handling/Pages/ShpfyOrderSubform.Page.al +++ b/src/Apps/W1/Shopify/App/src/Order handling/Pages/ShpfyOrderSubform.Page.al @@ -5,6 +5,8 @@ namespace Microsoft.Integration.Shopify; +using Microsoft.Inventory.Item; + /// /// Page Shpfy Order Subform (ID 30122). /// @@ -41,6 +43,17 @@ page 30122 "Shpfy Order Subform" ApplicationArea = All; ShowMandatory = true; ToolTip = 'Specifies the item number.'; + + trigger OnValidate() + var + Item: Record Item; + begin + if Item.Get(Rec."Item No.") then + if Item."Sales Unit of Measure" <> '' then + Rec."Unit of Measure Code" := Item."Sales Unit of Measure" + else + Rec."Unit of Measure Code" := Item."Base Unit of Measure"; + end; } field(UnitOfMeasureCode; Rec."Unit of Measure Code") { diff --git a/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al b/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al index 1632e349ef..ad135db30b 100644 --- a/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al +++ b/src/Apps/W1/Shopify/App/src/PermissionSets/ShpfyObjects.PermissionSet.al @@ -299,6 +299,7 @@ permissionset 30104 "Shpfy - Objects" codeunit "Shpfy Log Entries Delete" = X, codeunit "Shpfy Math" = X, codeunit "Shpfy Metafield API" = X, + codeunit "Shpfy Metafields" = X, codeunit "Shpfy Metafield Owner Company" = X, codeunit "Shpfy Metafield Owner Customer" = X, codeunit "Shpfy Metafield Owner Product" = X, diff --git a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductEvents.Codeunit.al b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductEvents.Codeunit.al index 075ac8013c..e6ae2b3a5d 100644 --- a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductEvents.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductEvents.Codeunit.al @@ -82,6 +82,15 @@ codeunit 30177 "Shpfy Product Events" begin end; + /// + /// Raised before updating product metafields in Shopify. + /// + /// The Shopify product Id. + [IntegrationEvent(false, false)] + internal procedure OnBeforeUpdateProductMetafields(ProductId: BigInteger) + begin + end; + #pragma warning disable AS0027 /// /// Raised After Find Item Template. diff --git a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al index 3312f04bdb..87df66b2ec 100644 --- a/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al +++ b/src/Apps/W1/Shopify/App/src/Products/Codeunits/ShpfyProductExport.Codeunit.al @@ -68,7 +68,6 @@ codeunit 30178 "Shpfy Product Export" ProductEvents: Codeunit "Shpfy Product Events"; ProductPriceCalc: Codeunit "Shpfy Product Price Calc."; VariantApi: Codeunit "Shpfy Variant API"; - MetafieldAPI: Codeunit "Shpfy Metafield API"; SkippedRecord: Codeunit "Shpfy Skipped Record"; OnlyUpdatePrice: Boolean; RecordCount: Integer; @@ -578,7 +577,6 @@ codeunit 30178 "Shpfy Product Export" ProductApi.SetShop(Shop); VariantApi.SetShop(Shop); ProductPriceCalc.SetShop(Shop); - MetafieldAPI.SetShop(Shop); end; /// @@ -765,17 +763,20 @@ codeunit 30178 "Shpfy Product Export" local procedure UpdateMetafields(ProductId: BigInteger) var ShpfyVariant: Record "Shpfy Variant"; + Metafields: Codeunit "Shpfy Metafields"; begin if OnlyUpdatePrice then exit; - MetafieldAPI.CreateOrUpdateMetafieldsInShopify(Database::"Shpfy Product", ProductId); + ProductEvents.OnBeforeUpdateProductMetafields(ProductId); + + Metafields.SyncMetafieldsToShopify(Database::"Shpfy Product", ProductId, Shop.Code); ShpfyVariant.SetRange("Product Id", ProductId); ShpfyVariant.ReadIsolation := IsolationLevel::ReadCommitted; if ShpfyVariant.FindSet() then repeat - MetafieldAPI.CreateOrUpdateMetafieldsInShopify(Database::"Shpfy Variant", ShpfyVariant.Id); + Metafields.SyncMetafieldsToShopify(Database::"Shpfy Variant", ShpfyVariant.Id, Shop.Code); until ShpfyVariant.Next() = 0; end; diff --git a/src/Apps/W1/Shopify/extensibility_examples.md b/src/Apps/W1/Shopify/extensibility_examples.md deleted file mode 100644 index 6f62fb0b86..0000000000 --- a/src/Apps/W1/Shopify/extensibility_examples.md +++ /dev/null @@ -1,3 +0,0 @@ -# Extensibility examples - -Extensibility examples are now moved to [Extend the Shopify Connector](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-extending-shopify). \ No newline at end of file diff --git a/src/System Application/App/Agent/Interaction/Internal/AgentTaskLogEntryList.Page.al b/src/System Application/App/Agent/Interaction/Internal/AgentTaskLogEntryList.Page.al index 501895ecd0..a4690d4341 100644 --- a/src/System Application/App/Agent/Interaction/Internal/AgentTaskLogEntryList.Page.al +++ b/src/System Application/App/Agent/Interaction/Internal/AgentTaskLogEntryList.Page.al @@ -58,7 +58,7 @@ page 4303 "Agent Task Log Entry List" field("User Full Name"; Rec."User Full Name") { Caption = 'User Full Name'; - Tooltip = 'Specifies the full name of the user that was involved in performing the step..'; + Tooltip = 'Specifies the full name of the user that was involved in performing the step.'; } field(Description; Rec.Description) { diff --git a/src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al b/src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al index 7fe0560367..03fc1f0402 100644 --- a/src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al +++ b/src/System Application/App/Agent/Permissions/AgentObjects.PermissionSet.al @@ -11,7 +11,6 @@ permissionset 4300 "Agent - Objects" Assignable = false; Permissions = codeunit "Agent Task Impl." = X, - page "Agent Access Control" = X, page "Agent Card" = X, page "Agent List" = X, page "Agent Task List" = X, diff --git a/src/System Application/App/Agent/Setup/AccessControls/AgentUserSubform.PageExt.al b/src/System Application/App/Agent/Setup/AccessControls/AgentUserSubform.PageExt.al new file mode 100644 index 0000000000..9bc2e947e9 --- /dev/null +++ b/src/System Application/App/Agent/Setup/AccessControls/AgentUserSubform.PageExt.al @@ -0,0 +1,66 @@ +#if not CLEAN28 +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Agents; + +using System.Security.AccessControl; +using System.Security.User; + +pageextension 4318 "Agent User Subform" extends "User Subform" +{ + ObsoleteState = Pending; + ObsoleteReason = 'The Agent User Subform page extension is obsolete. Use the View Agent Access Control page instead.'; + ObsoleteTag = '28.0'; + + layout + { + modify(Company) + { + Visible = (not IsAgent) or ShowCompanyField; + } + } + + actions + { + addlast(Processing) + { + action(AgentShowHideCompany) + { + ApplicationArea = All; + Caption = 'Show/hide company'; + Enabled = IsAgent; + Image = CompanyInformation; + ToolTip = 'Show or hide the company name.'; + Visible = IsAgent; + + ObsoleteState = Pending; + ObsoleteReason = 'The Agent User Subform page extension is obsolete. Use the View Agent Access Control page instead.'; + ObsoleteTag = '28.0'; + + trigger OnAction() + begin + ShowCompanyField := not ShowCompanyField; + CurrPage.Update(false); + end; + } + } + } + + trigger OnAfterGetRecord() + var + User: Record User; + begin + if User.Get(Rec."User Security ID") then + IsAgent := User."License Type" = User."License Type"::Agent + else + IsAgent := false; + end; + + var + IsAgent: Boolean; + ShowCompanyField: Boolean; +} +#endif \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/AccessControls/SelectAgentPermissions.Page.al b/src/System Application/App/Agent/Setup/AccessControls/SelectAgentPermissions.Page.al new file mode 100644 index 0000000000..23dc569676 --- /dev/null +++ b/src/System Application/App/Agent/Setup/AccessControls/SelectAgentPermissions.Page.al @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Agents; + +using System.Security.AccessControl; + +page 4336 "Select Agent Permissions" +{ + PageType = StandardDialog; + ApplicationArea = All; + SourceTable = "Access Control Buffer"; + SourceTableTemporary = true; + Caption = 'Edit Agent Permissions'; + Extensible = false; + DataCaptionExpression = ''; + InherentPermissions = X; + InherentEntitlements = X; + + layout + { + area(Content) + { + label(Info) + { + Caption = 'Agent tasks use permissions shared by both the agent and the task creator/approver.'; + } + part(PermissionsPart; "Select Agent Permissions Part") + { + Caption = 'Agent permissions'; + ApplicationArea = All; + } + } + } + + trigger OnOpenPage() + begin + BackupAccessControlBuffer(); + CurrPage.PermissionsPart.Page.Initialize(AgentUserSecurityID, Rec); + CurrPage.PermissionsPart.Page.Update(false); + end; + + trigger OnQueryClosePage(CloseAction: Action): Boolean + begin + if CloseAction = CloseAction::Cancel then + RestoreAccessControlBuffer(); + + exit(true); + end; + + internal procedure Initialize(NewAgentUserSecurityID: Guid; var TempAccessControlBuffer: Record "Access Control Buffer" temporary) + begin + AgentUserSecurityID := NewAgentUserSecurityID; + Rec.Copy(TempAccessControlBuffer, true); + end; + + internal procedure GetTempAccessControlBuffer(var TempAccessControlBuffer: Record "Access Control Buffer" temporary) + begin + TempAccessControlBuffer.Copy(Rec, true); + end; + + local procedure BackupAccessControlBuffer() + begin + TempBackupAccessControlBuffer.Reset(); + TempBackupAccessControlBuffer.DeleteAll(); + + Rec.Reset(); + if not Rec.FindSet() then + exit; + + repeat + TempBackupAccessControlBuffer.TransferFields(Rec); + TempBackupAccessControlBuffer.Insert(); + until Rec.Next() = 0; + end; + + local procedure RestoreAccessControlBuffer() + begin + Rec.Reset(); + Rec.DeleteAll(); + + TempBackupAccessControlBuffer.Reset(); + if not TempBackupAccessControlBuffer.FindSet() then + exit; + + repeat + Rec.TransferFields(TempBackupAccessControlBuffer); + Rec.Insert(); + until TempBackupAccessControlBuffer.Next() = 0; + end; + + var + TempBackupAccessControlBuffer: Record "Access Control Buffer" temporary; + AgentUserSecurityID: Guid; +} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/AccessControls/SelectAgentPermissionsPart.Page.al b/src/System Application/App/Agent/Setup/AccessControls/SelectAgentPermissionsPart.Page.al new file mode 100644 index 0000000000..f6b5957a23 --- /dev/null +++ b/src/System Application/App/Agent/Setup/AccessControls/SelectAgentPermissionsPart.Page.al @@ -0,0 +1,224 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Agents; + +using System.Security.AccessControl; + +page 4340 "Select Agent Permissions Part" +{ + PageType = ListPart; + ApplicationArea = All; + SourceTable = "Access Control Buffer"; + SourceTableTemporary = true; + Caption = 'Agent permissions'; + MultipleNewLines = false; + Editable = true; + DataCaptionExpression = ''; + InherentEntitlements = X; + InherentPermissions = X; + + layout + { + area(Content) + { + repeater(Main) + { + Caption = 'Agent permissions'; + + field(PermissionSet; Rec."Role ID") + { + ApplicationArea = Basic, Suite; + Caption = 'Permission Set'; + ToolTip = 'Specifies the ID of a security role that has been assigned to this Windows login in the current database.'; + Style = Unfavorable; + StyleExpr = PermissionSetNotFound; + NotBlank = true; + + trigger OnLookup(var Text: Text): Boolean + var + LookupPermissionSet: Page "Lookup Permission Set"; + begin + LookupPermissionSet.LookupMode := true; + if LookupPermissionSet.RunModal() = ACTION::LookupOK then begin + LookupPermissionSet.GetRecord(PermissionSetLookupRecord); + Text := PermissionSetLookupRecord."Role ID"; + PermissionSetLookupRecord.SetRecFilter(); + exit(true); + end; + end; + + trigger OnValidate() + begin + PermissionSetLookupRecord.SetRange("Role ID", Rec."Role ID"); + if PermissionSetLookupRecord.FindFirst() then begin + if PermissionSetLookupRecord.Count() > 1 then + Error(MultipleRoleIDErr, Rec."Role ID"); + + PermissionSetNotFound := false; + Rec.Scope := PermissionSetLookupRecord.Scope; + Rec."App ID" := PermissionSetLookupRecord."App ID"; + PermissionScope := Format(PermissionSetLookupRecord.Scope); + PermissionAppName := PermissionSetLookupRecord."App Name"; + PermissionRoleName := PermissionSetLookupRecord.Name; + end; + + PermissionSetLookupRecord.Reset(); + end; + } + field(Description; PermissionRoleName) + { + ApplicationArea = Basic, Suite; + Caption = 'Description'; + DrillDown = false; + Editable = false; + ToolTip = 'Specifies the name of the security role that has been given to this Windows login in the current database.'; + } + field(Company; Rec."Company Name") + { + ApplicationArea = Basic, Suite; + Caption = 'Company'; + ToolTip = 'Specifies the name of the company that this role is limited to for this Windows login.'; + Visible = ShowCompanyField; + } + field(ExtensionName; PermissionAppName) + { + ApplicationArea = Basic, Suite; + Caption = 'Extension Name'; + DrillDown = false; + Editable = false; + ToolTip = 'Specifies the name of the extension.'; + } + field(PermissionScope; PermissionScope) + { + ApplicationArea = Basic, Suite; + Caption = 'Permission Scope'; + Editable = false; + ToolTip = 'Specifies the scope of the permission set.'; + } + } + } + } + + actions + { + area(Processing) + { + action(AgentShowHideCompany) + { + ApplicationArea = All; + Caption = 'Show/hide company'; + Image = CompanyInformation; + ToolTip = 'Show or hide the company name.'; + + trigger OnAction() + begin + if (not ShowCompanyField and (GlobalSingleCompanyName <> '')) then + // A confirmation dialog is raised when the user shows the company field + // for an agent that operates in a single company. + if not Confirm(ShowSingleCompanyQst, false) then + exit; + + ShowCompanyFieldOverride := true; + ShowCompanyField := not ShowCompanyField; + CurrPage.Update(false); + end; + } + } + } + + trigger OnOpenPage() + var + AgentUtilities: Codeunit "Agent Utilities"; + begin + AgentUtilities.BlockPageFromBeingOpenedByAgent(); + end; + + trigger OnAfterGetRecord() + begin + UpdateGlobalVariables(); + end; + + trigger OnNewRecord(BelowxRec: Boolean) + begin + PermissionSetNotFound := false; + PermissionAppName := ''; + PermissionRoleName := ''; + PermissionScope := ''; + end; + + trigger OnInsertRecord(BelowxRec: Boolean): Boolean + begin + HandleCompanyNameOnInsert(); + end; + + internal procedure Initialize(NewAgentUserSecurityID: Guid; var TempAccessControlBuffer: Record "Access Control Buffer" temporary) + var + AgentSingleCompany: Boolean; + begin + AgentUserSecurityID := NewAgentUserSecurityID; + Rec.Copy(TempAccessControlBuffer, true); + + AgentSingleCompany := GetAccessControlForSingleCompany(GlobalSingleCompanyName); + if not ShowCompanyFieldOverride then + ShowCompanyField := not AgentSingleCompany; + end; + + local procedure HandleCompanyNameOnInsert() + begin + if GlobalSingleCompanyName <> '' then begin + if (Rec."Company Name" = '') and not ShowCompanyField then + // Default the company name for the inserted record when all these conditions are met: + // 1. The agent operates in a single company, + // 2. The company name is not explicit specified, + // 3. The user didn't toggle to view the company name field on permissions. + Rec."Company Name" := GlobalSingleCompanyName; + + if (Rec."Company Name" <> GlobalSingleCompanyName) then + // The agent used to operation in a single company, but operates in multiple ones now. + // Ideally, other scenarios should also trigger an update (delete, modify), but insert + // was identified as the main one. + GlobalSingleCompanyName := ''; + end; + end; + + local procedure UpdateGlobalVariables() + begin + if PermissionSetLookupRecord.Get(Rec.Scope, Rec."App ID", Rec."Role ID") then begin + PermissionSetNotFound := false; + PermissionAppName := PermissionSetLookupRecord."App Name"; + PermissionRoleName := PermissionSetLookupRecord.Name; + PermissionScope := Format(PermissionSetLookupRecord.Scope); + end + else begin + PermissionSetNotFound := true; + PermissionAppName := ''; + PermissionRoleName := ''; + PermissionScope := ''; + end; + + if not ShowCompanyFieldOverride then begin + ShowCompanyField := not GetAccessControlForSingleCompany(GlobalSingleCompanyName); + CurrPage.Update(false); + end; + end; + + protected procedure GetAccessControlForSingleCompany(var SingleCompanyName: Text[30]): Boolean + var + AgentImpl: Codeunit "Agent Impl."; + begin + exit(AgentImpl.GetAccessControlForSingleCompany(AgentUserSecurityID, SingleCompanyName)); + end; + + var + PermissionSetLookupRecord: Record "Aggregate Permission Set"; + AgentUserSecurityID: Guid; + PermissionScope, PermissionAppName, PermissionRoleName : Text; + PermissionSetNotFound: Boolean; + ShowCompanyField, ShowCompanyFieldOverride : Boolean; + GlobalSingleCompanyName: Text[30]; + MultipleRoleIDErr: Label 'The permission set %1 is defined multiple times in this context. Use the lookup button to select the relevant permission set.', Comment = '%1 will be replaced with a Role ID code value from the Permission Set table'; + ShowSingleCompanyQst: Label 'This agent currently has permissions in only one company. By showing the Company field, you will be able to assign permissions in other companies, making the agent available there. The agent may not have been designed to work cross companies.\\Do you want to continue?'; +} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/ViewAgentPermissions.Page.al b/src/System Application/App/Agent/Setup/AccessControls/ViewAgentPermissions.Page.al similarity index 75% rename from src/System Application/App/Agent/Setup/ViewAgentPermissions.Page.al rename to src/System Application/App/Agent/Setup/AccessControls/ViewAgentPermissions.Page.al index 6f7dd0f708..c4d7bfcb2f 100644 --- a/src/System Application/App/Agent/Setup/ViewAgentPermissions.Page.al +++ b/src/System Application/App/Agent/Setup/AccessControls/ViewAgentPermissions.Page.al @@ -5,17 +5,16 @@ namespace System.Agents; -using System.Environment; -using System.Environment.Configuration; using System.Security.AccessControl; - page 4334 "View Agent Permissions" { + ApplicationArea = All; Caption = 'Agent Permission Sets'; PageType = ListPart; SourceTable = "Access Control"; Editable = false; + Extensible = false; InsertAllowed = false; DeleteAllowed = false; ModifyAllowed = false; @@ -85,6 +84,8 @@ page 4334 "View Agent Permissions" trigger OnAction() var Agent: Record Agent; + TempAccessControlBuffer: Record "Access Control Buffer" temporary; + AgentImpl: Codeunit "Agent Impl."; SelectAgentPermissions: Page "Select Agent Permissions"; begin if not Agent.Get(Rec."User Security ID") then @@ -99,8 +100,14 @@ page 4334 "View Agent Permissions" Commit(); end; - SelectAgentPermissions.SetRecord(Agent); - SelectAgentPermissions.RunModal(); + CopyAccessControlToBuffer(Rec."User Security ID", TempAccessControlBuffer); + + SelectAgentPermissions.Initialize(Rec."User Security ID", TempAccessControlBuffer); + if SelectAgentPermissions.RunModal() = Action::OK then begin + SelectAgentPermissions.GetTempAccessControlBuffer(TempAccessControlBuffer); + AgentImpl.AssignPermissionSets(Rec."User Security ID", TempAccessControlBuffer); + end; + CurrPage.Update(false); end; } @@ -125,6 +132,8 @@ page 4334 "View Agent Permissions" trigger OnAfterGetRecord() var AggregatePermissionSet: Record "Aggregate Permission Set"; + AgentImpl: Codeunit "Agent Impl."; + GlobalSingleCompanyName: Text[30]; begin PermissionScope := Format(Rec.Scope); @@ -133,22 +142,30 @@ page 4334 "View Agent Permissions" PermissionSetNotFound := not AggregatePermissionSet.Get(Rec.Scope, Rec."App ID", Rec."Role ID"); if not ShowCompanyFieldOverride then begin - ShowCompanyField := not AccessControlForSingleCompany(GlobalSingleCompanyName); + ShowCompanyField := not AgentImpl.GetAccessControlForSingleCompany(Rec."User Security ID", GlobalSingleCompanyName); CurrPage.Update(false); end; end; - local procedure AccessControlForSingleCompany(var SingleCompanyName: Text[30]): Boolean + local procedure CopyAccessControlToBuffer(UserSecurityID: Guid; var TempAccessControlBuffer: Record "Access Control Buffer" temporary) var - TempCompany: Record Company temporary; - UserSettings: Codeunit "User Settings"; + AccessControl: Record "Access Control"; begin - UserSettings.GetAllowedCompaniesForUser(Rec."User Security ID", TempCompany); - if TempCompany.Count() <> 1 then - exit(false); - - SingleCompanyName := TempCompany.Name; - exit(true); + TempAccessControlBuffer.Reset(); + TempAccessControlBuffer.DeleteAll(); + + AccessControl.SetRange("User Security ID", UserSecurityID); + if not AccessControl.FindSet() then + exit; + + repeat + Clear(TempAccessControlBuffer); + TempAccessControlBuffer."Company Name" := AccessControl."Company Name"; + TempAccessControlBuffer.Scope := AccessControl.Scope; + TempAccessControlBuffer."App ID" := AccessControl."App ID"; + TempAccessControlBuffer."Role ID" := AccessControl."Role ID"; + TempAccessControlBuffer.Insert(); + until AccessControl.Next() = 0; end; var @@ -156,6 +173,5 @@ page 4334 "View Agent Permissions" ShowCompanyFieldOverride: Boolean; PermissionScope: Text; PermissionSetNotFound: Boolean; - GlobalSingleCompanyName: Text[30]; DeactivateAgentToEditPermissionsQst: Label 'Permissions can only be edited for inactive agents. Do you want to make the agent inactive now?'; } \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/AgentAccessControl.Page.al b/src/System Application/App/Agent/Setup/AgentAccessControl.Page.al deleted file mode 100644 index 38d0ce06ff..0000000000 --- a/src/System Application/App/Agent/Setup/AgentAccessControl.Page.al +++ /dev/null @@ -1,141 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.Agents; - -using System.Security.AccessControl; - -page 4320 "Agent Access Control" -{ - PageType = ListPart; - ApplicationArea = All; - SourceTable = "Agent Access Control"; - Caption = 'Agent Access Control'; - MultipleNewLines = false; - Extensible = false; - InherentEntitlements = X; - InherentPermissions = X; - - layout - { - area(Content) - { - repeater(Main) - { - field(UserName; UserName) - { - Caption = 'User Name'; - ToolTip = 'Specifies the name of the User that can access the agent.'; - TableRelation = User where("License Type" = filter(<> Application & <> "Windows Group" & <> Agent)); - - trigger OnValidate() - begin - ValidateUserName(UserName); - end; - } - field(UserFullName; UserFullName) - { - Caption = 'User Full Name'; - ToolTip = 'Specifies the Full Name of the User that can access the agent.'; - Editable = false; - } - field(CanConfigureAgent; Rec."Can Configure Agent") - { - Caption = 'Can Configure'; - Tooltip = 'Specifies whether the user can configure the agent.'; - - trigger OnValidate() - var - AgentImpl: Codeunit "Agent Impl."; - begin - if not Rec."Can Configure Agent" then - AgentImpl.VerifyOwnerExists(Rec); - end; - } - } - } - } - - trigger OnOpenPage() - var - AgentUtilities: Codeunit "Agent Utilities"; - begin - AgentUtilities.BlockPageFromBeingOpenedByAgent(); - end; - - trigger OnAfterGetRecord() - begin - UpdateGlobalVariables(); - end; - - trigger OnAfterGetCurrRecord() - begin - UpdateGlobalVariables(); - end; - - trigger OnDeleteRecord(): Boolean - var - AgentImpl: Codeunit "Agent Impl."; - begin - AgentImpl.VerifyOwnerExists(Rec); - end; - - local procedure ValidateUserName(NewUserName: Text) - var - User: Record "User"; - UserGuid: Guid; - begin - if Evaluate(UserGuid, NewUserName) then begin - User.Get(UserGuid); - UpdateUser(User."User Security ID"); - UpdateGlobalVariables(); - exit; - end; - - User.SetRange("User Name", NewUserName); - if not User.FindFirst() then begin - User.SetFilter("User Name", '@*''''' + NewUserName + '''''*'); - User.FindFirst(); - end; - - UpdateUser(User."User Security ID"); - UpdateGlobalVariables(); - end; - - local procedure UpdateUser(NewUserID: Guid) - var - RecordExists: Boolean; - begin - RecordExists := Rec.Find(); - - if RecordExists then - Error(CannotUpdateUserErr); - - Rec."User Security ID" := NewUserID; - Rec.Insert(true); - end; - - local procedure UpdateGlobalVariables() - var - User: Record "User"; - begin - Clear(UserFullName); - Clear(UserName); - - if IsNullGuid(Rec."User Security ID") then - exit; - - if not User.Get(Rec."User Security ID") then - exit; - - UserName := User."User Name"; - UserFullName := User."Full Name"; - end; - - var - UserFullName: Text[80]; - UserName: Code[50]; - CannotUpdateUserErr: Label 'You cannot change the User. Delete and create the entry again.'; -} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/AgentAccessControls/SelectAgentAccessControl.Page.al b/src/System Application/App/Agent/Setup/AgentAccessControls/SelectAgentAccessControl.Page.al new file mode 100644 index 0000000000..b42bee6285 --- /dev/null +++ b/src/System Application/App/Agent/Setup/AgentAccessControls/SelectAgentAccessControl.Page.al @@ -0,0 +1,91 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Agents; + +page 4321 "Select Agent Access Control" +{ + PageType = StandardDialog; + ApplicationArea = All; + SourceTable = "Agent Access Control"; + SourceTableTemporary = true; + Caption = 'Select users to manage tasks and configure the agent'; + Extensible = false; + DataCaptionExpression = ''; + InherentEntitlements = X; + InherentPermissions = X; + + layout + { + area(Content) + { + part(AccessControlPart; "Select Agent Access Ctrl Part") + { + Caption = 'Agent Access Control'; + ApplicationArea = All; + } + } + } + + trigger OnOpenPage() + begin + BackupAgentAccessControl(); + CurrPage.AccessControlPart.Page.Initialize(AgentUserSecurityID, Rec); + CurrPage.AccessControlPart.Page.Update(false); + end; + + trigger OnQueryClosePage(CloseAction: Action): Boolean + begin + if CloseAction = CloseAction::Cancel then + RestoreAgentAccessControl(); + + exit(true); + end; + + internal procedure Initialize(NewAgentUserSecurityID: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary) + begin + AgentUserSecurityID := NewAgentUserSecurityID; + Rec.Copy(TempAgentAccessControl, true); + end; + + internal procedure GetTempAgentAccessControl(var TempAgentAccessControl: Record "Agent Access Control" temporary) + begin + TempAgentAccessControl.Copy(Rec, true); + end; + + local procedure BackupAgentAccessControl() + begin + TempBackupAgentAccessControl.Reset(); + TempBackupAgentAccessControl.DeleteAll(); + + Rec.Reset(); + if not Rec.FindSet() then + exit; + + repeat + TempBackupAgentAccessControl.TransferFields(Rec); + TempBackupAgentAccessControl.Insert(); + until Rec.Next() = 0; + end; + + local procedure RestoreAgentAccessControl() + begin + Rec.Reset(); + Rec.DeleteAll(); + + TempBackupAgentAccessControl.Reset(); + if not TempBackupAgentAccessControl.FindSet() then + exit; + + repeat + Rec.TransferFields(TempBackupAgentAccessControl); + Rec.Insert(); + until TempBackupAgentAccessControl.Next() = 0; + end; + + var + TempBackupAgentAccessControl: Record "Agent Access Control" temporary; + AgentUserSecurityID: Guid; +} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/AgentAccessControls/SelectAgentAccessCtrlPart.Page.al b/src/System Application/App/Agent/Setup/AgentAccessControls/SelectAgentAccessCtrlPart.Page.al new file mode 100644 index 0000000000..52a83dba5f --- /dev/null +++ b/src/System Application/App/Agent/Setup/AgentAccessControls/SelectAgentAccessCtrlPart.Page.al @@ -0,0 +1,246 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Agents; + +using System.Security.AccessControl; + +page 4338 "Select Agent Access Ctrl Part" +{ + PageType = ListPart; + ApplicationArea = All; + SourceTable = "Agent Access Control"; + SourceTableTemporary = true; + Caption = 'Agent Access Control'; + MultipleNewLines = false; + Extensible = false; + InherentEntitlements = X; + InherentPermissions = X; + + layout + { + area(Content) + { + repeater(Main) + { + field(UserName; UserName) + { + Caption = 'User Name'; + ToolTip = 'Specifies the name of the User that can access the agent.'; + TableRelation = User where("License Type" = filter(<> Application & <> "Windows Group" & <> Agent)); + NotBlank = true; + + trigger OnValidate() + begin + ValidateUserName(UserName); + end; + } + field(UserFullName; UserFullName) + { + Caption = 'User Full Name'; + ToolTip = 'Specifies the Full Name of the User that can access the agent.'; + Editable = false; + } + field(Company; Rec."Company Name") + { + Caption = 'Company'; + ToolTip = 'Specifies the company in which the user has access to the agent.'; + Visible = ShowCompanyField; + } + field(CanConfigureAgent; Rec."Can Configure Agent") + { + Caption = 'Can Configure'; + Tooltip = 'Specifies whether the user can configure the agent.'; + + trigger OnValidate() + begin + if not Rec."Can Configure Agent" then + VerifyOwnerExists(); + end; + } + } + } + } + + actions + { + area(Processing) + { + action(AgentShowHideCompany) + { + ApplicationArea = All; + Caption = 'Show/hide company'; + Image = CompanyInformation; + ToolTip = 'Show or hide the company name.'; + + trigger OnAction() + begin + if (not ShowCompanyField) and (GlobalSingleCompanyName <> '') then + // A confirmation dialog is raised when the user shows the company field + // for an agent that operates in a single company. + if not Confirm(ShowSingleCompanyQst, false) then + exit; + + ShowCompanyFieldOverride := true; + ShowCompanyField := not ShowCompanyField; + CurrPage.Update(false); + end; + } + } + } + + trigger OnOpenPage() + var + AgentUtilities: Codeunit "Agent Utilities"; + begin + AgentUtilities.BlockPageFromBeingOpenedByAgent(); + end; + + trigger OnAfterGetRecord() + begin + UpdateGlobalVariables(); + end; + + trigger OnDeleteRecord(): Boolean + begin + if Rec."Can Configure Agent" then + VerifyOwnerExists(); + exit(true); + end; + + trigger OnInsertRecord(BelowxRec: Boolean): Boolean + begin + exit(HandleCompanyNameOnInsert()); + end; + + internal procedure Initialize(NewAgentUserSecurityId: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary) + var + AgentSingleCompany: Boolean; + begin + AgentUserSecurityID := NewAgentUserSecurityId; + Rec.Copy(TempAgentAccessControl, true); + + AgentSingleCompany := AgentImpl.GetAccessControlForSingleCompany(AgentUserSecurityID, GlobalSingleCompanyName); + if not ShowCompanyFieldOverride then + ShowCompanyField := not AgentSingleCompany; + end; + + local procedure HandleCompanyNameOnInsert(): Boolean + var + UserSecurityID: Guid; + begin + // If UserName is already populated (e.g., when duplicating a row), validate it to set User Security ID. + if (UserName <> '') and IsNullGuid(Rec."User Security ID") then + if FindUserByName(UserName, UserSecurityID) then + Rec."User Security ID" := UserSecurityID; + + if IsNullGuid(Rec."User Security ID") then + exit(true); + + Rec."Agent User Security ID" := AgentUserSecurityID; + + if (Rec."Company Name" = '') and not ShowCompanyField then + if (GlobalSingleCompanyName <> '') then + // Default to the single company used by the agent if the company field is not shown. + Rec."Company Name" := GlobalSingleCompanyName + else +#pragma warning disable AA0139 + // Default to the current company used by the agent if the company field is not shown and the agent operates in multiple ones. + Rec."Company Name" := CompanyName(); +#pragma warning restore AA0139 + + exit(true); + end; + + local procedure UpdateGlobalVariables() + var + User: Record "User"; + begin + Clear(UserFullName); + Clear(UserName); + + if IsNullGuid(Rec."User Security ID") then + exit; + + if not User.Get(Rec."User Security ID") then + exit; + + UserName := User."User Name"; + UserFullName := User."Full Name"; + end; + + local procedure ValidateUserName(NewUserName: Text) + var + UserSecurityID: Guid; + begin + if not FindUserByName(NewUserName, UserSecurityID) then + exit; + + Rec.Validate("User Security ID", UserSecurityID); + UpdateGlobalVariables(); + end; + + local procedure FindUserByName(NewUserName: Text; var UserSecurityID: Guid): Boolean + var + User: Record User; + UserGuid: Guid; + begin + if Evaluate(UserGuid, NewUserName) then begin + if not User.Get(UserGuid) then + exit(false); + UserSecurityID := User."User Security ID"; + exit(true); + end; + + User.SetRange("User Name", NewUserName); + if not User.FindFirst() then begin + User.SetFilter("User Name", '@*''''' + NewUserName + '''''*'); + if not User.FindFirst() then + exit(false); + end; + + UserSecurityID := User."User Security ID"; + exit(true); + end; + + local procedure VerifyOwnerExists() + var + TempAgentAccessControl: Record "Agent Access Control" temporary; + CurrentUserSecurityID: Guid; + CurrentCompanyName: Text[30]; + OwnerFound: Boolean; + begin + CurrentUserSecurityID := Rec."User Security ID"; + CurrentCompanyName := Rec."Company Name"; + + TempAgentAccessControl.Copy(Rec); + + // Check if there's at least one other record with "Can Configure Agent" = true + Rec.SetRange("Agent User Security ID", AgentUserSecurityID); + Rec.SetRange("Can Configure Agent", true); + + OwnerFound := false; + if Rec.FindSet() then + repeat + if not ((Rec."User Security ID" = CurrentUserSecurityID) and (Rec."Company Name" = CurrentCompanyName)) then + OwnerFound := true; + until (Rec.Next() = 0) or OwnerFound; + + Rec.Copy(TempAgentAccessControl); + if not OwnerFound then + Error(OneOwnerMustBeDefinedForAgentErr); + end; + + var + AgentImpl: Codeunit "Agent Impl."; + AgentUserSecurityID: Guid; + UserFullName: Text[80]; + UserName: Code[50]; + GlobalSingleCompanyName: Text[30]; + ShowCompanyField: Boolean; + ShowCompanyFieldOverride: Boolean; + OneOwnerMustBeDefinedForAgentErr: Label 'One owner must be defined for the agent.'; + ShowSingleCompanyQst: Label 'This agent currently has permissions in only one company. By showing the Company field, you will be able to assign access controls in other companies where the agent is not available.\\Do you want to continue?'; +} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/AgentAccessControls/ViewAgentAccessControl.Page.al b/src/System Application/App/Agent/Setup/AgentAccessControls/ViewAgentAccessControl.Page.al new file mode 100644 index 0000000000..0f17aeebba --- /dev/null +++ b/src/System Application/App/Agent/Setup/AgentAccessControls/ViewAgentAccessControl.Page.al @@ -0,0 +1,152 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Agents; + +using System.Security.AccessControl; + +#pragma warning disable AS0125 +page 4320 "View Agent Access Control" +{ +#pragma warning restore AS0125 + + Caption = 'Agent Access Control'; + PageType = ListPart; + ApplicationArea = All; + SourceTable = "Agent Access Control"; + Editable = false; + Extensible = false; + InsertAllowed = false; + DeleteAllowed = false; + ModifyAllowed = false; + InherentEntitlements = X; + InherentPermissions = X; + + layout + { + area(Content) + { + repeater(Main) + { + field(UserName; UserName) + { + Caption = 'User Name'; + ToolTip = 'Specifies the name of the User that can access the agent.'; + TableRelation = User where("License Type" = filter(<> Application & <> "Windows Group" & <> Agent)); + } + field(UserFullName; UserFullName) + { + Caption = 'User Full Name'; + ToolTip = 'Specifies the Full Name of the User that can access the agent.'; + Editable = false; + } + field(Company; Rec."Company Name") + { + Caption = 'Company'; + ToolTip = 'Specifies the company in which the user has access to the agent.'; + Visible = ShowCompanyField; + } + field(CanConfigureAgent; Rec."Can Configure Agent") + { + Caption = 'Can Configure'; + Tooltip = 'Specifies whether the user can configure the agent.'; + } + } + } + } + + actions + { + area(Processing) + { + action(EditUserAccess) + { + ApplicationArea = All; + Caption = 'Edit user access'; + Image = Edit; + ToolTip = 'Edit the users that can access this agent.'; + + trigger OnAction() + var + TempAgentAccessControl: Record "Agent Access Control" temporary; + AgentImpl: Codeunit "Agent Impl."; + SelectAgentAccessControl: Page "Select Agent Access Control"; + begin + CopyAgentAccessControlToBuffer(Rec."Agent User Security ID", TempAgentAccessControl); + + SelectAgentAccessControl.Initialize(Rec."Agent User Security ID", TempAgentAccessControl); + if SelectAgentAccessControl.RunModal() = Action::OK then begin + SelectAgentAccessControl.GetTempAgentAccessControl(TempAgentAccessControl); + AgentImpl.UpdateAgentAccessControl(Rec."Agent User Security ID", TempAgentAccessControl); + end; + + CurrPage.Update(false); + end; + } + + action(AgentShowHideCompany) + { + ApplicationArea = All; + Caption = 'Show/hide company'; + Image = CompanyInformation; + ToolTip = 'Show or hide the company name.'; + + trigger OnAction() + begin + ShowCompanyFieldOverride := true; + ShowCompanyField := not ShowCompanyField; + CurrPage.Update(false); + end; + } + } + } + + trigger OnAfterGetRecord() + var + User: Record "User"; + AgentImpl: Codeunit "Agent Impl."; + GlobalSingleCompanyName: Text[30]; + begin + if not ShowCompanyFieldOverride then begin + ShowCompanyField := not AgentImpl.GetAccessControlForSingleCompany(Rec."Agent User Security ID", GlobalSingleCompanyName); + CurrPage.Update(false); + end; + + Clear(UserFullName); + Clear(UserName); + + if IsNullGuid(Rec."User Security ID") then + exit; + + if not User.Get(Rec."User Security ID") then + exit; + + UserName := User."User Name"; + UserFullName := User."Full Name"; + end; + + local procedure CopyAgentAccessControlToBuffer(UserSecurityID: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary) + var + AgentAccessControl: Record "Agent Access Control"; + begin + TempAgentAccessControl.Reset(); + TempAgentAccessControl.DeleteAll(); + + AgentAccessControl.SetRange("Agent User Security ID", UserSecurityID); + if not AgentAccessControl.FindSet() then + exit; + + repeat + Clear(TempAgentAccessControl); + TempAgentAccessControl.TransferFields(AgentAccessControl); + TempAgentAccessControl.Insert(); + until AgentAccessControl.Next() = 0; + end; + + var + UserFullName: Text[80]; + UserName: Code[50]; + ShowCompanyField, ShowCompanyFieldOverride : Boolean; +} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/AgentCard.Page.al b/src/System Application/App/Agent/Setup/AgentCard.Page.al index 3e5ce04ece..d643021891 100644 --- a/src/System Application/App/Agent/Setup/AgentCard.Page.al +++ b/src/System Application/App/Agent/Setup/AgentCard.Page.al @@ -108,12 +108,11 @@ page 4315 "Agent Card" } part(Permissions; "View Agent Permissions") { - Editable = false; ApplicationArea = Basic, Suite; Caption = 'Agent Permissions'; SubPageLink = "User Security ID" = field("User Security ID"); } - part(UserAccess; "Agent Access Control") + part(UserAccess; "View Agent Access Control") { ApplicationArea = Basic, Suite; Caption = 'User Access'; diff --git a/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al b/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al index 0154d88e55..f004ba6af5 100644 --- a/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al +++ b/src/System Application/App/Agent/Setup/AgentImpl.Codeunit.al @@ -71,21 +71,6 @@ codeunit 4301 "Agent Impl." AgentAccessControl.Insert(); end; - procedure VerifyOwnerExists(AgentAccessControlModified: Record "Agent Access Control") - var - ExistingAgentAccessControl: Record "Agent Access Control"; - begin - if (AgentAccessControlModified."Can Configure Agent") then - exit; - - SetOwnerFilters(ExistingAgentAccessControl); - ExistingAgentAccessControl.SetFilter("User Security ID", '<>%1', AgentAccessControlModified."User Security ID"); - ExistingAgentAccessControl.SetRange("Agent User Security ID", AgentAccessControlModified."Agent User Security ID"); - - if ExistingAgentAccessControl.IsEmpty() then - Error(OneOwnerMustBeDefinedForAgentErr); - end; - procedure GetUserAccess(AgentUserSecurityID: Guid; var TempAgentAccessControl: Record "Agent Access Control" temporary) var Agent: Record Agent; @@ -374,68 +359,47 @@ codeunit 4301 "Agent Impl." local procedure UpdateAgentAccessControl(var TempAgentAccessControl: Record "Agent Access Control" temporary; var Agent: Record Agent) begin // We must delete or update the user doing the change the last to avoid removing permissions that are needed to commit the change - UpdateUsersOtherThanMainUser(TempAgentAccessControl, Agent); + UpdateAgentAccessControlForUsers(TempAgentAccessControl, Agent, '<>%1', UserSecurityId()); // Update the user at the end - UpdateUserDoingTheChange(TempAgentAccessControl, Agent); + UpdateAgentAccessControlForUsers(TempAgentAccessControl, Agent, '%1', UserSecurityId()); end; - local procedure UpdateUsersOtherThanMainUser(var TempAgentAccessControl: Record "Agent Access Control" temporary; var Agent: Record Agent) + local procedure UpdateAgentAccessControlForUsers(var TempAgentAccessControl: Record "Agent Access Control" temporary; var Agent: Record Agent; UserSecurityIdFilter: Text; UserSecurityIdValue: Guid) var AgentAccessControl: Record "Agent Access Control"; begin + // Delete any existing records that match the filter and are not in the temp table AgentAccessControl.SetRange("Agent User Security ID", Agent."User Security ID"); - AgentAccessControl.SetFilter("User Security ID", '<>%1', UserSecurityId()); + AgentAccessControl.SetFilter("User Security ID", UserSecurityIdFilter, UserSecurityIdValue); if AgentAccessControl.FindSet() then repeat - if not TempAgentAccessControl.Get(AgentAccessControl."Agent User Security ID", AgentAccessControl."User Security ID") then + if not TempAgentAccessControl.Get(AgentAccessControl."Agent User Security ID", AgentAccessControl."User Security ID", AgentAccessControl."Company Name") then AgentAccessControl.Delete(true); until AgentAccessControl.Next() = 0; + // Insert or update all records from temp table that match the filter AgentAccessControl.Reset(); TempAgentAccessControl.Reset(); - TempAgentAccessControl.SetFilter("User Security ID", '<>%1', UserSecurityId()); + TempAgentAccessControl.SetFilter("User Security ID", UserSecurityIdFilter, UserSecurityIdValue); if not TempAgentAccessControl.FindSet() then exit; repeat - if AgentAccessControl.Get(Agent."User Security ID", TempAgentAccessControl."User Security ID") then begin - AgentAccessControl.TransferFields(TempAgentAccessControl, true); - AgentAccessControl."Agent User Security ID" := Agent."User Security ID"; + if AgentAccessControl.Get(Agent."User Security ID", TempAgentAccessControl."User Security ID", TempAgentAccessControl."Company Name") then begin + AgentAccessControl."Can Configure Agent" := TempAgentAccessControl."Can Configure Agent"; AgentAccessControl.Modify(); end else begin - AgentAccessControl.TransferFields(TempAgentAccessControl, true); + Clear(AgentAccessControl); AgentAccessControl."Agent User Security ID" := Agent."User Security ID"; + AgentAccessControl."User Security ID" := TempAgentAccessControl."User Security ID"; + AgentAccessControl."Company Name" := TempAgentAccessControl."Company Name"; + AgentAccessControl."Can Configure Agent" := TempAgentAccessControl."Can Configure Agent"; AgentAccessControl.Insert(); end; until TempAgentAccessControl.Next() = 0; end; - local procedure UpdateUserDoingTheChange(var TempAgentAccessControl: Record "Agent Access Control" temporary; var Agent: Record Agent) - var - AgentAccessControl: Record "Agent Access Control"; - begin - TempAgentAccessControl.SetFilter("User Security ID", UserSecurityId()); - if not TempAgentAccessControl.FindFirst() then begin - if AgentAccessControl.Get(Agent."User Security ID", UserSecurityId()) then - AgentAccessControl.Delete(); - - exit; - end; - - if AgentAccessControl.Get(Agent."User Security ID", UserSecurityId()) then begin - AgentAccessControl.TransferFields(TempAgentAccessControl, true); - AgentAccessControl."Agent User Security ID" := Agent."User Security ID"; - AgentAccessControl.Modify(); - exit; - end else begin - AgentAccessControl.TransferFields(TempAgentAccessControl, true); - AgentAccessControl."Agent User Security ID" := Agent."User Security ID"; - AgentAccessControl.Insert(); - exit; - end; - end; - procedure SelectAgent(var Agent: Record "Agent") begin Agent.SetRange(State, Agent.State::Enabled); @@ -551,8 +515,27 @@ codeunit 4301 "Agent Impl." Page.RunModal(SetupPageId, SourceRecordVariant); end; + procedure GetAccessControlForSingleCompany(AgentUserSecurityID: Guid; var SingleCompanyName: Text[30]): Boolean + var + TempCompany: Record Company temporary; + UserSettings: Codeunit "User Settings"; + begin + UserSettings.GetAllowedCompaniesForUser(AgentUserSecurityID, TempCompany); + if TempCompany.IsEmpty() then begin +#pragma warning disable AA0139 + SingleCompanyName := CompanyName(); +#pragma warning restore AA0139 + exit(true); + end; + + if TempCompany.Count() <> 1 then + exit(false); + + SingleCompanyName := TempCompany.Name; + exit(true); + end; + var - OneOwnerMustBeDefinedForAgentErr: Label 'One owner must be defined for the agent.'; AgentDoesNotExistErr: Label 'Agent does not exist.'; NoActiveAgentsErr: Label 'There are no active agents setup on the system.'; NoAgentsAvailableNotificationLbl: Label 'Business Central agents are currently not available in your country.'; diff --git a/src/System Application/App/Agent/Setup/AgentUserSubform.PageExt.al b/src/System Application/App/Agent/Setup/AgentUserSubform.PageExt.al deleted file mode 100644 index ccdbcbaf06..0000000000 --- a/src/System Application/App/Agent/Setup/AgentUserSubform.PageExt.al +++ /dev/null @@ -1,119 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.Agents; - -using System.Environment; -using System.Environment.Configuration; -using System.Security.AccessControl; -using System.Security.User; - -pageextension 4318 "Agent User Subform" extends "User Subform" -{ - layout - { - modify(Company) - { - Visible = (not IsAgent) or ShowCompanyField; - } - } - - actions - { - addlast(Processing) - { - action(AgentShowHideCompany) - { - ApplicationArea = All; - Caption = 'Show/hide company'; - Enabled = IsAgent; - Image = CompanyInformation; - ToolTip = 'Show or hide the company name.'; - Visible = IsAgent; - - trigger OnAction() - begin - if (not ShowCompanyField and (GlobalSingleCompanyName <> '')) then - // A confirmation dialog is raised when the user shows the company field - // for an agent that operates in a single company. - if not Confirm(ShowSingleCompanyQst, false) then - exit; - - ShowCompanyFieldOverride := true; - ShowCompanyField := not ShowCompanyField; - CurrPage.Update(false); - end; - } - } - } - - trigger OnOpenPage() - var - AgentUtilities: Codeunit "Agent Utilities"; - begin - AgentUtilities.BlockPageFromBeingOpenedByAgent(); - end; - - trigger OnAfterGetRecord() - begin - UpdateGlobalVariables(); - end; - - trigger OnInsertRecord(BelowxRec: Boolean): Boolean - begin - if (IsAgent and (GlobalSingleCompanyName <> '')) then begin - if (Rec."Company Name" = '') and not ShowCompanyField then - // Default the company name for the inserted record when all these conditions are met: - // 1. The agent operates in a single company, - // 2. The company name is not explicit specified, - // 3. The user didn't toggle to view the company name field on permissions. - Rec."Company Name" := GlobalSingleCompanyName; - - if (Rec."Company Name" <> GlobalSingleCompanyName) then - // The agent used to operation in a single company, but operates in multiple ones now. - // Ideally, other scenarios should also trigger an update (delete, modify), but insert - // was identified as the main one. - GlobalSingleCompanyName := ''; - end; - end; - - local procedure UpdateGlobalVariables() - var - User: Record User; - begin - if User.Get(Rec."User Security ID") then - IsAgent := User."License Type" = User."License Type"::Agent - else - IsAgent := false; - - if not ShowCompanyFieldOverride then begin - ShowCompanyField := not AccessControlForSingleCompany(GlobalSingleCompanyName); - CurrPage.Update(false); - end; - end; - - local procedure AccessControlForSingleCompany(var SingleCompanyName: Text[30]): Boolean - var - TempCompany: Record Company temporary; - UserSettings: Codeunit "User Settings"; - begin - if not IsAgent then - exit(false); - - UserSettings.GetAllowedCompaniesForUser(Rec."User Security ID", TempCompany); - if TempCompany.Count() <> 1 then - exit(false); - - SingleCompanyName := TempCompany.Name; - exit(true); - end; - - var - IsAgent: Boolean; - ShowCompanyField: Boolean; - ShowCompanyFieldOverride: Boolean; - GlobalSingleCompanyName: Text[30]; - ShowSingleCompanyQst: Label 'This agent currently has permissions in only one company. By showing the Company field, you will be able to assign permissions in other companies, making the agent available there. The agent may not have been designed to work cross companies.\\Do you want to continue?'; -} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/SelectAgentAccessControl.Page.al b/src/System Application/App/Agent/Setup/SelectAgentAccessControl.Page.al deleted file mode 100644 index a462f55015..0000000000 --- a/src/System Application/App/Agent/Setup/SelectAgentAccessControl.Page.al +++ /dev/null @@ -1,179 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.Agents; - -using System.Security.AccessControl; - -page 4321 "Select Agent Access Control" -{ - PageType = StandardDialog; - ApplicationArea = All; - SourceTable = "Agent Access Control"; - SourceTableTemporary = true; - Caption = 'Select users to manage tasks and configure the agent'; - MultipleNewLines = false; - Extensible = false; - DataCaptionExpression = ''; - InherentEntitlements = X; - InherentPermissions = X; - - layout - { - area(Content) - { - repeater(Main) - { - field(UserName; UserName) - { - Caption = 'User Name'; - ToolTip = 'Specifies the name of the User that can access the agent.'; - TableRelation = User where("License Type" = filter(<> Application & <> "Windows Group" & <> Agent)); - - trigger OnValidate() - begin - ValidateUserName(UserName); - CurrPage.Update(true); - end; - } - field(UserFullName; UserFullName) - { - Caption = 'User Full Name'; - ToolTip = 'Specifies the Full Name of the User that can access the agent.'; - Editable = false; - } - field(CanConfigureAgent; Rec."Can Configure Agent") - { - Caption = 'Can configure'; - Tooltip = 'Specifies whether the user can configure the agent.'; - - trigger OnValidate() - begin - if not Rec."Can Configure Agent" then - VerifyOwnerExists(); - end; - } - } - } - } - - trigger OnAfterGetRecord() - begin - UpdateGlobalVariables(); - end; - - trigger OnAfterGetCurrRecord() - begin - UpdateGlobalVariables(); - end; - - trigger OnDeleteRecord(): Boolean - begin - VerifyOwnerExists(); - end; - - trigger OnOpenPage() - var - AgentImpl: Codeunit "Agent Impl."; - begin - if Rec.GetFilter("Agent User Security ID") <> '' then - Evaluate(AgentUserSecurityID, Rec.GetFilter("Agent User Security ID")); - - if Rec.Count() = 0 then - AgentImpl.InsertCurrentOwner(Rec."Agent User Security ID", Rec); - end; - - local procedure ValidateUserName(NewUserName: Text) - var - User: Record "User"; - UserGuid: Guid; - begin - if Evaluate(UserGuid, NewUserName) then begin - User.Get(UserGuid); - UpdateUser(User."User Security ID"); - UpdateGlobalVariables(); - exit; - end; - - User.SetRange("User Name", NewUserName); - if not User.FindFirst() then begin - User.SetFilter("User Name", '@*''''' + NewUserName + '''''*'); - User.FindFirst(); - end; - - UpdateUser(User."User Security ID"); - UpdateGlobalVariables(); - end; - - local procedure UpdateUser(NewUserID: Guid) - var - TempAgentAccessControl: Record "Agent Access Control" temporary; - RecordExists: Boolean; - begin - RecordExists := Rec.Find(); - - if RecordExists then begin - TempAgentAccessControl.Copy(Rec); - Rec.Delete(); - Rec.Copy(TempAgentAccessControl); - end; - - Rec."User Security ID" := NewUserID; - Rec."Agent User Security ID" := AgentUserSecurityID; - Rec.Insert(true); - VerifyOwnerExists(); - end; - - local procedure UpdateGlobalVariables() - var - User: Record "User"; - begin - Clear(UserFullName); - Clear(UserName); - - if IsNullGuid(Rec."User Security ID") then - exit; - - if not User.Get(Rec."User Security ID") then - exit; - - UserName := User."User Name"; - UserFullName := User."Full Name"; - end; - - [Scope('OnPrem')] - procedure GetAgentUserAccess(var TempAgentAccessControl: Record "Agent Access Control" temporary) - begin - TempAgentAccessControl.Reset(); - TempAgentAccessControl.DeleteAll(); - - if Rec.FindSet() then - repeat - TempAgentAccessControl.Copy(Rec); - TempAgentAccessControl.Insert(); - until Rec.Next() = 0; - end; - - local procedure VerifyOwnerExists() - var - TempAgentAccessControl: Record "Agent Access Control" temporary; - begin - TempAgentAccessControl.Copy(Rec); - Rec.SetFilter("Can Configure Agent", '%1', true); - Rec.SetFilter("User Security ID", '<>%1', Rec."User Security ID"); - if Rec.IsEmpty() then begin - Rec.Copy(TempAgentAccessControl); - Error(OneOwnerMustBeDefinedForAgentErr); - end; - - Rec.Copy(TempAgentAccessControl); - end; - - var - UserFullName: Text[80]; - UserName: Code[50]; - AgentUserSecurityID: Guid; - OneOwnerMustBeDefinedForAgentErr: Label 'One owner must be defined for the agent.'; -} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/SelectAgentPermissions.Page.al b/src/System Application/App/Agent/Setup/SelectAgentPermissions.Page.al deleted file mode 100644 index 07bf589300..0000000000 --- a/src/System Application/App/Agent/Setup/SelectAgentPermissions.Page.al +++ /dev/null @@ -1,38 +0,0 @@ -// ------------------------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. -// ------------------------------------------------------------------------------------------------ - -namespace System.Agents; - -using System.Security.User; - -page 4336 "Select Agent Permissions" -{ - PageType = StandardDialog; - ApplicationArea = All; - SourceTable = Agent; - Caption = 'Edit Agent Permissions (Preview)'; - DataCaptionExpression = ''; - InherentPermissions = X; - InherentEntitlements = X; - - layout - { - area(Content) - { - group(Info) - { - ShowCaption = false; - InstructionalText = 'Agent tasks use permissions shared by both the agent and the task creator/approver.'; - } - - part(Permissions; "User Subform") - { - Editable = true; - Caption = 'Agent Permissions'; - SubPageLink = "User Security ID" = field("User Security ID"); - } - } - } -} \ No newline at end of file diff --git a/src/System Application/App/Agent/Setup/SetupPart/AgentSetupImpl.Codeunit.al b/src/System Application/App/Agent/Setup/SetupPart/AgentSetupImpl.Codeunit.al index 3f13e655af..435882eb4e 100644 --- a/src/System Application/App/Agent/Setup/SetupPart/AgentSetupImpl.Codeunit.al +++ b/src/System Application/App/Agent/Setup/SetupPart/AgentSetupImpl.Codeunit.al @@ -49,9 +49,11 @@ codeunit 4325 "Agent Setup Impl." procedure UpdateUserAccessControl(var AgentSetupBuffer: Record "Agent Setup Buffer"): Boolean var TempAgentAccessControl: Record "Agent Access Control" temporary; + SelectAgentAccessControl: Page "Select Agent Access Control"; begin AgentSetupBuffer.GetTempAgentAccessControl(TempAgentAccessControl); - if (Page.RunModal(Page::"Select Agent Access Control", TempAgentAccessControl) in [Action::LookupOK, Action::OK]) then begin + SelectAgentAccessControl.Initialize(AgentSetupBuffer."User Security ID", TempAgentAccessControl); + if (SelectAgentAccessControl.RunModal() in [Action::LookupOK, Action::OK]) then begin AgentSetupBuffer."Access Updated" := true; AgentSetupBuffer.Modify(true); AgentSetupBuffer.SetTempAgentAccessControl(TempAgentAccessControl); diff --git a/src/System Application/App/External File Storage/src/FileStorage/ExternalFileStorageImpl.Codeunit.al b/src/System Application/App/External File Storage/src/FileStorage/ExternalFileStorageImpl.Codeunit.al index 8fb2250bfb..adbb308602 100644 --- a/src/System Application/App/External File Storage/src/FileStorage/ExternalFileStorageImpl.Codeunit.al +++ b/src/System Application/App/External File Storage/src/FileStorage/ExternalFileStorageImpl.Codeunit.al @@ -160,6 +160,10 @@ codeunit 9455 "External File Storage Impl." if TempFileAccountContent.Type <> TempFileAccountContent.Type::Directory then exit(''); + // The ".." entry represents the current directory for selection purposes + if TempFileAccountContent.Name = '..' then + exit(StorageBrowser.GetCurrentDirectory()); + exit(CombinePath(TempFileAccountContent."Parent Directory", TempFileAccountContent.Name)); end; diff --git a/src/System Application/App/User Permissions/src/UserSubform.Page.al b/src/System Application/App/User Permissions/src/UserSubform.Page.al index 39c958f7b7..83cafc5465 100644 --- a/src/System Application/App/User Permissions/src/UserSubform.Page.al +++ b/src/System Application/App/User Permissions/src/UserSubform.Page.al @@ -5,6 +5,7 @@ namespace System.Security.User; +using System.Agents; using System.Security.AccessControl; page 9801 "User Subform" @@ -100,6 +101,13 @@ page 9801 "User Subform" PermissionSetNotFound: Boolean; UserPermissionsTok: Label 'User Permissions', Locked = true; + trigger OnOpenPage() + var + AgentUtilities: Codeunit "Agent Utilities"; + begin + AgentUtilities.BlockPageFromBeingOpenedByAgent(); + end; + trigger OnAfterGetRecord() var AggregatePermissionSet: Record "Aggregate Permission Set";