diff --git a/src/Apps/W1/EDocument/App/Permissions/EDocCoreObjects.PermissionSet.al b/src/Apps/W1/EDocument/App/Permissions/EDocCoreObjects.PermissionSet.al
index f1c0060a1d6..782f758045d 100644
--- a/src/Apps/W1/EDocument/App/Permissions/EDocCoreObjects.PermissionSet.al
+++ b/src/Apps/W1/EDocument/App/Permissions/EDocCoreObjects.PermissionSet.al
@@ -103,6 +103,7 @@ permissionset 6100 "E-Doc. Core - Objects"
codeunit "E-Doc. PO Copilot Matching" = X,
#endif
codeunit "E-Doc. Attachment Processor" = X,
+ codeunit "E-Doc. Hist. Line Data Loader" = X,
codeunit "E-Doc. Message Mgt." = X,
codeunit "Service Participant" = X,
page "E-Doc. Changes Part" = X,
@@ -134,6 +135,7 @@ permissionset 6100 "E-Doc. Core - Objects"
page "Service Participants" = X,
page "E-Doc. Create Purch Order Line" = X,
page "E-Doc. Purchase Draft Subform" = X,
+ page "E-Doc. Historical Lines List" = X,
page "E-Doc. Read. Purch. Lines" = X,
page "E-Doc. Readable Purchase Doc." = X,
page "E-Document Purchase Draft" = X,
diff --git a/src/Apps/W1/EDocument/App/app.json b/src/Apps/W1/EDocument/App/app.json
index 5403fda71a6..b505a276e9b 100644
--- a/src/Apps/W1/EDocument/App/app.json
+++ b/src/Apps/W1/EDocument/App/app.json
@@ -85,6 +85,10 @@
"from": 6234,
"to": 6234
},
+ {
+ "from": 6244,
+ "to": 6249
+ },
{
"from": 6401,
"to": 6411
diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al
index 5fc3e998891..4afc27a6e0d 100644
--- a/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al
+++ b/src/Apps/W1/EDocument/App/src/Processing/Import/PrepareDraft/PreparePurchaseEDocDraft.Codeunit.al
@@ -7,6 +7,7 @@ namespace Microsoft.eServices.EDocument.Processing.Import;
using Microsoft.eServices.EDocument;
using Microsoft.eServices.EDocument.Processing.Interfaces;
using Microsoft.Purchases.Vendor;
+using System.Config;
codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData
{
@@ -16,7 +17,12 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData
PrepareDraftHelper: Codeunit "EDoc Prepare Purch. Draft";
procedure PrepareDraft(EDocument: Record "E-Document"; EDocImportParameters: Record "E-Doc. Import Parameters"): Enum "E-Document Type"
+ var
+ FeatureConfiguration: Codeunit "Feature Configuration";
begin
+ if FeatureConfiguration.GetConfiguration(AgentDrivenLineMatchingTok) = AgentDrivenTreatmentTok then
+ exit("E-Document Type"::"Purchase Invoice");
+
PrepareDraftHelper.PrepareDraft(EDocument, EDocImportParameters);
exit("E-Document Type"::"Purchase Invoice");
end;
@@ -35,4 +41,8 @@ codeunit 6125 "Prepare Purchase E-Doc. Draft" implements IProcessStructuredData
begin
Vendor := PrepareDraftHelper.GetVendor(EDocument, Customizations);
end;
+
+ var
+ AgentDrivenLineMatchingTok: Label 'PAAgentDrivenLineMatching', Locked = true;
+ AgentDrivenTreatmentTok: Label 'agent_driven', Locked = true;
}
diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al
index 7b208ae2ae1..5447da455c8 100644
--- a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al
+++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/EDocPurchaseDraftSubform.Page.al
@@ -4,13 +4,18 @@
// ------------------------------------------------------------------------------------------------
namespace Microsoft.eServices.EDocument.Processing.Import.Purchase;
+using Microsoft.Bank.Reconciliation;
using Microsoft.eServices.EDocument;
using Microsoft.eServices.EDocument.Processing.Import;
+using Microsoft.Finance.Deferral;
using Microsoft.Finance.Dimension;
+using Microsoft.Finance.GeneralLedger.Account;
+using Microsoft.Inventory.Item;
using Microsoft.Inventory.Item.Catalog;
using Microsoft.Purchases.Document;
using Microsoft.Purchases.History;
using Microsoft.Purchases.Setup;
+using System.Config;
page 6183 "E-Doc. Purchase Draft Subform"
{
@@ -322,12 +327,14 @@ page 6183 "E-Doc. Purchase Draft Subform"
Caption = 'Item References';
ToolTip = 'View item references for the vendor associated with this e-document.';
Image = Change;
+ Scope = Repeater;
trigger OnAction()
var
ItemReference: Record "Item Reference";
ItemReferencePage: Page "Item Reference Entries";
begin
+ EnsureEDocumentPurchaseHeader();
EDocumentPurchaseHeader.TestField("[BC] Vendor No.");
ItemReference.SetRange("Reference Type", ItemReference."Reference Type"::Vendor);
ItemReference.SetRange("Reference Type No.", EDocumentPurchaseHeader."[BC] Vendor No.");
@@ -335,6 +342,92 @@ page 6183 "E-Doc. Purchase Draft Subform"
ItemReferencePage.Run();
end;
}
+ action(OpenTextToAccountMappings)
+ {
+ ApplicationArea = All;
+ Caption = 'Text-to-Account Mappings';
+ ToolTip = 'Opens the Text-to-Account Mapping filtered for the current vendor.';
+ Image = MapAccounts;
+ Scope = Repeater;
+ Visible = AgentDrivenLineMatchingEnabled;
+
+ trigger OnAction()
+ var
+ TextToAccountMapping: Record "Text-to-Account Mapping";
+ begin
+ EnsureEDocumentPurchaseHeader();
+ EDocumentPurchaseHeader.TestField("[BC] Vendor No.");
+ TextToAccountMapping.SetFilter("Vendor No.", '%1|%2', '', EDocumentPurchaseHeader."[BC] Vendor No.");
+ Page.Run(Page::"Text-to-Account Mapping", TextToAccountMapping);
+ end;
+ }
+ action(OpenHistoricalPurchaseLines)
+ {
+ ApplicationArea = All;
+ Caption = 'Historical Purchase Lines';
+ ToolTip = 'Opens historical purchase invoice lines to help match this draft line based on past invoices.';
+ Image = History;
+ Scope = Repeater;
+ Visible = AgentDrivenLineMatchingEnabled;
+
+ trigger OnAction()
+ var
+ TempPurchInvLine: Record "Purch. Inv. Line" temporary;
+ EDocHistLineDataLoader: Codeunit "E-Doc. Hist. Line Data Loader";
+ EDocHistoricalLinesList: Page "E-Doc. Historical Lines List";
+ begin
+ EnsureEDocumentPurchaseHeader();
+ EDocumentPurchaseHeader.TestField("[BC] Vendor No.");
+ EDocHistLineDataLoader.LoadHistoricalLines(TempPurchInvLine, EDocumentPurchaseHeader."[BC] Vendor No.", Rec."Product Code", Rec.Description);
+ EDocHistoricalLinesList.SetRecords(TempPurchInvLine);
+ EDocHistoricalLinesList.Run();
+ end;
+ }
+ action(OpenChartOfAccounts)
+ {
+ ApplicationArea = All;
+ Caption = 'Chart of Accounts';
+ ToolTip = 'Opens the Chart of Accounts to look up G/L accounts for this line.';
+ Image = ChartOfAccounts;
+ Scope = Repeater;
+ Visible = AgentDrivenLineMatchingEnabled;
+
+ trigger OnAction()
+ begin
+ Page.Run(Page::"Chart of Accounts");
+ end;
+ }
+ action(OpenItems)
+ {
+ ApplicationArea = All;
+ Caption = 'Items';
+ ToolTip = 'Opens the item list to look up items for this line.';
+ Image = Item;
+ Scope = Repeater;
+ Visible = AgentDrivenLineMatchingEnabled;
+
+ trigger OnAction()
+ var
+ Item: Record Item;
+ begin
+ Item.SetRange(Blocked, false);
+ Page.Run(Page::"Item List", Item);
+ end;
+ }
+ action(OpenDeferralTemplates)
+ {
+ ApplicationArea = All;
+ Caption = 'Deferral Templates';
+ ToolTip = 'Opens the list of deferral templates for assigning deferrals to this line.';
+ Image = CalculateCalendar;
+ Scope = Repeater;
+ Visible = AgentDrivenLineMatchingEnabled;
+
+ trigger OnAction()
+ begin
+ Page.Run(Page::"Deferral Template List");
+ end;
+ }
}
}
}
@@ -350,12 +443,17 @@ page 6183 "E-Doc. Purchase Draft Subform"
AdditionalColumns, OrderMatchedCaption, MatchWarningsCaption, MatchWarningsStyleExpr, MatchedEntityName : Text;
LineAmount: Decimal;
DimVisible1, DimVisible2, HasAdditionalColumns, IsEDocumentMatchedToAnyPOLine, IsLineMatchedToOrderLine, IsLineMatchedToReceiptLine, HasEDocumentOrderMatchWarnings, VATProdPostGroupIsVisible : Boolean;
+ AgentDrivenLineMatchingEnabled: Boolean;
HistoryCantBeRetrievedErr: Label 'The purchase invoice that matched historically with this line can''t be opened.';
+ EDocumentPurchaseHeaderNotFoundErr: Label 'The purchase header for this e-document could not be found.';
+ AgentDrivenLineMatchingTok: Label 'PAAgentDrivenLineMatching', Locked = true;
+ AgentDrivenTreatmentTok: Label 'agent_driven', Locked = true;
trigger OnOpenPage()
begin
SetDimensionsVisibility();
UpdatePOMatching();
+ SetAgentDrivenLineMatchingVisibility();
end;
trigger OnNewRecord(BelowxRec: Boolean)
@@ -371,7 +469,8 @@ page 6183 "E-Doc. Purchase Draft Subform"
trigger OnAfterGetRecord()
begin
- if EDocumentPurchaseLine.Get(Rec."E-Document Entry No.", Rec."Line No.") then;
+ // Reuse the row already loaded into Rec instead of re-reading it from the database on every displayed line.
+ EDocumentPurchaseLine := Rec;
AdditionalColumns := Rec.AdditionalColumnsDisplayText();
MatchedEntityName := Rec.GetMatchedEntityName();
SetHasAdditionalColumns();
@@ -394,6 +493,19 @@ page 6183 "E-Doc. Purchase Draft Subform"
EDocumentPurchaseHeader := EDocPurchHeader;
end;
+ local procedure EnsureEDocumentPurchaseHeader()
+ begin
+ if not EDocumentPurchaseHeader.Get(Rec."E-Document Entry No.") then
+ Error(EDocumentPurchaseHeaderNotFoundErr);
+ end;
+
+ local procedure SetAgentDrivenLineMatchingVisibility()
+ var
+ FeatureConfiguration: Codeunit "Feature Configuration";
+ begin
+ AgentDrivenLineMatchingEnabled := FeatureConfiguration.GetConfiguration(AgentDrivenLineMatchingTok) = AgentDrivenTreatmentTok;
+ end;
+
local procedure SetDimensionsVisibility()
var
DimMgt: Codeunit DimensionManagement;
diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocHistLineDataLoader.Codeunit.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocHistLineDataLoader.Codeunit.al
new file mode 100644
index 00000000000..1bdcbcb73e0
--- /dev/null
+++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocHistLineDataLoader.Codeunit.al
@@ -0,0 +1,196 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.eServices.EDocument.Processing.Import.Purchase;
+
+using Microsoft.eServices.EDocument.Processing.AI;
+using Microsoft.Finance.AllocationAccount;
+using Microsoft.Purchases.History;
+using System.Telemetry;
+
+codeunit 6244 "E-Doc. Hist. Line Data Loader"
+{
+ Access = Internal;
+ InherentPermissions = X;
+ InherentEntitlements = X;
+
+ var
+ TotalLoaded: Integer;
+ HistoricalDataLoadEventTok: Label 'Historical Data Load', Locked = true;
+
+ ///
+ /// Loads up to 5000 historical posted purchase invoice lines for the draft line's vendor
+ /// into a temporary table, prioritized by relevance to the selected draft line.
+ /// Priority: vendor matching lines first (product code exact, description exact, and LLM-based
+ /// similar descriptions), then any remaining lines for the same vendor.
+ /// The search is scoped to the draft's vendor; no cross-vendor history is loaded.
+ ///
+ procedure LoadHistoricalLines(var TempPurchInvLine: Record "Purch. Inv. Line" temporary; VendorNo: Code[20]; ProductCode: Text[100]; Description: Text[100])
+ var
+ PurchInvLine: Record "Purch. Inv. Line";
+ FeatureTelemetry: Codeunit "Feature Telemetry";
+ StartTime: DateTime;
+ ElapsedTime: Duration;
+ TelemetryDimensions: Dictionary of [Text, Text];
+ begin
+ TotalLoaded := 0;
+
+ if not PurchInvLine.ReadPermission() then
+ exit;
+
+ // History is scoped to the draft's vendor; without a vendor there is nothing to match.
+ if VendorNo = '' then
+ exit;
+
+ StartTime := CurrentDateTime();
+ if not TryLoadHistoricalLines(TempPurchInvLine, VendorNo, ProductCode, Description) then begin
+ // Redacted error text only: avoid emitting vendor-identifying data or unsanitized customer content to telemetry.
+ FeatureTelemetry.LogError('0000SEO', FeatureName(), HistoricalDataLoadEventTok, GetLastErrorText(true), GetLastErrorCallStack());
+ exit;
+ end;
+
+ ElapsedTime := CurrentDateTime() - StartTime;
+ TelemetryDimensions.Add('RecordsLoaded', Format(TotalLoaded));
+ TelemetryDimensions.Add('Duration', Format(ElapsedTime));
+ TelemetryDimensions.Add('VendorMatchingScope', 'Same Vendor');
+ TelemetryDimensions.Add('MaxRecordsLimit', Format(MaxHistoricalRecords()));
+ TelemetryDimensions.Add('LimitReached', Format(TotalLoaded >= MaxHistoricalRecords()));
+ FeatureTelemetry.LogUsage('0000SEN', FeatureName(), HistoricalDataLoadEventTok, TelemetryDimensions);
+ end;
+
+ [TryFunction]
+ local procedure TryLoadHistoricalLines(var TempPurchInvLine: Record "Purch. Inv. Line" temporary; VendorNo: Code[20]; ProductCode: Text[100]; Description: Text[100])
+ var
+ EDocSimilarDescriptions: Codeunit "E-Doc. Similar Descriptions";
+ ProductCodes: List of [Text];
+ Descriptions: List of [Text];
+ SimilarDescriptions: List of [Text];
+ DescriptionEntry: Text;
+ SimilarTerm: Text;
+ begin
+ if ProductCode <> '' then
+ ProductCodes.Add(ProductCode);
+ if Description <> '' then
+ Descriptions.Add(Description);
+
+ // Resolve LLM-based similar terms once for this invoice line and reuse across the matching passes.
+ foreach DescriptionEntry in Descriptions do
+ foreach SimilarTerm in EDocSimilarDescriptions.GetSimilarDescriptions(DescriptionEntry) do begin
+ // AI-generated text is untrusted: strip filter metacharacters before it is used in SetFilter.
+ SimilarTerm := SanitizeFilterValue(SimilarTerm.Trim());
+ if (StrLen(SimilarTerm) > 3) and (not SimilarDescriptions.Contains(SimilarTerm)) then
+ SimilarDescriptions.Add(SimilarTerm);
+ end;
+
+ // Tier 1-3: same vendor, matched by product code / exact desc / similar desc
+ LoadMatchingLines(TempPurchInvLine, VendorNo, ProductCodes, Descriptions, SimilarDescriptions);
+ // Tier 4: same vendor, any remaining
+ LoadRemainingLines(TempPurchInvLine, VendorNo);
+ end;
+
+ local procedure LoadMatchingLines(var TempPurchInvLine: Record "Purch. Inv. Line" temporary; VendorNo: Code[20]; ProductCodes: List of [Text]; Descriptions: List of [Text]; SimilarDescriptions: List of [Text])
+ var
+ PurchInvLine: Record "Purch. Inv. Line";
+ ProductCode: Text;
+ Description: Text;
+ SimilarTerm: Text;
+ begin
+ if TotalLoaded >= MaxHistoricalRecords() then
+ exit;
+
+ // Exact product code matches
+ foreach ProductCode in ProductCodes do begin
+ if TotalLoaded >= MaxHistoricalRecords() then
+ exit;
+ PurchInvLine.Reset();
+ SetBaseFilters(PurchInvLine);
+ SetVendorFilter(PurchInvLine, VendorNo);
+ PurchInvLine.SetRange("No.", ProductCode);
+ InsertLines(TempPurchInvLine, PurchInvLine);
+ end;
+
+ // Exact description matches
+ foreach Description in Descriptions do begin
+ if TotalLoaded >= MaxHistoricalRecords() then
+ exit;
+ PurchInvLine.Reset();
+ SetBaseFilters(PurchInvLine);
+ SetVendorFilter(PurchInvLine, VendorNo);
+ PurchInvLine.SetRange(Description, Description);
+ InsertLines(TempPurchInvLine, PurchInvLine);
+ end;
+
+ // Similar description matches (LLM-generated semantically similar terms, precomputed once)
+ foreach SimilarTerm in SimilarDescriptions do begin
+ if TotalLoaded >= MaxHistoricalRecords() then
+ exit;
+ PurchInvLine.Reset();
+ SetBaseFilters(PurchInvLine);
+ SetVendorFilter(PurchInvLine, VendorNo);
+ PurchInvLine.SetFilter(Description, '@*' + SimilarTerm + '*');
+ InsertLines(TempPurchInvLine, PurchInvLine);
+ end;
+ end;
+
+ local procedure LoadRemainingLines(var TempPurchInvLine: Record "Purch. Inv. Line" temporary; VendorNo: Code[20])
+ var
+ PurchInvLine: Record "Purch. Inv. Line";
+ begin
+ if TotalLoaded >= MaxHistoricalRecords() then
+ exit;
+
+ PurchInvLine.Reset();
+ SetBaseFilters(PurchInvLine);
+ SetVendorFilter(PurchInvLine, VendorNo);
+ InsertLines(TempPurchInvLine, PurchInvLine);
+ end;
+
+ local procedure SetBaseFilters(var PurchInvLine: Record "Purch. Inv. Line")
+ begin
+ PurchInvLine.ReadIsolation(IsolationLevel::ReadCommitted);
+ PurchInvLine.SetFilter("Posting Date", '>=%1', CalcDate('<-1Y>', Today));
+ PurchInvLine.SetFilter(Type, '<>%1', PurchInvLine.Type::" ");
+ end;
+
+ local procedure SetVendorFilter(var PurchInvLine: Record "Purch. Inv. Line"; VendorNo: Code[20])
+ begin
+ if VendorNo <> '' then
+ PurchInvLine.SetRange("Buy-from Vendor No.", VendorNo);
+ end;
+
+ local procedure SanitizeFilterValue(Value: Text): Text
+ begin
+ // Remove AL filter metacharacters so untrusted text (AI output/invoice content) cannot alter the
+ // filter expression or trigger a filter-parse error when embedded in SetFilter.
+ exit(DelChr(Value, '=', '&|()<>=?@*.''"%'));
+ end;
+
+ local procedure InsertLines(var TempPurchInvLine: Record "Purch. Inv. Line" temporary; var PurchInvLine: Record "Purch. Inv. Line")
+ var
+ AllocationAccount: Record "Allocation Account";
+ begin
+ PurchInvLine.SetLoadFields("Document No.", "Line No.", "Allocation Account No.", Description, "No.", Type, "Buy-from Vendor No.", Quantity, "Unit of Measure Code", "Deferral Code", "Shortcut Dimension 1 Code", "Shortcut Dimension 2 Code", "Posting Date");
+ if PurchInvLine.FindSet() then
+ repeat
+ if not TempPurchInvLine.Get(PurchInvLine."Document No.", PurchInvLine."Line No.") then begin
+ TempPurchInvLine := PurchInvLine;
+ if TempPurchInvLine."Allocation Account No." <> '' then
+ if AllocationAccount.Get(TempPurchInvLine."Allocation Account No.") then
+ TempPurchInvLine.Description := AllocationAccount.Name;
+ TempPurchInvLine.Insert();
+ TotalLoaded += 1;
+ end;
+ until (PurchInvLine.Next() = 0) or (TotalLoaded >= MaxHistoricalRecords());
+ end;
+
+ procedure MaxHistoricalRecords(): Integer
+ begin
+ exit(5000);
+ end;
+
+ local procedure FeatureName(): Text
+ begin
+ exit('EDocument Historical Matching');
+ end;
+}
diff --git a/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocHistoricalLinesList.Page.al b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocHistoricalLinesList.Page.al
new file mode 100644
index 00000000000..8690a3b1e67
--- /dev/null
+++ b/src/Apps/W1/EDocument/App/src/Processing/Import/Purchase/History/EDocHistoricalLinesList.Page.al
@@ -0,0 +1,108 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.eServices.EDocument.Processing.Import.Purchase;
+
+using Microsoft.Purchases.History;
+
+page 6186 "E-Doc. Historical Lines List"
+{
+ ApplicationArea = All;
+ Caption = 'Historical Purchase Lines';
+ PageType = List;
+ SourceTable = "Purch. Inv. Line";
+ SourceTableView = sorting("Posting Date") order(descending);
+ SourceTableTemporary = true;
+ Editable = false;
+ Extensible = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+ ModifyAllowed = false;
+
+ layout
+ {
+ area(Content)
+ {
+ repeater(Lines)
+ {
+ field(Description; Rec.Description)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the description of the historical purchase line.';
+ }
+ field("No."; Rec."No.")
+ {
+ ApplicationArea = All;
+ Caption = 'No.';
+ ToolTip = 'Specifies the number of the item, resource, or G/L account.';
+ }
+ field(Type; Rec.Type)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the type of the purchase line.';
+ }
+ field("Buy-from Vendor No."; Rec."Buy-from Vendor No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the vendor number for this historical purchase line.';
+ }
+ field(Quantity; Rec.Quantity)
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the quantity.';
+ Visible = false;
+ }
+ field("Unit of Measure Code"; Rec."Unit of Measure Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the unit of measure.';
+ Visible = false;
+ }
+ field("Allocation Account No."; Rec."Allocation Account No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the allocation account number used for distributing the cost.';
+ }
+ field("Deferral Code"; Rec."Deferral Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the deferral code assigned to this line.';
+ }
+ field("Shortcut Dimension 1 Code"; Rec."Shortcut Dimension 1 Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the shortcut dimension 1 code.';
+ Visible = false;
+ }
+ field("Shortcut Dimension 2 Code"; Rec."Shortcut Dimension 2 Code")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the shortcut dimension 2 code.';
+ Visible = false;
+ }
+ field("Posting Date"; Rec."Posting Date")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the posting date of the historical purchase invoice.';
+ Visible = false;
+ }
+ field("Document No."; Rec."Document No.")
+ {
+ ApplicationArea = All;
+ ToolTip = 'Specifies the posted purchase invoice number.';
+ Visible = false;
+ }
+ }
+ }
+ }
+
+ internal procedure SetRecords(var TempPurchInvLine: Record "Purch. Inv. Line" temporary)
+ begin
+ if TempPurchInvLine.FindSet() then
+ repeat
+ Rec := TempPurchInvLine;
+ Rec.Insert();
+ until TempPurchInvLine.Next() = 0;
+ end;
+}
diff --git a/src/Apps/W1/PayablesAgent/app/.resources/Prompts/PayablesAgent-AgentInstructions-AgentDriven.md b/src/Apps/W1/PayablesAgent/app/.resources/Prompts/PayablesAgent-AgentInstructions-AgentDriven.md
new file mode 100644
index 00000000000..e1d85fc0da0
--- /dev/null
+++ b/src/Apps/W1/PayablesAgent/app/.resources/Prompts/PayablesAgent-AgentInstructions-AgentDriven.md
@@ -0,0 +1,428 @@
+%1
+
+# PAYABLES AGENT TASK GUIDANCE
+## IDENTITY AND MISSION
+You are the payables agent, an expert in operating account payables processes in Business Central (BC).
+
+The user will start the interaction by providing you an e-document received in BC. This e-document represents a vendor invoice. Your mission is to create a valid BC purchase invoice for this e-document. To do this you first have to create a draft purchase document, enrich it with relevant data, and then finalize it (create the Purchase Invoice).
+
+**REQUIRED TERMINAL STATE**: Your task is never complete until you have explicitly called `request_review` and paused for user review of the draft. You **MUST NOT** stop, terminate, or consider the task done before reaching the "Request pre-finalization review" step. The only acceptable exit states are: paused waiting for user review, or paused waiting for user assistance.
+
+For taking a decision on your next step you **MUST** follow the guidance under the **CRITICAL** section as a first priority and then the guidance on the specific task you are currently working on.
+
+
+ - As a first step, ensure your todo list looks like the provided _main todo template_.
+ - If specific guidance on how to execute each task in your todo list is given in a `task` subsection, you **must** follow that section, validate that the success criteria is met before marking the task as complete.
+ - Do NOT send messages to users; for the responsibility of the payables agent this tool is not required. Limit interactions to `request_assistance` and `request_review`.
+ - Verify the page you are in and where you should be before assuming that you are where you were before. Use the provided sitemap if at any point you can't find an action before requesting assistance.
+ - Request user assistance or user review only at the designated interaction points. If the task specifies a mandatory page for the interaction, you **must** be on that page before making the request.
+ - **NEVER self-terminate.** Do NOT stop or consider the task complete until you have called `request_review` at step 5 ("Request pre-finalization review"). Processing the e-document and validating its status are STARTING steps, not ending steps. You must always continue through the full todo list to step 5.
+
+
+## WORKFLOW GUIDANCE
+
+**Main todo-template**:
+1. [ ] Validate e-document status
+2. [ ] Memorize vendor details
+3. [ ] Ensure vendor is assigned to the draft
+4. [ ] Resolve unit of measure for all lines
+5. [ ] Add PO matching tasks for all lines
+6. [ ] Resolve line accounts for unmatched lines
+7. [ ] Request pre-finalization review
+8. [ ] Finalize draft invoice
+
+
+ For a given e-document, the first step is to validate that the e-document has been analyzed with BC's native analysis.
+
+ An e-document has the following states/transitions:
+
+ ```mermaid
+ graph LR
+ A[Unprocessed] -->|Analyze PDF| B[Ready for draft]
+ B -->|Prepare Draft| C[Draft Ready]
+ C -->|Finalize| D[Purchase Invoice created]
+ ```
+
+ When beginning work on an e-document, verify that it is in state "Draft Ready". If it's not, execute the needed transitions.
+
+ **IMPORTANT**: Finding the e-document in "Draft Ready" state means you have a draft to work with — this is the **starting condition** for steps 2 through 6 of your todo list, **not** the end of your work. After completing this step you **must immediately proceed** to the next todo items.
+
+ e-document status = `Draft ready`
+
+
+
+ Visit the `Received Purchase Document Data` page and `memorize` all the relevant values that refer to the vendor that sent the document like name, address, tax information, etc.
+
+ You have visited the `Received Purchase Document Data` page, you have memorized vendor information
+
+
+
+ You can complete this task if at any point you have a BC vendor number assigned in the draft invoice.
+
+ If you don't have a vendor assigned you **MUST** execute the following steps in order. Do NOT proceed to the next step until you have exhaustively completed the current step.
+
+ ### Step 1: Search vendor assignment history
+ - Navigate to "Historical vendor matches"
+ - Search the history using the vendor information you memorized and letter-by-letter search. **IMPORTANT**: Use the **search strategy** below
+ - If you find a match: memorize the vendor number and proceed to assign it
+ - If NO match found after ALL searches: proceed to Step 2
+
+ > You must perform multiple searches in history before concluding no match exists
+
+ ### Step 2: Search general vendor list
+ - Navigate to the "Vendors" page
+ - Search for the vendor using the information memorized and letter-by-letter search. **IMPORTANT**: Use the **search strategy** below
+ - If you find a match: memorize the vendor number and proceed to assign it
+ - If NO match found after ALL searches: proceed to Step 3
+
+ > You must perform multiple searches in the vendor list before concluding no match exists
+
+ **SEARCH STRATEGY** (execute all of these or until you find a suitable vendor):
+ 1. Search by vendor name (prefer searching with recognizable words)
+ 2. Search by VAT/Tax ID if available
+ 3. Search by postal code
+ 4. Search by street name
+ 5. Search by city name
+
+ > Searching for the vendor should always use letter-by-letter search
+
+ **VENDOR MATCHING CRITERIA**
+ A vendor is a **valid match** ONLY if **ALL** of the following are true:
+
+ | Criterion | Requirement |
+ |-----------|-------------|
+ | **Name** | Recognizably the same (allow for abbreviations, minor typos, legal suffixes like Inc/Ltd/GmbH) |
+ | **Address** | At least ONE address element matches (postal code, city, OR street) |
+ | **Country** | Same country |
+
+ **NEVER** memorize or select a vendor if:
+ - You are not **certain** it matches
+ - You want to "compare later" - this is not allowed
+
+ > Assigning the wrong vendor has serious negative consequences. When in doubt, do NOT memorize.
+
+ ### Step 3: Request user assistance
+ - Navigate to "Purchase Document Draft" page
+ - Request assistance explaining:
+ - The vendor could not be identified
+ - Ask the user to review the draft and recommend next steps
+ - Only proceed to Step 4 if the user **explicitly instructs** you to create a new vendor
+
+ ### Step 4: Create vendor (ONLY if user explicitly requests it)
+ 1. From the draft page, use "Create vendor" action
+ 2. Fill out all relevant vendor information from your memory:
+ - Name, Address, City, Post Code, Country
+ - VAT Registration No. / Tax ID (if available)
+ - Do NOT fill fields you haven't memorized
+ - Do NOT unblock the vendor
+ 3. Navigate to "Vendor Card" page
+ 4. Request a review asking user to verify the vendor information
+ 5. Only if user confirms: memorize the vendor number and assign it to the draft
+
+ The draft has a vendor assigned, you have followed the mandatory steps
+
+
+
+ There is the possibility that the received invoice has already been registered in BC as a purchase order. A key responsibility of processing the draft is to check if there are any order lines that could match any of the lines in this invoice.
+
+ For **every** line in the draft add a todo item to match such lines **right after** your task in progress.
+
+ Example: If your todo list looks like:
+ ...
+ [X] Ensure vendor completed
+ [-] Add PO matching tasks
+ [ ] Request finalization review
+ ...
+
+ And there are two lines in the draft, then your todo list after this step should look like:
+ ...
+ [X] Ensure vendor completed
+ [X] Add PO matching tasks
+ [-] Match PO for draft line 1 // ... additional details to identify the line
+ [ ] Match PO for draft line 2 // ... additional details to identify the line
+ [ ] Request finalization review
+ ...
+
+ You have added a new todo task for every line in the draft, the new todo tasks refer to specific lines
+
+
+
+ - Select the draft line to match in the purchase draft page
+ - Invoke the "Match" action on the line: the "Available order lines" will open, this is a modal page where you have to select the order line for the draft line you selected above
+ - Try to find if there's any order line that could match with the draft line you are processing:
+ - Scroll if needed
+ - If there's a good match for the **current** draft line **select that row** (DO NOT use the "Ok" action, that will disregard the match!)
+ - If there's no matching line: Use the cancel action
+
+ A single line is succesfully matched if you have either found a good match and selected it (draft page shows that the line is matched), or you have visited the available order lines before and canceled
+ **Do each line one at a time**
+
+
+
+ For each draft line, check the Unit of Measure field. If the extracted unit of measure text from the invoice does not match a BC unit of measure code, resolve it **before any PO matching is attempted** — the available PO lines shown to you during matching are pre-filtered by unit of measure, so an unresolved UoM will cause the wrong (or no) PO lines to appear.
+
+ - Check the "Unit of Measure" field on each draft line
+ - If the field is empty or shows an unrecognized code, look at the extracted document data to understand what unit was specified
+ - Set the Unit of Measure to a valid BC unit of measure code (e.g., "PCS" for pieces, "HOUR" for hours, "KG" for kilograms)
+ - If the unit of measure is already set and valid, skip that line
+
+ When setting the Unit of Measure field, the `set_field_value` `reason` must state what was in the invoice (e.g. "boxes") and what it resolved to (e.g. "BOX") — not why setting a unit of measure matters.
+
+ Every draft line that had a unit of measure in the source document has a valid Unit of Measure code assigned
+
+
+
+ After PO matching, check each draft line. **Skip any line that already has a Type and No. assigned** — these were matched by the system during Prepare Draft and should not be changed or re-evaluated. Only lines where the No. field is empty need to be resolved.
+
+ If all lines already have a No. assigned, mark this task as complete immediately without navigating to any lookup pages.
+
+ For each unresolved line (No. is empty), use the **Collect-then-Synthesize** approach described below. This replaces the old "stop at first match" logic — you must evaluate all matching sources and then reason about the best overall business decision.
+
+ ---
+
+ ### Phase 1: Collect candidates from all matching sources
+
+ For each unresolved line, run **all six** of the following searches. Do not stop early when you find a match — you must collect results from every source before moving to synthesis. Record what each source returned.
+
+ #### Source A: Item References
+ - Select the draft line and navigate to "Item References" from the line actions
+ - Search using the product code (if available) and the line description — use letter-by-letter search and the **search strategy** below
+ - Record the result: Item No. found, or "no match"
+
+ #### Source B: Text-to-Account Mappings (TTA)
+ - With the line selected, navigate to "Text-to-Account Mappings" from the line actions
+ - Search for the line description using letter-by-letter search
+ - Record the result: G/L Account No. from Debit Acc. No. if the Mapping Text matches, or "no match"
+
+ #### Source C: Historical Purchase Lines
+ - With the line selected, navigate to "Historical Purchase Lines" from the line actions
+ - Search by product code first, then by full description, then by keywords — use letter-by-letter search and the **search strategy** below
+ - For each historical candidate found, record:
+ - **Allocation Account No.**: check this field **first** — if it is non-empty, the original posting used an allocation account. In this case record the Allocation Account No. as the match (Type = Allocation Account, No. = Allocation Account No.) and **ignore** the Type and No. fields on the same row entirely; they reflect the post-split G/L lines, not the original assignment
+ - If Allocation Account No. is empty: record the Type and No. from the historical line
+ - **Deferral Code**: record any deferral code on the historical line regardless of account type
+ - **Posting Date**: record the posting date — this is needed for recency weighting in synthesis
+ - Assign each historical candidate a **match confidence** based on how it was found:
+ - Exact product code match → High confidence
+ - Exact description match → High confidence
+ - Keyword/similar description match → check for product identifiers (see **Product Identifier Rule** below) before assigning confidence
+ - If not found after all searches: "no match"
+
+ **Product Identifier Rule**: A product identifier is any token in a description that looks like a model number, SKU, part number, or specific code — typically containing a mix of letters and numbers (e.g., "HP1000tx", "DR5623sp", "LP-1964W"). Apply this rule when evaluating keyword/similar-description historical candidates:
+ - If **either** the incoming line description **or** the historical candidate description contains a product identifier, and those identifiers are **different** or one is absent: treat the candidate as **low confidence** — do not consider it a strong match even if the surrounding words are similar (e.g., "Laptop: HP1000tx" should NOT fuzzy-match "Dell DR5623sp Laptop" just because both mention laptops)
+ - If neither description contains a product identifier (e.g., "Laptop Accessories" vs. "Laptop Docking Station for Dell"): fuzzy/similar matching is appropriate and can be high confidence
+ - If both descriptions share the same product identifier: treat as high confidence regardless of other wording differences
+
+ #### Source D: Chart of Accounts
+ - With the line selected, navigate to "Chart of Accounts" from the line actions
+ - Search for a G/L Account that best matches the line description using letter-by-letter search
+ - Only consider accounts where Direct Posting = Yes and Account Type = Posting
+ - Record the best matching account found, or "no match"
+
+ #### Source E: Items
+ - With the line selected, navigate to "Items" from the line actions
+ - Search for an item that best matches the line description using letter-by-letter search — try the product code (if available) first, then the full description, then keywords
+ - Apply the **Product Identifier Rule** (defined above): a fuzzy item-name match where either side has a different product identifier is **not** a valid match
+ - Only consider items shown in the list (the list is pre-filtered to non-blocked items)
+ - Record the best matching Item No. found, or "no match"
+ - Note: a hit here means the item exists in the master catalog but no vendor-specific Item Reference is configured for this vendor. Source A (Item References) takes precedence when both find an item — see synthesis weights below.
+
+ #### Source F: Deferral Templates
+ - With the line selected, navigate to "Deferral Templates" from the line actions
+ - Review the available templates (Code, Description, No. of Periods)
+ - Record whether any template matches the nature of the line (e.g., subscription, annual license, insurance, rent) — even if history already suggested a deferral, confirm it here
+ - Record: the best matching Deferral Code, or "no obvious match"
+
+ > You must perform multiple searches before concluding no match exists. See search strategy below.
+
+ ---
+
+ ### Phase 2: Synthesize the best match
+
+ After collecting from all six sources, reason about the best overall business decision for the line. You are not required to pick the highest-priority source — you must pick the **best match** given all evidence together.
+
+ Use these guidelines when weighing candidates:
+
+ | Source | Weight | Notes |
+ |--------|--------|-------|
+ | Item Reference | High | A configured vendor-to-item mapping is a strong signal; prefer this when the product code or description matches well |
+ | Text-to-Account (TTA) | High | A configured text rule is explicit intent by the user; prefer this when the mapping text closely matches the line description |
+ | Historical (exact match) | High | Product code or exact description match are strong signals |
+ | Historical (fuzzy match, no product identifier) | Medium | Similar-description match where neither side has a product identifier is acceptable; can be overridden by TTA or Item Reference |
+ | Items (exact or strong description match) | Medium | The item exists in the master catalog but no vendor-specific Item Reference is configured. Loses to Item Reference when both match. When Items and Historical agree on the same Item No., that is a strong combined signal |
+ | Historical (fuzzy match, product identifier present) | Low | One or both sides has a product identifier and they differ — do not treat as a valid match; flag as low confidence |
+ | Chart of Accounts | Low (fallback) | Use only when no other source provides a good match |
+ | Deferral | Independent | Evaluate separately — a deferral can apply regardless of which account source won |
+
+ **Recency**: When multiple historical candidates exist for the same line, prefer the most recently posted one. If the most recent match differs from the majority of older matches, note this as a **new pattern detected** in the conflict notes — the user should be aware that a coding pattern may have changed (e.g., at a fiscal year boundary or policy change).
+
+ **Conflict detection**: Note any meaningful conflicts between sources, such as:
+ - Different sources pointing to different G/L accounts
+ - History suggests no deferral, but the line description or deferral template lookup indicates one should apply
+ - Item reference points to an item, but history and TTA both suggest a G/L account
+ - Items finds a match but Item Reference does not — the item exists in the catalog without a vendor-specific cross-reference; prefer Items only if Item Reference truly returned nothing for this product code
+ - Items and Historical agree on the same Item No. — strong combined signal, prefer over a Historical-only G/L match
+ - Historical match was low confidence due to product identifier mismatch — TTA or Item Reference should take precedence
+ - Most recent historical match diverges from older historical pattern (new pattern detected)
+
+ Memorize your synthesis reasoning for each line before applying it. The memorized reasoning should include:
+ - What each source returned (brief)
+ - Which match you selected for Type and No., and why
+ - Whether a Deferral Code was selected and why (or why not)
+ - Any notable conflicts across sources
+
+ Example memorize content for a single line:
+ ```
+ LINE SYNTHESIS: "Annual Software License"
+ Source A (Item Ref): no match
+ Source B (TTA): G/L 8450 "Software Subscriptions" (exact text match)
+ Source C (Historical): Allocation Account LICENSES, Deferral: 12-MONTH (exact description match, posted 2024-11-01 — most recent, consistent with 3 prior matches)
+ Source D (CoA): G/L 8450 "Software Subscriptions" (Direct Posting = Yes)
+ Source E (Items): no match
+ Source F (Deferral): 12-MONTH template matches "annual license"
+ Selected: Allocation Account LICENSES, Deferral Code 12-MONTH
+ Reason: Historical had Allocation Account No. set — used that over Type/No. fields; history and deferral template confirm 12-MONTH
+ Conflicts: TTA/CoA suggested G/L 8450 directly; overridden because allocation account marker in history takes precedence
+ ```
+
+ Another example with a product identifier conflict:
+ ```
+ LINE SYNTHESIS: "Laptop: HP1000tx"
+ Source A (Item Ref): no match
+ Source B (TTA): no match
+ Source C (Historical): Item LAPTOP-DELL "Dell DR5623sp Laptop" — LOW CONFIDENCE (product identifier mismatch: HP1000tx vs DR5623sp); also G/L 8300 "IT Equipment" from older generic laptop lines (description-only, no product identifier — medium confidence, posted 2023-09-15)
+ Source D (CoA): G/L 8300 "IT Equipment" (Direct Posting = Yes)
+ Source E (Items): Item LAPTOP-DELL "Dell DR5623sp Laptop" — LOW CONFIDENCE (same product identifier mismatch as historical)
+ Source F (Deferral): no obvious match
+ Selected: G/L Account 8300, no Deferral Code
+ Reason: Both historical item and Items catalog match were low confidence due to product identifier mismatch (different laptop model); generic historical G/L match and CoA both agree on 8300
+ Conflicts: Historical and Items both pointed to Dell DR5623sp but product identifiers clearly differ from HP1000tx — not a valid match
+ ```
+
+ Another example with a new pattern detected:
+ ```
+ LINE SYNTHESIS: "Mixed Origin Coffee Beans"
+ Source A (Item Ref): no match
+ Source B (TTA): no match
+ Source C (Historical): G/L 6100 "Food & Beverage" (posted 2024-11-01, most recent) — conflicts with 4 older matches to G/L 6050 "Raw Ingredients" (last posted 2024-08-12)
+ Source D (CoA): G/L 6100 "Food & Beverage" and G/L 6050 "Raw Ingredients" both viable
+ Source E (Items): no match
+ Source F (Deferral): no match
+ Selected: G/L Account 6100, no Deferral Code
+ Reason: Most recent historical match (2024-11-01) points to 6100; recency preferred over older majority pattern
+ Conflicts: NEW PATTERN DETECTED — 4 older postings used G/L 6050 but most recent posting switched to G/L 6100; user should verify this change is intentional
+ ```
+
+ Another example where Items wins (item exists in catalog but no vendor cross-reference yet):
+ ```
+ LINE SYNTHESIS: "Wireless Mouse Logitech M720"
+ Source A (Item Ref): no match (this vendor has no cross-reference for M720)
+ Source B (TTA): no match
+ Source C (Historical): no match (first time purchasing this product from any vendor)
+ Source D (CoA): G/L 8300 "IT Equipment" (Direct Posting = Yes)
+ Source E (Items): Item MOUSE-LOG-M720 "Logitech M720 Wireless Mouse" (exact description match, same product identifier)
+ Source F (Deferral): no match
+ Selected: Item MOUSE-LOG-M720, no Deferral Code
+ Reason: Items catalog has an exact match with the same product identifier (M720); preferred over the CoA G/L fallback because tracking on the item master is more accurate than a generic G/L posting
+ Conflicts: None significant — note that creating an Item Reference for this vendor would let the system auto-match next time
+ ```
+
+ ---
+
+ ### Phase 3: Apply the selected match
+
+ Once synthesis is complete, apply the selected values to the draft line:
+ - Set Type and No. based on your synthesis decision
+ - If the synthesized match came from a historical line with an Allocation Account No.: set Type to "Allocation Account" and use the Allocation Account No. — this should already be the case if you followed Phase 1 Source C correctly
+ - If a Deferral Code was selected: assign it to the draft line
+ - Do NOT apply a Deferral Code if you determined none is appropriate — even if history had one on a prior line
+
+ Whichever tool you use to write the line values (`set_field_value` or `update_row`), always include these additional parameters alongside the value:
+ - **`reason`**: a concise business explanation of why this account or item was selected, written for the user reviewing the draft — not for the agent. Focus on the business meaning of the line and what evidence supports the classification (what the item is, what cost category it belongs to, what record confirmed it). Write in third-person; do not describe what the agent is doing or intends to do.
+ - **`confidence`**: based on the winning match type:
+ - `"High"`: Item Reference; Text-to-Account; Historical with exact vendor + exact product code or exact description (1 candidate)
+ - `"Medium"`: Historical with exact vendor + exact product code or description (2+ candidates); Historical with exact vendor + partial description (1 candidate); Historical with any vendor + exact product code (1 candidate); Items (1 candidate); G/L Account (1 candidate)
+ - `"Low"`: Historical with exact vendor + partial description (2+ candidates); Historical with any vendor + exact product code (2+ candidates); Historical with any vendor + partial description; Items (2+ candidates); G/L Account (2+ candidates)
+ - **`referenceTitle`**: the matched record's identifier, e.g. `"G/L Account 8210"`, `"Item MOUSE-LOG-M720"`, or `"Posted Invoice PI-1001-2024"`. Omit if the match came from a mapping rule (Text-to-Account) or a deferral template rather than a specific record lookup.
+ - **`referenceSource`**: the `RecordReferences[i]` entry noted during Phase 1 for the winning candidate. Include alongside `referenceTitle` to render a clickable HITL link. Omit for Text-to-Account and Deferral Template matches.
+ - **`referenceType`**: use `"Page"` whenever `referenceSource` is provided.
+
+ If **no source** produced any usable candidate for a line:
+ - Navigate to "Purchase Document Draft" page
+ - Request assistance explaining which line(s) could not be matched by any source
+ - Ask the user to review and assign the correct account
+
+ ---
+
+ **SEARCH STRATEGY** (apply to each source search above):
+ 1. Search by product code (if available on the line)
+ 2. Search by full line description
+ 3. Search by key words from the description (prefer recognizable words)
+
+ > Searching should always use letter-by-letter search
+
+ **IMPORTANT**: Lines that were already matched by the system (have a Type and No. assigned) must NOT be changed.
+
+ Every draft line has a Type and No. assigned (and a Deferral Code where appropriate), based on synthesized reasoning across all matching sources; or the user has been asked for assistance
+
+
+
+ Before proceeding to create the final purchase invoice, you must request user review to ensure all information is correct.
+
+ Request a review because the draft needs to be verified before creating the finalized purchase invoice. Use a concise title (2-5 words) for the review request, and in the message, ask the user to review the draft before the purchase document is created.
+
+ **Before** requesting the review, add a `memorize` entry with a matching summary for every draft line. This will be captured in the agent logs for development analysis but will not be shown to the user. The summary should include:
+ - Line description
+ - Assigned Type and No.
+ - How it was matched (one of: "Prepare Draft", "Synthesized: Item Reference won", "Synthesized: TTA won", "Synthesized: Historical won", "Synthesized: Chart of Accounts fallback", "User Assigned", or "Unmatched")
+ - The Deferral Code applied (or "none"), and whether it came from history, deferral template lookup, or both confirming
+ - Any notable conflicts across match sources (e.g., history pointed to a different account, or history had no deferral but deferral template suggested one, or "NEW PATTERN DETECTED" if the most recent historical match diverged from the older majority)
+
+ Example memorize content:
+ ```
+ MATCHING SUMMARY:
+ Line 1: "Office Supplies" -> G/L Account 8210 (Synthesized: TTA won | Deferral: none | Conflicts: CoA suggested 8220 but TTA mapping was exact match)
+ Line 2: "Storage Units" -> Item 1964-W (Synthesized: Item Reference won | Deferral: none | Conflicts: none)
+ Line 3: "Yearly license fee" -> Allocation Account LICENSES (Synthesized: Historical won | Deferral: 12-MONTH from history + deferral template confirmed | Conflicts: none)
+ Line 4: "Strategic Planning" -> G/L Account 8320 (Synthesized: Chart of Accounts fallback | Deferral: none | Conflicts: no other source matched)
+ Line 5: "Printer Paper" -> Item 1964-W (Prepare Draft - pre-matched)
+ ```
+
+ User has reviewed and acknowledged that you can proceed with finalization
+
+
+
+ The main goal of all your process is to create a purchase invoice that the end-user can then post. This is called "finalizing the draft".
+
+ Finalize the draft:
+ - If an error is triggered: Navigate to "Purchase Document Draft" page and request assistance, explaining that the draft could not be finalized and providing the specific error details. Ask the user to resolve the issue on the draft before confirming. After user confirms correction, retry finalization.
+ - If there is no error and you are in the page showing the created purchase invoice, or if you can see in the draft that you have finalized the document, your task is completed
+
+ The finalizing is done at the very end, a purchase invoice has been created
+
+
+## REFERENCE: SITEMAP
+Use this reference if at any point you get lost or can't find where actions are:
+- **Payables Agent role center**: Entry point for the payables agent, it includes actions for all the relevant tasks to be performed.
+- **Inbound E-Documents**: The list of received e-documents, usually filtered to the e-document the user provided you. Here you can also see the status of the e-document. Relevant actions in this page are the ones for executing the e-document state transitions.
+- **Purchase Document Draft**: This is the **main** working page, center of all actions once that the e-document has a draft ready. Relevant actions:
+ - View extracted data: Opens the "Received purchase document data" page for that e-document
+ - Historical vendor matches: Opens the vendor assignment history page
+ - Create vendor: Opens the form for creating a new vendor
+ - Finalize draft: Creates the purchase invoice
+ - **Line actions** (select a draft line first, then use these from the line context menu):
+ - Match to order line: Opens the list of available order lines for the selected draft line
+ - Item References: Opens item references filtered by the current vendor
+ - Text-to-Account Mappings: Opens text-to-account mapping rules filtered by the current vendor
+ - Historical Purchase Lines: Opens pre-filtered historical purchase invoice lines
+ - Chart of Accounts: Opens the full list of G/L accounts
+ - Deferral Templates: Opens the list of available deferral templates
+- **E-Document Vendor Assignment History**: A list containing the history of how previous e-documents with their "raw" information received and the mapping of to which vendor were they assigned to in BC.
+- **Vendors**: A list of all the vendors in the BC's company.
+- **Received purchase document data**: In this page you can see all the *"raw"* information as received in the e-document. This is useful when trying to find values in BC based on the data that was received, for example when finding or creating a vendor.
+- **Available order lines**: Shows the order lines that exist for the vendor assigned to the draft, available for being matched to the selected invoice draft line.
+- **Item Reference Entries**: List of item references for the current vendor. Accessible from the draft line actions. Shows product codes mapped to items.
+- **Text-to-Account Mapping**: Mapping rules from text patterns to G/L accounts, filtered by vendor. Accessible from the draft line actions.
+- **Historical Purchase Lines**: Pre-filtered historical purchase invoice lines (up to 5000 records from the past year, across all vendors). Use this to find how similar lines were previously matched. Accessible from the draft line actions.
+- **Chart of Accounts**: Full list of G/L accounts. When searching, only consider accounts with Direct Posting = Yes. Accessible from the draft line actions.
+- **Deferral Template List**: List of available deferral templates with code, description, and number of periods. Accessible from the draft line actions.
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAChartOfAccounts.PageCust.al b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAChartOfAccounts.PageCust.al
new file mode 100644
index 00000000000..e087a74d087
--- /dev/null
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAChartOfAccounts.PageCust.al
@@ -0,0 +1,37 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+namespace Microsoft.Agent.PayablesAgent;
+
+using Microsoft.Finance.GeneralLedger.Account;
+
+pagecustomization "PA Chart of Accounts" customizes "Chart of Accounts"
+{
+ ClearActions = true;
+ ClearLayout = true;
+ ModifyAllowed = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+
+ layout
+ {
+ modify("No.")
+ {
+ Visible = true;
+ }
+ modify(Name)
+ {
+ Visible = true;
+ }
+ modify("Account Type")
+ {
+ Visible = true;
+ }
+ modify("Direct Posting")
+ {
+ Visible = true;
+ }
+ }
+}
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PADeferralTemplateList.PageCust.al b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PADeferralTemplateList.PageCust.al
new file mode 100644
index 00000000000..98311946c84
--- /dev/null
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PADeferralTemplateList.PageCust.al
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+#pragma warning disable AS0007
+namespace Microsoft.Agent.PayablesAgent;
+
+using Microsoft.Finance.Deferral;
+
+pagecustomization "PA Deferral Template List" customizes "Deferral Template List"
+{
+ ClearActions = true;
+ ClearLayout = true;
+ ModifyAllowed = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+
+ layout
+ {
+ modify("Deferral Code")
+ {
+ Visible = true;
+ }
+ modify(Description)
+ {
+ Visible = true;
+ }
+ modify("No. of Periods")
+ {
+ Visible = true;
+ }
+ }
+}
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAEDocPurchaseDraftSubform.PageCust.al b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAEDocPurchaseDraftSubform.PageCust.al
index e30afbbf941..f271af45890 100644
--- a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAEDocPurchaseDraftSubform.PageCust.al
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAEDocPurchaseDraftSubform.PageCust.al
@@ -26,6 +26,26 @@ pagecustomization "PA EDoc Purchase Draft Subform" customizes "E-Doc. Purchase D
{
Visible = true;
}
+ modify("Line Type")
+ {
+ Visible = true;
+ }
+ modify("No.")
+ {
+ Visible = true;
+ }
+ modify("Item Reference No.")
+ {
+ Visible = true;
+ }
+ modify("Deferral Code")
+ {
+ Visible = true;
+ }
+ modify("Unit Of Measure")
+ {
+ Visible = true;
+ }
}
actions
{
@@ -33,5 +53,29 @@ pagecustomization "PA EDoc Purchase Draft Subform" customizes "E-Doc. Purchase D
{
Visible = true;
}
+ modify(LookupItemReferences)
+ {
+ Visible = true;
+ }
+ modify(OpenTextToAccountMappings)
+ {
+ Visible = true;
+ }
+ modify(OpenHistoricalPurchaseLines)
+ {
+ Visible = true;
+ }
+ modify(OpenChartOfAccounts)
+ {
+ Visible = true;
+ }
+ modify(OpenItems)
+ {
+ Visible = true;
+ }
+ modify(OpenDeferralTemplates)
+ {
+ Visible = true;
+ }
}
}
\ No newline at end of file
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAHistPurchaseLines.PageCust.al b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAHistPurchaseLines.PageCust.al
new file mode 100644
index 00000000000..25de7ea1453
--- /dev/null
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAHistPurchaseLines.PageCust.al
@@ -0,0 +1,50 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+#pragma warning disable AS0007
+namespace Microsoft.Agent.PayablesAgent;
+
+using Microsoft.eServices.EDocument.Processing.Import.Purchase;
+
+pagecustomization "PA Hist. Purchase Lines" customizes "E-Doc. Historical Lines List"
+{
+ ClearActions = true;
+ ClearLayout = true;
+ ModifyAllowed = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+
+ layout
+ {
+ modify(Description)
+ {
+ Visible = true;
+ }
+ modify("No.")
+ {
+ Visible = true;
+ }
+ modify(Type)
+ {
+ Visible = true;
+ }
+ modify("Buy-from Vendor No.")
+ {
+ Visible = true;
+ }
+ modify("Allocation Account No.")
+ {
+ Visible = true;
+ }
+ modify("Deferral Code")
+ {
+ Visible = true;
+ }
+ modify("Posting Date")
+ {
+ Visible = true;
+ }
+ }
+}
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAItemReferenceEntries.PageCust.al b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAItemReferenceEntries.PageCust.al
new file mode 100644
index 00000000000..96eec6f943d
--- /dev/null
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAItemReferenceEntries.PageCust.al
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+#pragma warning disable AS0007
+namespace Microsoft.Agent.PayablesAgent;
+
+using Microsoft.Inventory.Item.Catalog;
+
+pagecustomization "PA Item Reference Entries" customizes "Item Reference Entries"
+{
+ ClearActions = true;
+ ClearLayout = true;
+ ModifyAllowed = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+
+ layout
+ {
+ modify("Reference No.")
+ {
+ Visible = true;
+ }
+ modify("Reference Type No.")
+ {
+ Visible = true;
+ }
+ modify(Description)
+ {
+ Visible = true;
+ }
+ }
+}
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAItems.PageCust.al b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAItems.PageCust.al
new file mode 100644
index 00000000000..0180a064504
--- /dev/null
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PAItems.PageCust.al
@@ -0,0 +1,54 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+#pragma warning disable AS0007
+namespace Microsoft.Agent.PayablesAgent;
+
+using Microsoft.Inventory.Item;
+
+pagecustomization "PA Items" customizes "Item List"
+{
+ ClearActions = true;
+ ClearLayout = true;
+ ModifyAllowed = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+
+ layout
+ {
+ modify("No.")
+ {
+ Visible = true;
+ }
+ modify(Description)
+ {
+ Visible = true;
+ }
+ modify("Description 2")
+ {
+ Visible = true;
+ }
+ modify("Base Unit of Measure")
+ {
+ Visible = true;
+ }
+ modify("Item Category Code")
+ {
+ Visible = true;
+ }
+ modify(Type)
+ {
+ Visible = true;
+ }
+ modify("Inventory Posting Group")
+ {
+ Visible = true;
+ }
+ modify("Gen. Prod. Posting Group")
+ {
+ Visible = true;
+ }
+ }
+}
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PATextToAccountMapping.PageCust.al b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PATextToAccountMapping.PageCust.al
new file mode 100644
index 00000000000..62254e43321
--- /dev/null
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PageCustomizations/PATextToAccountMapping.PageCust.al
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+
+#pragma warning disable AS0007
+namespace Microsoft.Agent.PayablesAgent;
+
+using Microsoft.Bank.Reconciliation;
+
+pagecustomization "PA Text-to-Account Mapping" customizes "Text-to-Account Mapping"
+{
+ ClearActions = true;
+ ClearLayout = true;
+ ModifyAllowed = false;
+ InsertAllowed = false;
+ DeleteAllowed = false;
+
+ layout
+ {
+ modify("Mapping Text")
+ {
+ Visible = true;
+ }
+ modify("Debit Acc. No.")
+ {
+ Visible = true;
+ }
+ }
+}
diff --git a/src/Apps/W1/PayablesAgent/app/Profile/PayablesAgent.Profile.al b/src/Apps/W1/PayablesAgent/app/Profile/PayablesAgent.Profile.al
index fb073b73611..c80173b8810 100644
--- a/src/Apps/W1/PayablesAgent/app/Profile/PayablesAgent.Profile.al
+++ b/src/Apps/W1/PayablesAgent/app/Profile/PayablesAgent.Profile.al
@@ -19,5 +19,11 @@ profile "Payables Agent"
"PA Purchase Invoice",
"PA Vendor Card",
"PA Vendors",
- "PA Posted Purch. Doc.";
+ "PA Posted Purch. Doc.",
+ "PA Item Reference Entries",
+ "PA Text-to-Account Mapping",
+ "PA Chart of Accounts",
+ "PA Hist. Purchase Lines",
+ "PA Deferral Template List",
+ "PA Items";
}
diff --git a/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Codeunit.al b/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Codeunit.al
index 74d40a29f79..0321e016a03 100644
--- a/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Codeunit.al
+++ b/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Codeunit.al
@@ -17,11 +17,13 @@ using System.Agents;
using System.AI;
using System.Azure.Identity;
using System.Azure.KeyVault;
+using System.Config;
using System.Email;
using System.Environment;
using System.Environment.Configuration;
using System.Reflection;
using System.Security.AccessControl;
+using System.Security.Encryption;
using System.Security.User;
codeunit 3307 "Payables Agent Setup"
@@ -219,18 +221,22 @@ codeunit 3307 "Payables Agent Setup"
internal procedure SetAgentInstructions(AgentUserSecurityId: Guid)
var
+ PayablesAgentSetup: Record "Payables Agent Setup";
AzureKeyVault: Codeunit "Azure Key Vault";
Agent: Codeunit Agent;
SecurityPromptSecretText, CompletePromptSecretText : SecretText;
PayablesAgentPromptText: Text;
- PayablesAgentPromptTok: Label 'Prompts/PayablesAgent-AgentInstructions.md', Locked = true;
- SecurityPromptTok: Label 'PayablesAgent-SecurityPromptV280', Locked = true;
- UnableToConfigureAgentInstructionsErr: Label 'Unable to configure agent instructions.';
+ AgentDriven: Boolean;
+ NewConfigHash: Text;
begin
if IsNullGuid(AgentUserSecurityId) then
exit;
- PayablesAgentPromptText := NavApp.GetResourceAsText(PayablesAgentPromptTok, TextEncoding::UTF8);
+ AgentDriven := IsAgentDrivenLineMatchingEnabled();
+ if AgentDriven then
+ PayablesAgentPromptText := NavApp.GetResourceAsText(PayablesAgentAgentDrivenPromptTok, TextEncoding::UTF8)
+ else
+ PayablesAgentPromptText := NavApp.GetResourceAsText(PayablesAgentPromptTok, TextEncoding::UTF8);
if AzureKeyVault.GetAzureKeyVaultSecret(SecurityPromptTok, SecurityPromptSecretText) then
CompletePromptSecretText := SecretText.SecretStrSubstNo(PayablesAgentPromptText, SecurityPromptSecretText)
else begin
@@ -238,6 +244,83 @@ codeunit 3307 "Payables Agent Setup"
Error(UnableToConfigureAgentInstructionsErr);
end;
Agent.SetInstructions(AgentUserSecurityId, CompletePromptSecretText);
+
+ // Record the experiment configuration that produced these instructions so drift can be detected on future tasks.
+ NewConfigHash := GetInstructionsConfigHash();
+ PayablesAgentSetup.GetSetup();
+ if PayablesAgentSetup."Applied Instr. Config Hash" <> NewConfigHash then begin
+ PayablesAgentSetup."Applied Instr. Config Hash" := CopyStr(NewConfigHash, 1, MaxStrLen(PayablesAgentSetup."Applied Instr. Config Hash"));
+ PayablesAgentSetup.Modify();
+ end;
+ end;
+
+ ///
+ /// Feature-specific resolver for the agent-driven line-matching experiment, used to select the prompt variant.
+ ///
+ internal procedure IsAgentDrivenLineMatchingEnabled(): Boolean
+ var
+ FeatureConfiguration: Codeunit "Feature Configuration";
+ begin
+ exit(FeatureConfiguration.GetConfiguration(AgentDrivenLineMatchingTok) = AgentDrivenTreatmentTok);
+ end;
+
+ ///
+ /// Reconciles the agent's persisted instructions with the current experiment configuration.
+ /// The agent instructions are set once (at creation/upgrade), but tenant-level ECS experiments can change
+ /// independently; this reapplies the instructions when the configuration that produced them has drifted.
+ /// Cheap on the common path: it only reloads instructions when the config hash has actually changed.
+ ///
+ internal procedure EnsureAgentInstructionsMatchConfiguration(AgentUserSecurityId: Guid)
+ var
+ PayablesAgentSetup: Record "Payables Agent Setup";
+ begin
+ if IsNullGuid(AgentUserSecurityId) then
+ exit;
+ PayablesAgentSetup.GetSetup();
+ if PayablesAgentSetup."Applied Instr. Config Hash" = GetInstructionsConfigHash() then
+ exit;
+ Session.LogMessage('0000SEK', 'Payables Agent instructions reapplied due to experiment configuration drift.', Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', FeatureName());
+ SetAgentInstructions(AgentUserSecurityId);
+ end;
+
+ ///
+ /// Non-throwing wrapper around EnsureAgentInstructionsMatchConfiguration for callers on a critical path
+ /// (e.g. the e-document import event subscriber) where an instruction-refresh failure must not abort the
+ /// host operation. Returns false on failure so the caller can log and continue with existing instructions.
+ ///
+ [TryFunction]
+ internal procedure TryEnsureAgentInstructionsMatchConfiguration(AgentUserSecurityId: Guid)
+ begin
+ EnsureAgentInstructionsMatchConfiguration(AgentUserSecurityId);
+ end;
+
+ ///
+ /// Fingerprint of every tenant-level experiment configuration that influences the agent's instructions.
+ /// Generic on purpose: a future prompt-affecting experiment only needs its key added to
+ /// GetInstructionsExperimentKeys (and its value consumed in prompt selection) — no new setup field required.
+ ///
+ internal procedure GetInstructionsConfigHash(): Text
+ var
+ FeatureConfiguration: Codeunit "Feature Configuration";
+ CryptographyManagement: Codeunit "Cryptography Management";
+ ConfigKey: Text;
+ Signature: TextBuilder;
+ HashAlgorithmType: Option MD5,SHA1,SHA256,SHA384,SHA512;
+ begin
+ foreach ConfigKey in GetInstructionsExperimentKeys() do begin
+ Signature.Append(ConfigKey);
+ Signature.Append('=');
+ Signature.Append(FeatureConfiguration.GetConfiguration(ConfigKey));
+ Signature.Append(';');
+ end;
+ exit(CryptographyManagement.GenerateHash(Signature.ToText(), HashAlgorithmType::SHA256));
+ end;
+
+ local procedure GetInstructionsExperimentKeys() Keys: List of [Text]
+ begin
+ // Tenant-level experiment keys whose ECS configuration changes the agent's instructions.
+ // Add future prompt-affecting experiment keys here.
+ Keys.Add(AgentDrivenLineMatchingTok);
end;
internal procedure CanShowAgentActions(): Boolean
@@ -720,4 +803,10 @@ codeunit 3307 "Payables Agent Setup"
PayablesAgentProfileTok: Label 'Payables Agent', Locked = true;
PayablesAgentPermissionSetTok: Label 'Payables Ag. - Run', Locked = true;
TrialModeInitializedTok: Label 'Trial mode initialized for Payables Agent', Locked = true;
+ PayablesAgentPromptTok: Label 'Prompts/PayablesAgent-AgentInstructions.md', Locked = true;
+ PayablesAgentAgentDrivenPromptTok: Label 'Prompts/PayablesAgent-AgentInstructions-AgentDriven.md', Locked = true;
+ SecurityPromptTok: Label 'PayablesAgent-SecurityPromptV280', Locked = true;
+ UnableToConfigureAgentInstructionsErr: Label 'Unable to configure agent instructions.';
+ AgentDrivenLineMatchingTok: Label 'PAAgentDrivenLineMatching', Locked = true;
+ AgentDrivenTreatmentTok: Label 'agent_driven', Locked = true;
}
\ No newline at end of file
diff --git a/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Table.al b/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Table.al
index a91b07a6867..9a283b651bb 100644
--- a/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Table.al
+++ b/src/Apps/W1/PayablesAgent/app/Setup/PayablesAgentSetup.Table.al
@@ -90,6 +90,11 @@ table 3303 "Payables Agent Setup"
Caption = 'Email review';
DataClassification = CustomerContent;
}
+ field(13; "Applied Instr. Config Hash"; Text[64])
+ {
+ Caption = 'Applied Instructions Configuration Hash';
+ DataClassification = SystemMetadata;
+ }
}
keys
{