-
Notifications
You must be signed in to change notification settings - Fork 370
Sales Validation Agent - Implementation of the sample agent via the AL SDK #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,141
−0
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2fb1fbc
Added sales validation agent
SotirisDragonas db18ddb
fix namespace
SotirisDragonas c611d25
Address PR review comments
SotirisDragonas 78d5f00
remove external ID
SotirisDragonas 0b787da
openup names
SotirisDragonas c4533a0
fix
SotirisDragonas c998e2c
fix event subscriber
SotirisDragonas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
samples/BCAgents/SalesValidationAgent/.resources/Instructions/InstructionsV1.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| You are a sales validation agent. A detail-oriented professional responsible for ensuring timely and accurate processing of sales orders. The sales validation agent acts as the bridge between order management and inventory control, prioritizing efficiency, compliance, and customer satisfaction. This role requires strong attention to detail, adherence to business rules, and proactive communication when issues arise. | ||
|
|
||
| ## Instructions | ||
|
|
||
| 1. Find all **open** sales orders for the **specified date**. | ||
| 2. For **each sales order**, perform the following steps **in exact sequential order**: | ||
| * **FIRST**: Open the **Statistics page** for the sales order. | ||
| * **SECOND**: **Verify and record** the reservation stock status from the Statistics page. | ||
| * **THIRD**: **Check and record** the shipping advice setting on the sales order. | ||
| * **FOURTH**: Based on the verified reservation stock and shipping advice, determine if the order can be released: | ||
| * If shipping advice is **Complete**: Release **ONLY** if reservation stock is **Full** (100% reserved). | ||
| * If shipping advice is **Partial**: Release **ONLY** if reservation stock shows any quantity reserved (**Partial** or **Full**). | ||
| * If reservation stock is None: **DO NOT release** - flag for user review. | ||
| * **FIFTH**: If release criteria are met, release the document. Otherwise, flag it with the specific reason. | ||
| 3. **Repeat step 2** for each sales order found in step 1. | ||
| 4. After completing all orders for the date, provide a summary in this format: | ||
| ``` | ||
| All open sales orders with shipment date [DATE] have been reviewed: | ||
|
|
||
| Sales orders [o1], [o2], [o3], ... were released. | ||
|
|
||
| Not released: | ||
| Sales order [####]: Reservation Status: [None/Partial/Full], Shipping Advice: [Complete/Partial] | ||
| Sales order [####]: Reservation Status: [None/Partial/Full], Shipping Advice: [Complete/Partial] | ||
|
|
||
| Please review flagged orders for further action. | ||
| ``` | ||
| * List released orders as a simple comma-separated list without additional details. | ||
| * For not released orders, include the order number, reservation status, and shipping advice (if relevant to the issue). | ||
| * Keep messaging concise and actionable. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # Sales Validation Agent | ||
|
|
||
| A sample **third-party agent** extension for Microsoft Dynamics 365 Business Central that validates and processes sales orders by checking inventory reservation and releasing eligible orders to the warehouse. It's purpose is to demonstrate how the sample agent in Business Central can be productized into an app. | ||
|
|
||
| ## Overview | ||
|
|
||
| The Sales Validation Agent is a Copilot-powered agent that automates the routine work of reviewing open sales orders for a given shipment date, verifying that inventory is reserved, and releasing qualifying orders. It is built on the Business Central **Agent** framework and is intended as a reference implementation for partners building their own agents. | ||
|
|
||
| --- | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| app/ | ||
| ├── Integration/ # Copilot capability registration & install logic | ||
| ├── Interaction/ # User-facing pages and page extensions | ||
| └── Setup/ # Agent configuration, metadata, KPIs, and profile | ||
| ├── KPI/ # Performance tracking (orders released) | ||
| ├── Metadata/ # IAgentFactory / IAgentMetadata implementations | ||
| └── Profile/ # Role Center profile & page customizations | ||
| ``` | ||
|
|
||
| ### Integration | ||
|
|
||
| Contains the install codeunit and enum extension that register the **Sales Validation Agent** as a Copilot capability. On first install the capability is registered with the platform; on subsequent installs or upgrades the agent's instructions are refreshed. | ||
|
|
||
| ### Interaction | ||
|
|
||
| Provides the user-facing entry point for the agent. A page extension on the **Sales Order List** adds a *Validate with Agent* action that verifies the agent is active, prompts the user for a shipment date via a dialog page, and creates an `Agent Task` with the selected date. | ||
|
|
||
| ### Setup | ||
|
|
||
| Holds the core configuration for the agent: | ||
|
|
||
| - **Setup table & page** – A lightweight table stores the agent's User Security ID (company-independent), and a `ConfigurationDialog` page lets admins provision or update the agent user with the correct profile and permissions. The Copilot capability must be enabled before the setup page can be opened. | ||
| - **Setup codeunit** – Central helper that resolves the agent user, supplies default profile and access controls (`D365 READ` + `D365 SALES`), loads instructions from a resource file, and ensures the setup record exists. | ||
|
|
||
| #### KPI | ||
|
|
||
| Tracks agent performance metrics. A KPI table records counters per agent user (currently *Orders Released*), exposed through a `CardPart` summary page. An event subscriber on `OnAfterReleaseSalesDoc` automatically increments the counter each time the agent releases a sales order, so metrics stay up to date without any manual bookkeeping. | ||
|
|
||
| #### Metadata | ||
|
|
||
| Implements the `IAgentFactory` and `IAgentMetadata` interfaces required by the Business Central Agent framework. The factory provides default initials, the setup page, the Copilot capability, the default profile, and access control templates. The `ShowCanCreateAgent` method controls UI visibility for agent creation. The agent here is single-instance. The metadata codeunit supplies page IDs for setup, summary, and task message cards. An enum extension on `Agent Metadata Provider` wires these implementations into the platform. | ||
|
|
||
| #### Profile | ||
|
|
||
| Defines a dedicated **Sales Validation Agent (Copilot)** profile based on the *Order Processor Role Center*. Accompanying page customizations tailor the Role Center, Sales Order card, Sales Order List, Sales Order Statistics, Sales Order Subform, and SO Processor Activities pages to present only the information the agent needs. | ||
|
|
||
| --- | ||
|
|
||
| ## How It Works | ||
|
|
||
| 1. **Create the agent** – The *Sales Validation Agent* Copilot capability is registered as **Preview** and is therefore enabled by default. Open the *Sales Val. Agent Setup* configuration dialog (accessible from the agent avatar in the Role Center) to provision the agent user with the correct profile and permissions. | ||
| 2. **Assign a task** – From the **Sales Order List**, choose *Validate with Agent*, pick a shipment date, and a task is created for the agent. | ||
| 3. **Agent processes orders** – The agent reads its instructions (loaded from `Instructions/InstructionsV1.txt`), validates open sales orders for the specified shipment date, checks inventory reservation, and releases eligible orders. | ||
| 4. **KPIs are tracked** – Each time the agent releases an order, the `OnAfterReleaseSalesDoc` event subscriber increments the *Orders Released* counter, visible on the agent's summary page. | ||
|
|
||
| --- | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Business Central **application version 27.4** or later | ||
| - The **Copilot & AI Capabilities** feature enabled in the environment | ||
| - Appropriate licensing for Copilot / Agent functionality |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| { | ||
| "id": "5878e09e-13b4-4058-a9f6-4a9ee1605d6a", | ||
| "name": "Sales Validation Agent Sample", | ||
| "publisher": "Default Publisher", | ||
| "version": "1.0.0.0", | ||
| "brief": "", | ||
| "description": "", | ||
| "privacyStatement": "", | ||
| "EULA": "", | ||
| "help": "", | ||
| "url": "", | ||
| "logo": "", | ||
| "dependencies": [], | ||
| "screenshots": [], | ||
| "platform": "1.0.0.0", | ||
| "application": "27.4.0.0", | ||
| "idRanges": [ | ||
| { | ||
| "from": 50100, | ||
| "to": 50149 | ||
| } | ||
| ], | ||
| "resourceExposurePolicy": { | ||
| "allowDebugging": true, | ||
| "allowDownloadingSource": true, | ||
| "includeSourceInSymbolFile": true, | ||
| "applyToDevExtension": false | ||
| }, | ||
| "runtime": "17.0", | ||
| "features": [ | ||
| "NoImplicitWith" | ||
| ], | ||
| "resourceFolders": [".resources"], | ||
| "target": "Cloud" | ||
| } |
59 changes: 59 additions & 0 deletions
59
samples/BCAgents/SalesValidationAgent/app/Integration/SalesValAgentInstall.Codeunit.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace SalesValidationAgent.Integration; | ||
|
|
||
| using SalesValidationAgent.Setup; | ||
| using System.Agents; | ||
| using System.AI; | ||
| using System.Security.AccessControl; | ||
|
|
||
| codeunit 50101 "Sales Val. Agent Install" | ||
| { | ||
| Subtype = Install; | ||
| Access = Internal; | ||
| InherentEntitlements = X; | ||
| InherentPermissions = X; | ||
|
|
||
| trigger OnInstallAppPerDatabase() | ||
| var | ||
| SalesValAgentSetupRec: Record "Sales Val. Agent Setup"; | ||
| begin | ||
| RegisterCapability(); | ||
|
|
||
| if not SalesValAgentSetupRec.FindSet() then | ||
| exit; | ||
|
|
||
| repeat | ||
| InstallAgent(SalesValAgentSetupRec); | ||
| until SalesValAgentSetupRec.Next() = 0; | ||
| end; | ||
|
|
||
| local procedure InstallAgent(var SalesValAgentSetupRec: Record "Sales Val. Agent Setup") | ||
| begin | ||
| InstallAgentInstructions(SalesValAgentSetupRec); | ||
| end; | ||
|
|
||
| local procedure InstallAgentInstructions(var SalesValAgentSetupRec: Record "Sales Val. Agent Setup") | ||
| var | ||
| Agent: Codeunit Agent; | ||
| SalesValAgentSetup: Codeunit "Sales Val. Agent Setup"; | ||
| begin | ||
| Agent.SetInstructions(SalesValAgentSetupRec."User Security ID", SalesValAgentSetup.GetInstructions()); | ||
| end; | ||
|
|
||
| local procedure RegisterCapability() | ||
| var | ||
| CopilotCapability: Codeunit "Copilot Capability"; | ||
| LearnMoreUrlTxt: Label 'https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/ai/ai-development-toolkit-sales-validation', Locked = true; | ||
| begin | ||
| if not CopilotCapability.IsCapabilityRegistered(Enum::"Copilot Capability"::"Sales Validation Agent") then | ||
| CopilotCapability.RegisterCapability( | ||
| Enum::"Copilot Capability"::"Sales Validation Agent", | ||
| Enum::"Copilot Availability"::Preview, | ||
| "Copilot Billing Type"::"Microsoft Billed", | ||
| LearnMoreUrlTxt); | ||
| end; | ||
| } | ||
16 changes: 16 additions & 0 deletions
16
samples/BCAgents/SalesValidationAgent/app/Integration/SalesValCopilotCapability.EnumExt.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace SalesValidationAgent.Integration; | ||
|
|
||
| using System.AI; | ||
|
|
||
| enumextension 50100 "Sales Val. Copilot Capability" extends "Copilot Capability" | ||
| { | ||
| value(50100; "Sales Validation Agent") | ||
| { | ||
| Caption = 'Sales Validation Agent'; | ||
| } | ||
| } |
42 changes: 42 additions & 0 deletions
42
samples/BCAgents/SalesValidationAgent/app/Interaction/SalesValAgentDatePicker.Page.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace SalesValidationAgent.Interaction; | ||
|
|
||
| /// <summary> | ||
| /// A simple dialog page that prompts the user to select a shipment date | ||
| /// for the Sales Validation Agent to process open sales orders. | ||
| /// </summary> | ||
| page 50102 "Sales Val. Agent Date Picker" | ||
| { | ||
| PageType = StandardDialog; | ||
| Caption = 'Select Shipment Date'; | ||
|
|
||
| layout | ||
| { | ||
| area(Content) | ||
| { | ||
| field(ShipmentDate; ShipmentDate) | ||
| { | ||
| Caption = 'Shipment Date'; | ||
| ToolTip = 'Specify the shipment date to validate sales orders for.'; | ||
| ApplicationArea = All; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var | ||
| ShipmentDate: Date; | ||
|
|
||
| procedure GetShipmentDate(): Date | ||
| begin | ||
| exit(ShipmentDate); | ||
| end; | ||
|
|
||
| procedure SetShipmentDate(NewDate: Date) | ||
| begin | ||
| ShipmentDate := NewDate; | ||
| end; | ||
| } |
77 changes: 77 additions & 0 deletions
77
samples/BCAgents/SalesValidationAgent/app/Interaction/SalesValAgentSalesOrders.PageExt.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace SalesValidationAgent.Interaction; | ||
|
|
||
| using Microsoft.Sales.Document; | ||
| using SalesValidationAgent.Setup; | ||
| using System.Agents; | ||
|
|
||
| /// <summary> | ||
| /// Extends the Sales Order List page to enable Sales Validation Agent task assignment. | ||
| /// Adds an action that allows users to trigger the agent to validate and process open sales orders. | ||
| /// </summary> | ||
| pageextension 50101 "Sales Val. Agent Sales Orders" extends "Sales Order List" | ||
| { | ||
| actions | ||
| { | ||
| addlast(processing) | ||
| { | ||
| action(ValidateWithAgent) | ||
| { | ||
| Caption = 'Validate with Agent'; | ||
| ToolTip = 'Assign a validation task to the Sales Validation Agent to process open orders for a specific date.'; | ||
| Image = Task; | ||
| ApplicationArea = All; | ||
|
|
||
| trigger OnAction() | ||
| var | ||
| AgentTask: Record "Agent Task"; | ||
| Agent: Codeunit Agent; | ||
| AgentTaskBuilder: Codeunit "Agent Task Builder"; | ||
| SalesValAgentSetup: Codeunit "Sales Val. Agent Setup"; | ||
| DatePicker: Page "Sales Val. Agent Date Picker"; | ||
| AgentUserSecurityId: Guid; | ||
| TaskTitle: Text[150]; | ||
| From: Text[250]; | ||
| Message: Text; | ||
| ShipmentDate: Date; | ||
| begin | ||
| if not SalesValAgentSetup.TryGetAgent(AgentUserSecurityId) then | ||
| Error(SVAgentDoesNotExistErr); | ||
|
|
||
| if not Agent.IsActive(AgentUserSecurityId) then | ||
| Error(SVAgentNotActiveErr); | ||
|
|
||
| DatePicker.SetShipmentDate(WorkDate()); | ||
| if DatePicker.RunModal() <> Action::OK then | ||
| exit; | ||
|
|
||
| ShipmentDate := DatePicker.GetShipmentDate(); | ||
| if ShipmentDate = 0D then | ||
| Error(ShipmentDateRequiredErr); | ||
|
|
||
| Message := StrSubstNo(TaskMessageLbl, ShipmentDate); | ||
| TaskTitle := CopyStr(StrSubstNo(TaskTitleLbl, ShipmentDate), 1, MaxStrLen(TaskTitle)); | ||
| From := CopyStr(UserId(), 1, MaxStrLen(From)); | ||
|
|
||
| AgentTask := AgentTaskBuilder.Initialize(AgentUserSecurityId, TaskTitle) | ||
| .AddTaskMessage(From, Message) | ||
| .Create(); | ||
|
|
||
| Message(TaskAssignedMsg, AgentTask.ID, ShipmentDate); | ||
| end; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var | ||
| SVAgentDoesNotExistErr: Label 'The Sales Validation Agent has not been created.'; | ||
| SVAgentNotActiveErr: Label 'The Sales Validation Agent is not active.'; | ||
| ShipmentDateRequiredErr: Label 'A shipment date must be specified.'; | ||
| TaskMessageLbl: Label 'Run and process shipment date %1.', Locked = true, Comment = '%1 = Shipment Date'; | ||
| TaskTitleLbl: Label 'Validate Sales Orders for %1', Comment = '%1 = Shipment Date'; | ||
| TaskAssignedMsg: Label 'Task %1 assigned successfully to validate sales orders for date: %2.', Comment = '%1 = Task ID, %2 = Shipment Date'; | ||
| } |
62 changes: 62 additions & 0 deletions
62
samples/BCAgents/SalesValidationAgent/app/Setup/KPI/SalesValAgentKPI.Page.al
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // ------------------------------------------------------------------------------------------------ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. See License.txt in the project root for license information. | ||
| // ------------------------------------------------------------------------------------------------ | ||
|
|
||
| namespace SalesValidationAgent.Setup.KPI; | ||
|
|
||
| page 50104 "Sales Val. Agent KPI" | ||
| { | ||
| PageType = CardPart; | ||
| ApplicationArea = All; | ||
| UsageCategory = Administration; | ||
| Caption = 'Sales Validation Agent Summary'; | ||
| SourceTable = "Sales Val. Agent KPI"; | ||
| Editable = false; | ||
| Extensible = false; | ||
|
|
||
| layout | ||
| { | ||
| area(Content) | ||
| { | ||
| cuegroup(KeyMetrics) | ||
| { | ||
| Caption = 'Key Performance Indicators'; | ||
|
|
||
| field(OrdersReleased; Rec."Orders Released") | ||
| { | ||
| Caption = 'Orders Released'; | ||
| ToolTip = 'Specifies the number of sales orders released by the agent.'; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| trigger OnOpenPage() | ||
| begin | ||
| GetRelevantAgent(); | ||
| end; | ||
|
|
||
| /// <summary> | ||
| /// Retrieves the relevant agent's KPI record for display. | ||
| /// This page is launched via IAgentMetadata.GetSummaryPageId(). The platform sets a filter on the | ||
| /// "User Security ID" field before opening the page, so the source record may not be fully populated | ||
| /// on open - the filter is evaluated here to resolve and load the correct record. | ||
| /// </summary> | ||
| local procedure GetRelevantAgent() | ||
| var | ||
| UserSecurityIDFilter: Text; | ||
| begin | ||
| if IsNullGuid(Rec."User Security ID") then begin | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should have a comment explaining how that page behaves and is launched. |
||
| UserSecurityIDFilter := Rec.GetFilter("User Security ID"); | ||
| if not Evaluate(Rec."User Security ID", UserSecurityIDFilter) then | ||
| Error(AgentDoesNotExistErr); | ||
| end; | ||
|
|
||
| if not Rec.Get(Rec."User Security ID") then | ||
| Rec.Insert(); | ||
| end; | ||
|
|
||
| var | ||
| AgentDoesNotExistErr: Label 'The agent does not exist. Please check the configuration.'; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.