diff --git a/.github/workflows/claude-evaluation.yml b/.github/workflows/claude-evaluation.yml index ab819c510..c55d13feb 100644 --- a/.github/workflows/claude-evaluation.yml +++ b/.github/workflows/claude-evaluation.yml @@ -24,6 +24,8 @@ on: - "bug-fix" - "test-generation" - "code-review" + - "extensibility-request-implement" + - "extensibility-request-triage" test-run: description: "Indicate this is a test run (with few entries)" required: false diff --git a/.github/workflows/copilot-evaluation.yml b/.github/workflows/copilot-evaluation.yml index 7df211423..f8ae07197 100644 --- a/.github/workflows/copilot-evaluation.yml +++ b/.github/workflows/copilot-evaluation.yml @@ -29,6 +29,8 @@ on: - "bug-fix" - "test-generation" - "code-review" + - "extensibility-request-implement" + - "extensibility-request-triage" test-run: description: "Indicate this is a test run (with few entries)" required: false diff --git a/CATEGORIES.md b/CATEGORIES.md index c18654899..5726ce72a 100644 --- a/CATEGORIES.md +++ b/CATEGORIES.md @@ -1,8 +1,8 @@ # Adding a New Category -BC-Bench is **category-based**. A category is a distinct evaluation scenario: `bug-fix` asks an agent to patch buggy code, `test-generation` asks it to write reproduction tests, `code-review` asks it to flag issues in a diff, and `nl2al` asks it to turn a natural-language spec into AL code. +BC-Bench is **category-based**. A category is a distinct evaluation scenario: `bug-fix` asks an agent to patch buggy code, `test-generation` asks it to write reproduction tests, `code-review` asks it to flag issues in a diff, `nl2al` asks it to turn a natural-language spec into AL code, `extensibility-request-implement` asks it to implement an approved extensibility request (e.g. add an integration event) in an existing repo, and `extensibility-request-triage` asks it to triage an extensibility request (emit managed labels, an advisory comment, and an open/closed decision). -Categories also differ in how they're scored and run. `bug-fix` and `test-generation` are execution-based: they build and run AL code, so they need a BC container. `code-review` and `nl2al` both leverage LLM-as-a-judge: `code-review` scores precision/recall/F1 of flagged issues against expected findings (an LLM judge only matches comments), and `nl2al` has an LLM grade the output against an LMChecklist. The `EvaluationCategory` properties (`requires_container`, `runner`, `evaluators`, `core_score`) capture these differences for the workflows. +Categories also differ in how they're scored and run. `bug-fix` and `test-generation` are execution-based: they build and run AL code, so they need a BC container. `code-review`, `nl2al`, `extensibility-request-implement`, and `extensibility-request-triage` all leverage LLM-as-a-judge: `code-review` scores precision/recall/F1 of flagged issues against expected findings (an LLM judge only matches comments), and `nl2al`, `extensibility-request-implement`, and `extensibility-request-triage` have an LLM grade the agent output against an LMChecklist (for triage, the checklist encodes the expected managed labels, open/closed state, and the substance of the advisory comment). `extensibility-request-implement` and `extensibility-request-triage` are the first of a planned family of extensibility categories (a further sibling such as `ext-advisor` is not yet implemented). The `EvaluationCategory` properties (`requires_container`, `runner`, `evaluators`, `core_score`) capture these differences for the workflows. Categories may share a dataset (`bug-fix` and `test-generation` do today), but a new category should generally have its own: dataset schema, entry type, result type, pipeline, etc. diff --git a/dataset/extensibility_request_implement.jsonl b/dataset/extensibility_request_implement.jsonl new file mode 100644 index 000000000..a05efbae5 --- /dev/null +++ b/dataset/extensibility_request_implement.jsonl @@ -0,0 +1,23 @@ +{"metadata": {"area": "projects"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30361", "base_commit": "2e361ce52fb9924db6f3585ed31c0048f95db3e0", "created_at": "2026-07-28", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Projects/Project/Archive/JobArchiveManagement.Codeunit.al b/App/Layers/W1/BaseApp/Projects/Project/Archive/JobArchiveManagement.Codeunit.al\nindex bbebf47..f3c5f19 100644\n--- a/App/Layers/W1/BaseApp/Projects/Project/Archive/JobArchiveManagement.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Projects/Project/Archive/JobArchiveManagement.Codeunit.al\n@@ -46,6 +46,8 @@ codeunit 5139 \"Job Archive Management\"\n StoreJob(Job, false);\n JobSetup.\"Archive Jobs\"::Question:\n ArchiveJob(Job);\n+ else\n+ OnAutoArchiveJobOnCaseElse(Job, JobSetup);\n end;\n end;\n \n@@ -490,5 +492,10 @@ codeunit 5139 \"Job Archive Management\"\n local procedure OnBeforeDeleteJobTasks(var JobTask: Record \"Job Task\"; var JobTaskDimension: Record \"Job Task Dimension\"; var JobPlanningLine: Record \"Job Planning Line\"; var JobArchive: Record \"Job Archive\"; Job: Record Job)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnAutoArchiveJobOnCaseElse(var Job: Record Job; JobsSetup: Record \"Jobs Setup\")\n+ begin\n+ end;\n }\n \n", "expected": [{"text": "A new integration event publisher is added to codeunit 5139 \"Job Archive Management\" in the W1 BaseApp file JobArchiveManagement.Codeunit.al.", "level": "critical"}, {"text": "An else branch is added to the case JobSetup.\"Archive Jobs\" statement in AutoArchiveJob, and that else branch raises the new integration event.", "level": "critical"}, {"text": "The new event is raised passing the Job record (var) and the Jobs Setup record to subscribers.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified and no country/region layer counterparts are edited (this codeunit exists only in W1).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement in AutoArchiveJob (e.g. OnAutoArchiveJobOnCaseElse or a similarly placement-accurate OnAutoArchiveJobOn... name).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (Job, Jobs Setup).", "level": "expected"}]} +{"metadata": {"area": "service"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30362", "base_commit": "2e361ce52fb9924db6f3585ed31c0048f95db3e0", "created_at": "2026-07-28", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Service/Posting/ServicePost.Codeunit.al b/App/Layers/W1/BaseApp/Service/Posting/ServicePost.Codeunit.al\nindex 8499176..7b162b9 100644\n--- a/App/Layers/W1/BaseApp/Service/Posting/ServicePost.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Service/Posting/ServicePost.Codeunit.al\n@@ -102,6 +102,7 @@ codeunit 5980 \"Service-Post\"\n WhseServiceRelease: Codeunit \"Whse.-Service Release\";\n GenJnlPostPreview: Codeunit \"Gen. Jnl.-Post Preview\";\n ServiceDocumentArchiveMgmt: Codeunit \"Service Document Archive Mgmt.\";\n+ IsHandled: Boolean;\n ServDocNo: Code[20];\n ServDocType: Integer;\n ServInvoiceNo: Code[20];\n@@ -155,14 +156,17 @@ codeunit 5980 \"Service-Post\"\n ServShipmentNo := ServDocumentsMgt.PrepareShipmentHeader();\n WhseShip := not TempWarehouseShipmentHeader.IsEmpty();\n end;\n- if Invoice then\n- if ServiceHeader.\"Document Type\" in [ServiceHeader.\"Document Type\"::Order, ServiceHeader.\"Document Type\"::Invoice] then begin\n- ServInvoiceNo := ServDocumentsMgt.PrepareInvoiceHeader(Window);\n- ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServInvoiceNo);\n- end else begin\n- ServCrMemoNo := ServDocumentsMgt.PrepareCrMemoHeader(Window);\n- ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServCrMemoNo);\n- end;\n+ IsHandled := false;\n+ OnPostDocumentLinesOnBeforeInsertPostedHeaders(ServiceHeader, IsHandled, ServInvoiceNo, ServCrMemoNo);\n+ if not IsHandled then\n+ if Invoice then\n+ if ServiceHeader.\"Document Type\" in [ServiceHeader.\"Document Type\"::Order, ServiceHeader.\"Document Type\"::Invoice] then begin\n+ ServInvoiceNo := ServDocumentsMgt.PrepareInvoiceHeader(Window);\n+ ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServInvoiceNo);\n+ end else begin\n+ ServCrMemoNo := ServDocumentsMgt.PrepareCrMemoHeader(Window);\n+ ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServCrMemoNo);\n+ end;\n \n if WhseShip then begin\n WarehouseShipmentHeader.Get(TempWarehouseShipmentHeader.\"No.\");\n@@ -798,4 +802,9 @@ codeunit 5980 \"Service-Post\"\n local procedure OnCheckAndSetConstantsOnBeforeSetPostingOptions(var ServiceHeader: Record \"Service Header\"; Invoice: Boolean; Ship: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostDocumentLinesOnBeforeInsertPostedHeaders(var ServiceHeader: Record \"Service Header\"; var IsHandled: Boolean; var ServInvoiceNo: Code[20]; var ServCrMemoNo: Code[20])\n+ begin\n+ end;\n }\n\\ No newline at end of file\ndiff --git a/App/Layers/IT/BaseApp/Service/Posting/ServicePost.Codeunit.al b/App/Layers/IT/BaseApp/Service/Posting/ServicePost.Codeunit.al\nindex 5160365..92d8fd3 100644\n--- a/App/Layers/IT/BaseApp/Service/Posting/ServicePost.Codeunit.al\n+++ b/App/Layers/IT/BaseApp/Service/Posting/ServicePost.Codeunit.al\n@@ -105,6 +105,7 @@ codeunit 5980 \"Service-Post\"\n WhseServiceRelease: Codeunit \"Whse.-Service Release\";\n GenJnlPostPreview: Codeunit \"Gen. Jnl.-Post Preview\";\n ServiceDocumentArchiveMgmt: Codeunit \"Service Document Archive Mgmt.\";\n+ IsHandled: Boolean;\n ServDocNo: Code[20];\n ServDocType: Integer;\n ServInvoiceNo: Code[20];\n@@ -158,14 +159,17 @@ codeunit 5980 \"Service-Post\"\n ServShipmentNo := ServDocumentsMgt.PrepareShipmentHeader();\n WhseShip := not TempWarehouseShipmentHeader.IsEmpty();\n end;\n- if Invoice then\n- if ServiceHeader.\"Document Type\" in [ServiceHeader.\"Document Type\"::Order, ServiceHeader.\"Document Type\"::Invoice] then begin\n- ServInvoiceNo := ServDocumentsMgt.PrepareInvoiceHeader(Window);\n- ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServInvoiceNo);\n- end else begin\n- ServCrMemoNo := ServDocumentsMgt.PrepareCrMemoHeader(Window);\n- ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServCrMemoNo);\n- end;\n+ IsHandled := false;\n+ OnPostDocumentLinesOnBeforeInsertPostedHeaders(ServiceHeader, IsHandled, ServInvoiceNo, ServCrMemoNo);\n+ if not IsHandled then\n+ if Invoice then\n+ if ServiceHeader.\"Document Type\" in [ServiceHeader.\"Document Type\"::Order, ServiceHeader.\"Document Type\"::Invoice] then begin\n+ ServInvoiceNo := ServDocumentsMgt.PrepareInvoiceHeader(Window);\n+ ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServInvoiceNo);\n+ end else begin\n+ ServCrMemoNo := ServDocumentsMgt.PrepareCrMemoHeader(Window);\n+ ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServCrMemoNo);\n+ end;\n \n if WhseShip then begin\n WarehouseShipmentHeader.Get(TempWarehouseShipmentHeader.\"No.\");\n@@ -843,4 +847,9 @@ codeunit 5980 \"Service-Post\"\n local procedure OnCheckAndSetConstantsOnBeforeSetPostingOptions(var ServiceHeader: Record \"Service Header\"; Invoice: Boolean; Ship: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostDocumentLinesOnBeforeInsertPostedHeaders(var ServiceHeader: Record \"Service Header\"; var IsHandled: Boolean; var ServInvoiceNo: Code[20]; var ServCrMemoNo: Code[20])\n+ begin\n+ end;\n }\n\\ No newline at end of file\ndiff --git a/App/Layers/NA/BaseApp/Service/Posting/ServicePost.Codeunit.al b/App/Layers/NA/BaseApp/Service/Posting/ServicePost.Codeunit.al\nindex 8499176..7b162b9 100644\n--- a/App/Layers/NA/BaseApp/Service/Posting/ServicePost.Codeunit.al\n+++ b/App/Layers/NA/BaseApp/Service/Posting/ServicePost.Codeunit.al\n@@ -102,6 +102,7 @@ codeunit 5980 \"Service-Post\"\n WhseServiceRelease: Codeunit \"Whse.-Service Release\";\n GenJnlPostPreview: Codeunit \"Gen. Jnl.-Post Preview\";\n ServiceDocumentArchiveMgmt: Codeunit \"Service Document Archive Mgmt.\";\n+ IsHandled: Boolean;\n ServDocNo: Code[20];\n ServDocType: Integer;\n ServInvoiceNo: Code[20];\n@@ -155,14 +156,17 @@ codeunit 5980 \"Service-Post\"\n ServShipmentNo := ServDocumentsMgt.PrepareShipmentHeader();\n WhseShip := not TempWarehouseShipmentHeader.IsEmpty();\n end;\n- if Invoice then\n- if ServiceHeader.\"Document Type\" in [ServiceHeader.\"Document Type\"::Order, ServiceHeader.\"Document Type\"::Invoice] then begin\n- ServInvoiceNo := ServDocumentsMgt.PrepareInvoiceHeader(Window);\n- ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServInvoiceNo);\n- end else begin\n- ServCrMemoNo := ServDocumentsMgt.PrepareCrMemoHeader(Window);\n- ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServCrMemoNo);\n- end;\n+ IsHandled := false;\n+ OnPostDocumentLinesOnBeforeInsertPostedHeaders(ServiceHeader, IsHandled, ServInvoiceNo, ServCrMemoNo);\n+ if not IsHandled then\n+ if Invoice then\n+ if ServiceHeader.\"Document Type\" in [ServiceHeader.\"Document Type\"::Order, ServiceHeader.\"Document Type\"::Invoice] then begin\n+ ServInvoiceNo := ServDocumentsMgt.PrepareInvoiceHeader(Window);\n+ ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServInvoiceNo);\n+ end else begin\n+ ServCrMemoNo := ServDocumentsMgt.PrepareCrMemoHeader(Window);\n+ ServDocumentsMgt.UpdateIncomingDocument(ServiceHeader.\"Incoming Document Entry No.\", ServiceHeader.\"Posting Date\", ServCrMemoNo);\n+ end;\n \n if WhseShip then begin\n WarehouseShipmentHeader.Get(TempWarehouseShipmentHeader.\"No.\");\n@@ -798,4 +802,9 @@ codeunit 5980 \"Service-Post\"\n local procedure OnCheckAndSetConstantsOnBeforeSetPostingOptions(var ServiceHeader: Record \"Service Header\"; Invoice: Boolean; Ship: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostDocumentLinesOnBeforeInsertPostedHeaders(var ServiceHeader: Record \"Service Header\"; var IsHandled: Boolean; var ServInvoiceNo: Code[20]; var ServCrMemoNo: Code[20])\n+ begin\n+ end;\n }\n\\ No newline at end of file\n", "expected": [{"text": "A new integration event publisher is added to codeunit 5980 \"Service-Post\" that fires immediately before the standard logic deciding whether a posted service invoice or posted service credit memo header is prepared (the if Invoice then ... block calling PrepareInvoiceHeader / PrepareCrMemoHeader).", "level": "critical"}, {"text": "The event is raised using the IsHandled pattern: an IsHandled Boolean is initialized to false, the event is raised before the standard block, and the standard block is guarded with if not IsHandled then so a subscriber setting IsHandled := true fully replaces the standard decision.", "level": "critical"}, {"text": "The new event passes the Service Header record (var), the IsHandled flag (var), and the posted invoice and credit memo document numbers (var, e.g. ServInvoiceNo and ServCrMemoNo) so subscribers can override the decision and set the resulting document numbers.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is propagated consistently to every layer that keeps a copy of this codeunit: exactly three .al files are modified, one each in the W1, IT, and NA BaseApp Service/Posting/ServicePost.Codeunit.al files, and no other layers are edited.", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement before the posted-header insertion (e.g. OnPostDocumentLinesOnBeforeInsertPostedHeaders or a similarly placement-accurate On... name).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (Service Header).", "level": "expected"}]} +{"metadata": {"area": "inventory"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30377", "base_commit": "2e361ce52fb9924db6f3585ed31c0048f95db3e0", "created_at": "2026-07-29", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/NA/BaseApp/Local/Inventory/Reports/ItemSalesStatistics.Report.al b/App/Layers/NA/BaseApp/Local/Inventory/Reports/ItemSalesStatistics.Report.al\nindex 005f29c9b..36084a7c5 100644\n--- a/App/Layers/NA/BaseApp/Local/Inventory/Reports/ItemSalesStatistics.Report.al\n+++ b/App/Layers/NA/BaseApp/Local/Inventory/Reports/ItemSalesStatistics.Report.al\n@@ -322,6 +322,8 @@ report 10135 \"Item Sales Statistics\"\n }\n \n trigger OnAfterGetRecord()\n+ var\n+ SkipRecord: Boolean;\n begin\n NoShow := false;\n if BreakdownByVariant then begin\n@@ -333,7 +335,9 @@ report 10135 \"Item Sales Statistics\"\n \n SetRange(\"Variant Filter\");\n CalcFields(\"Sales (Qty.)\", \"Sales (LCY)\", \"COGS (LCY)\");\n- if (\"Sales (Qty.)\" = 0) and PrintOnlyIfSales then\n+ SkipRecord := (\"Sales (Qty.)\" = 0) and PrintOnlyIfSales;\n+ OnAfterGetRecordItemOnBeforePrintOnlyIfSalesCheck(Item, PrintOnlyIfSales, SkipRecord);\n+ if SkipRecord then\n CurrReport.Skip();\n Profit := \"Sales (LCY)\" - \"COGS (LCY)\";\n if \"Sales (LCY)\" <> 0 then\n@@ -488,5 +492,10 @@ report 10135 \"Item Sales Statistics\"\n ItemVariant.SetRange(\"Item No.\", Item.\"No.\");\n exit(ItemVariant.FindFirst())\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnAfterGetRecordItemOnBeforePrintOnlyIfSalesCheck(var Item: Record Item; PrintOnlyIfSales: Boolean; var SkipRecord: Boolean)\n+ begin\n+ end;\n }\n \n", "expected": [{"text": "A new integration event publisher is added to report 10135 \"Item Sales Statistics\" in the NA layer file ItemSalesStatistics.Report.al.", "level": "critical"}, {"text": "The event is raised in the Item data item OnAfterGetRecord trigger, immediately after the CalcFields call and before the CurrReport.Skip() decision.", "level": "critical"}, {"text": "The skip decision is refactored into a Boolean variable (e.g. SkipRecord) that is passed to the event by reference (var) and then drives CurrReport.Skip(), so a subscriber can override whether the record is skipped.", "level": "critical"}, {"text": "The event passes the Item record and the PrintOnlyIfSales value to subscribers.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is confined to the NA layer: exactly one .al file is modified and no other country/region layer counterparts are edited (this report exists only in NA).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement (e.g. OnAfterGetRecordItemOnBeforePrintOnlyIfSalesCheck or a similarly placement-accurate On... name).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (Item).", "level": "expected"}]} +{"metadata": {"area": "inventory"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30346", "base_commit": "2e361ce52fb9924db6f3585ed31c0048f95db3e0", "created_at": "2026-07-29", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex ec280b8e9..4d773664f 100644\n--- a/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -5270,7 +5270,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n exit((ItemJnlLine.\"Value Entry Type\" = ItemJnlLine.\"Value Entry Type\"::Revaluation) and (ItemJnlLine.Quantity <> 0));\n end;\n \n- local procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n+ procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n var\n PostValueEntryToGL: Record \"Post Value Entry to G/L\";\n begin\ndiff --git a/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 980bea8d2..72c45978c 100644\n--- a/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -5242,7 +5242,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n exit((ItemJnlLine.\"Value Entry Type\" = ItemJnlLine.\"Value Entry Type\"::Revaluation) and (ItemJnlLine.Quantity <> 0));\n end;\n \n- local procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n+ procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n var\n PostValueEntryToGL: Record \"Post Value Entry to G/L\";\n begin\ndiff --git a/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 77ac0cae8..a6f8c6209 100644\n--- a/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -5251,7 +5251,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n exit((ItemJnlLine.\"Value Entry Type\" = ItemJnlLine.\"Value Entry Type\"::Revaluation) and (ItemJnlLine.Quantity <> 0));\n end;\n \n- local procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n+ procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n var\n PostValueEntryToGL: Record \"Post Value Entry to G/L\";\n begin\ndiff --git a/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 7f9692919..d789ece94 100644\n--- a/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -5269,7 +5269,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n exit((ItemJnlLine.\"Value Entry Type\" = ItemJnlLine.\"Value Entry Type\"::Revaluation) and (ItemJnlLine.Quantity <> 0));\n end;\n \n- local procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n+ procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n var\n PostValueEntryToGL: Record \"Post Value Entry to G/L\";\n begin\ndiff --git a/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 64ce03d14..c530de62c 100644\n--- a/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -5285,7 +5285,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n exit((ItemJnlLine.\"Value Entry Type\" = ItemJnlLine.\"Value Entry Type\"::Revaluation) and (ItemJnlLine.Quantity <> 0));\n end;\n \n- local procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n+ procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n var\n PostValueEntryToGL: Record \"Post Value Entry to G/L\";\n begin\ndiff --git a/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex b478a9505..cdbf0b125 100644\n--- a/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -5238,7 +5238,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n exit((ItemJnlLine.\"Value Entry Type\" = ItemJnlLine.\"Value Entry Type\"::Revaluation) and (ItemJnlLine.Quantity <> 0));\n end;\n \n- local procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n+ procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\")\n var\n PostValueEntryToGL: Record \"Post Value Entry to G/L\";\n begin\n", "expected": [{"text": "The InsertPostValueEntryToGL method in codeunit 22 \"Item Jnl.-Post Line\" is changed from a local procedure to a public procedure (the local keyword is removed), widening its accessibility.", "level": "critical"}, {"text": "The method signature and body are otherwise unchanged: procedure InsertPostValueEntryToGL(ValueEntry: Record \"Value Entry\").", "level": "critical"}, {"text": "No integration event or other logic is added; only the accessibility of this one method changes.", "level": "critical"}, {"text": "The change is propagated to every layer that keeps a copy of this codeunit: exactly six .al files are modified, one each in the W1, APAC, CH, ES, IT, and RU layers, and no other layers are edited.", "level": "critical"}, {"text": "No other procedures have their accessibility changed.", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (Value Entry).", "level": "expected"}]} +{"metadata": {"area": "purchases"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30223", "base_commit": "2e361ce52fb9924db6f3585ed31c0048f95db3e0", "created_at": "2026-07-29", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex febc4cf56..e719d521e 100644\n--- a/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -405,9 +405,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4955,8 +4958,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -9488,9 +9494,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -13188,4 +13199,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/BE/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/BE/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 7d577aad0..515f2bd08 100644\n--- a/App/Layers/BE/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/BE/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -397,9 +397,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4805,8 +4808,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8702,9 +8708,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12402,4 +12413,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/CH/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/CH/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 50870566a..d4fd53411 100644\n--- a/App/Layers/CH/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/CH/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -397,9 +397,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4803,8 +4806,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8660,9 +8666,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12295,4 +12306,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 647ce0b2b..2df7d39e7 100644\n--- a/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -397,9 +397,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4801,8 +4804,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8659,9 +8665,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12294,4 +12305,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/ES/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/ES/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 8fdc40b4f..43c213fb1 100644\n--- a/App/Layers/ES/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/ES/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -397,9 +397,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4815,8 +4818,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8689,9 +8695,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12330,4 +12341,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/FI/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/FI/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 5a79d2d3d..803c3547c 100644\n--- a/App/Layers/FI/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/FI/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -398,9 +398,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4794,8 +4797,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8652,9 +8658,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12287,4 +12298,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/GB/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/GB/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex eec9327f0..31fb43f26 100644\n--- a/App/Layers/GB/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/GB/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -400,9 +400,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4829,8 +4832,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8687,9 +8693,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12338,4 +12349,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/IT/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/IT/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex b8c7c627e..29df334fb 100644\n--- a/App/Layers/IT/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/IT/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -415,9 +415,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -5053,8 +5056,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8937,9 +8943,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12568,4 +12579,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/NA/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/NA/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex b3c5df7a5..ef765cb03 100644\n--- a/App/Layers/NA/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/NA/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -399,9 +399,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n PurchSetup.Get();\n if PurchSetup.\"Use Vendor's Tax Area Code\" then begin\n@@ -4964,8 +4967,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8954,9 +8960,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12645,4 +12656,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\n\\ No newline at end of file\ndiff --git a/App/Layers/NL/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/NL/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex bde2f7729..0435ec1a9 100644\n--- a/App/Layers/NL/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/NL/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -397,9 +397,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4788,8 +4791,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8646,9 +8652,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12281,4 +12292,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/NO/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/NO/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 283cf57c4..f94a69758 100644\n--- a/App/Layers/NO/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/NO/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -398,9 +398,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4807,8 +4810,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8665,9 +8671,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12308,4 +12319,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/RU/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/RU/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 27c9153b9..c71d57b56 100644\n--- a/App/Layers/RU/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/RU/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -402,9 +402,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4985,8 +4988,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8867,9 +8873,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12531,4 +12542,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/SE/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/SE/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 5a79d2d3d..803c3547c 100644\n--- a/App/Layers/SE/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/SE/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -398,9 +398,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4794,8 +4797,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8652,9 +8658,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12287,4 +12298,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\ndiff --git a/App/Layers/W1/BaseApp/Purchases/Document/PurchaseLine.Table.al b/App/Layers/W1/BaseApp/Purchases/Document/PurchaseLine.Table.al\nindex 8bca847b5..27acdcb15 100644\n--- a/App/Layers/W1/BaseApp/Purchases/Document/PurchaseLine.Table.al\n+++ b/App/Layers/W1/BaseApp/Purchases/Document/PurchaseLine.Table.al\n@@ -397,9 +397,12 @@ table 39 \"Purchase Line\"\n end;\n \"Bin Code\" := '';\n \n- if Type = Type::Item then\n- if \"Location Code\" <> xRec.\"Location Code\" then\n- PlanPriceCalcByField(FieldNo(\"Location Code\"));\n+ IsHandled := false;\n+ OnValidateLocationCodeOnBeforePlanPriceCalcByField(Rec, IsHandled, CurrFieldNo, xRec);\n+ if not IsHandled then\n+ if Type = Type::Item then\n+ if \"Location Code\" <> xRec.\"Location Code\" then\n+ PlanPriceCalcByField(FieldNo(\"Location Code\"));\n \n IsHandled := false;\n OnValidateLocationCodeOnBeforeSetInboundWhseHandlingTime(CurrFieldNo, Rec, xRec, IsHandled);\n@@ -4783,8 +4786,11 @@ table 39 \"Purchase Line\"\n GLSetup.\"Unit-Amount Rounding Precision\");\n end;\n \n- if PurchHeader.\"Language Code\" <> '' then\n- GetItemTranslation();\n+ IsHandled := false;\n+ OnCopyFromItemOnBeforeGetItemTranslation(Rec, Item, IsHandled);\n+ if not IsHandled then\n+ if PurchHeader.\"Language Code\" <> '' then\n+ GetItemTranslation();\n \n OnCopyFromItemOnAfterGetItemTranslation(Rec, Item);\n \n@@ -8641,9 +8647,14 @@ table 39 \"Purchase Line\"\n end;\n \n local procedure CheckWMS()\n+ var\n+ IsHandled: Boolean;\n begin\n- if CurrFieldNo <> 0 then\n- CheckLocationOnWMS();\n+ IsHandled := false;\n+ OnBeforeCheckWMS(Rec, CurrFieldNo, IsHandled);\n+ if not IsHandled then\n+ if CurrFieldNo <> 0 then\n+ CheckLocationOnWMS();\n if \"Document Type\" = \"Document Type\"::\"Return Order\" then\n if (\"Job No.\" <> '') and (Type = Type::Item) then\n if Location.Get(\"Location Code\") then\n@@ -12276,4 +12287,19 @@ table 39 \"Purchase Line\"\n local procedure OnValidateGenBusPostingGroupOnBeforeValidateVATBusPostingGroup(var PurchaseLine: Record \"Purchase Line\"; var ValidateVATBusPostingGroup: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckWMS(var PurchaseLine: Record \"Purchase Line\"; CurrFieldNo: Integer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateLocationCodeOnBeforePlanPriceCalcByField(var PurchaseLine: Record \"Purchase Line\"; var IsHandled: Boolean; CurrFieldNo: Integer; xPurchaseLine: Record \"Purchase Line\")\n+ begin\n+ end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCopyFromItemOnBeforeGetItemTranslation(var PurchaseLine: Record \"Purchase Line\"; var Item: Record Item; var IsHandled: Boolean)\n+ begin\n+ end;\n }\n", "expected": [{"text": "Three new integration event publishers are added to table 39 \"Purchase Line\": one before PlanPriceCalcByField in the \"Location Code\" OnValidate trigger, one before GetItemTranslation in CopyFromItem, and one before CheckLocationOnWMS in CheckWMS.", "level": "critical"}, {"text": "Each event uses the IsHandled pattern: IsHandled is initialized to false, the event is raised before the standard call, and the standard call is guarded with if not IsHandled then, so a subscriber setting IsHandled := true skips it.", "level": "critical"}, {"text": "The CheckWMS event passes the Purchase Line record, CurrFieldNo, and IsHandled (var); the CopyFromItem event passes the Purchase Line record, the Item record, and IsHandled (var); the \"Location Code\" event passes the Purchase Line record, IsHandled (var), CurrFieldNo, and the xPurchaseLine record.", "level": "critical"}, {"text": "A local IsHandled: Boolean variable is added to the CheckWMS procedure.", "level": "critical"}, {"text": "Each event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is propagated to every layer that keeps a copy of this table: exactly 14 .al files are modified (W1, APAC, BE, CH, DACH, ES, FI, GB, IT, NA, NL, NO, RU, SE), and no other layers are edited.", "level": "critical"}, {"text": "Event names follow BC naming conventions for their placement (e.g. OnBeforeCheckWMS, OnCopyFromItemOnBeforeGetItemTranslation, OnValidateLocationCodeOnBeforePlanPriceCalcByField, or similarly placement-accurate names).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (Purchase Line, Item).", "level": "expected"}]} +{"metadata": {"area": "manufacturing"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30336", "base_commit": "2e361ce52fb9924db6f3585ed31c0048f95db3e0", "created_at": "2026-07-29", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al b/App/Layers/W1/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al\nindex ac91f1d57..71c48d9fb 100644\n--- a/App/Layers/W1/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al\n+++ b/App/Layers/W1/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al\n@@ -138,6 +138,7 @@ table 5409 \"Prod. Order Routing Line\"\n MachineCtrTransferFields();\n end;\n end;\n+ OnValidateNoOnBeforeModifyCapNeedEntries(Rec, xRec);\n ModifyCapNeedEntries();\n \n GetProdOrderLine();\n@@ -2026,5 +2027,10 @@ table 5409 \"Prod. Order Routing Line\"\n local procedure OnCheckPreviousAndNextOnBeforeInsertTempRemainingProdOrderRtngLine(ProdOrderRoutingLine: Record \"Prod. Order Routing Line\"; var ProdOrderRoutingLineFiltered: Record \"Prod. Order Routing Line\"; var TempDeletedProdOrderRoutingLine: Record \"Prod. Order Routing Line\" temporary; var ErrorOnNext: Boolean; var ErrorOnPrevious: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateNoOnBeforeModifyCapNeedEntries(var ProdOrderRoutingLine: Record \"Prod. Order Routing Line\"; xProdOrderRoutingLine: Record \"Prod. Order Routing Line\")\n+ begin\n+ end;\n }\n \ndiff --git a/App/Layers/IT/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al b/App/Layers/IT/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al\nindex 29879dede..c819f2d84 100644\n--- a/App/Layers/IT/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al\n+++ b/App/Layers/IT/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al\n@@ -164,6 +164,7 @@ table 5409 \"Prod. Order Routing Line\"\n MachineCtrTransferFields();\n end;\n end;\n+ OnValidateNoOnBeforeModifyCapNeedEntries(Rec, xRec);\n ModifyCapNeedEntries();\n \n GetProdOrderLine();\n@@ -2243,5 +2244,10 @@ table 5409 \"Prod. Order Routing Line\"\n local procedure OnCheckPreviousAndNextOnBeforeInsertTempRemainingProdOrderRtngLine(ProdOrderRoutingLine: Record \"Prod. Order Routing Line\"; var ProdOrderRoutingLineFiltered: Record \"Prod. Order Routing Line\"; var TempDeletedProdOrderRoutingLine: Record \"Prod. Order Routing Line\" temporary; var ErrorOnNext: Boolean; var ErrorOnPrevious: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidateNoOnBeforeModifyCapNeedEntries(var ProdOrderRoutingLine: Record \"Prod. Order Routing Line\"; xProdOrderRoutingLine: Record \"Prod. Order Routing Line\")\n+ begin\n+ end;\n }\n \n", "expected": [{"text": "A new integration event publisher is added to table 5409 \"Prod. Order Routing Line\".", "level": "critical"}, {"text": "The event is raised in the \"No.\" field OnValidate trigger, immediately after the case Type of block (work/machine center field transfer) and immediately before the ModifyCapNeedEntries call.", "level": "critical"}, {"text": "The event passes the current record and the previous record (xRec) to subscribers.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is propagated to every layer that keeps a copy of this table: exactly two .al files are modified, one in the W1 layer and one in the IT layer, and no other layers are edited.", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement (e.g. OnValidateNoOnBeforeModifyCapNeedEntries or a similarly placement-accurate On... name).", "level": "expected"}, {"text": "Record parameters use the full table name without abbreviations (\"Prod. Order Routing Line\").", "level": "expected"}]} +{"metadata": {"area": "inventory"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30380", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Inventory/Tracking/ItemTrackingManagement.Codeunit.al b/App/Layers/W1/BaseApp/Inventory/Tracking/ItemTrackingManagement.Codeunit.al\nindex 03caea8be..ffc8030d0 100644\n--- a/App/Layers/W1/BaseApp/Inventory/Tracking/ItemTrackingManagement.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Inventory/Tracking/ItemTrackingManagement.Codeunit.al\n@@ -1103,8 +1103,12 @@ codeunit 6500 \"Item Tracking Management\"\n TempPostedWhseRcptLine.Modify();\n until WhseItemEntryRelation.Next() = 0\n else begin\n- TempPostedWhseRcptLine := PostedWhseRcptLine;\n- TempPostedWhseRcptLine.Insert();\n+ IsHandled := false;\n+ OnSplitPostedWhseReceiptLineOnNotFindWhseItemEntryRelation(PostedWhseRcptLine, TempPostedWhseRcptLine, IsHandled);\n+ if not IsHandled then begin\n+ TempPostedWhseRcptLine := PostedWhseRcptLine;\n+ TempPostedWhseRcptLine.Insert();\n+ end;\n end;\n \n OnAfterSplitPostedWhseReceiptLine(PostedWhseRcptLine, TempPostedWhseRcptLine);\n@@ -3670,6 +3674,11 @@ codeunit 6500 \"Item Tracking Management\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnSplitPostedWhseReceiptLineOnNotFindWhseItemEntryRelation(PostedWhseReceiptLine: Record \"Posted Whse. Receipt Line\"; var TempPostedWhseReceiptLine: Record \"Posted Whse. Receipt Line\" temporary; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCreateTrackingSpecification(var TrackingSpecification: Record \"Tracking Specification\"; ReservationEntry: Record \"Reservation Entry\")\n begin\n", "expected": [{"level": "critical", "text": "A new integration event publisher is added to codeunit 6500 \"Item Tracking Management\" in the W1 BaseApp file ItemTrackingManagement.Codeunit.al."}, {"level": "critical", "text": "The event is raised in the else branch of SplitPostedWhseRcptLine (the branch that runs when Whse. Item Entry Relation.FindSet() finds no records), before the default TempPostedWhseRcptLine copy is inserted."}, {"level": "critical", "text": "The IsHandled pattern is used: IsHandled is initialized to false, the event is raised before the default insert, and the default TempPostedWhseRcptLine := PostedWhseRcptLine / Insert() runs only inside an if not IsHandled then block."}, {"level": "critical", "text": "The event passes the posted warehouse receipt line, the temporary posted warehouse receipt line by reference (var ... temporary), and the IsHandled flag by reference (var) to subscribers."}, {"level": "critical", "text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body."}, {"level": "critical", "text": "The change is confined to the W1 layer: exactly one .al file is modified and no country/region layer counterparts are edited (this codeunit exists only in W1)."}, {"level": "expected", "text": "The event name follows BC naming conventions for its placement in SplitPostedWhseRcptLine (e.g. OnSplitPostedWhseReceiptLineOnNotFindWhseItemEntryRelation or a similarly placement-accurate On... name)."}, {"level": "expected", "text": "Record parameters use full table names without abbreviations (Posted Whse. Receipt Line)."}]} +{"metadata": {"area": "warehouse"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30339", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Warehouse/Worksheet/WhseWorksheetLine.Table.al b/App/Layers/W1/BaseApp/Warehouse/Worksheet/WhseWorksheetLine.Table.al\nindex 588b19ded8a..0b33ea87523 100644\n--- a/App/Layers/W1/BaseApp/Warehouse/Worksheet/WhseWorksheetLine.Table.al\n+++ b/App/Layers/W1/BaseApp/Warehouse/Worksheet/WhseWorksheetLine.Table.al\n@@ -783,6 +783,8 @@ table 7326 \"Whse. Worksheet Line\"\n \n AvailableQty := AvailQtyBase - QtyAssgndOnWkshBase + AssignedQtyOnReservedLines();\n end;\n+\n+ OnAfterCalcAvailableQtyBase(Rec, AvailableQty);\n end;\n \n procedure CalcReservedNotFromILEQty(QtyBaseAvailToPick: Decimal; var QtyToPick: Decimal; var QtyToPickBase: Decimal)\n@@ -1715,6 +1717,11 @@ table 7326 \"Whse. Worksheet Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnAfterCalcAvailableQtyBase(var WhseWorksheetLine: Record \"Whse. Worksheet Line\"; var AvailableQty: Decimal)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n procedure OnDeleteQtyToHandleOnBeforeModify(var WhseWorksheetLine: Record \"Whse. Worksheet Line\")\n begin\n", "expected": [{"text": "A new integration event publisher is added to table 7326 \"Whse. Worksheet Line\" in the W1 BaseApp file WhseWorksheetLine.Table.al.", "level": "critical"}, {"text": "The event is raised at the end of the CalcAvailableQtyBase() procedure, after the final AvailableQty has been computed.", "level": "critical"}, {"text": "The event passes the record (Rec) and AvailableQty by reference (var), so subscribers can adjust the calculated available quantity.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified (this table exists only in W1).", "level": "critical"}, {"text": "The event name follows BC naming conventions for an after-event on CalcAvailableQtyBase (e.g. OnAfterCalcAvailableQtyBase).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Whse. Worksheet Line\").", "level": "expected"}]} +{"metadata": {"area": "manufacturing"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30337", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Manufacturing/Document/CreateProdOrderLines.Codeunit.al b/App/Layers/W1/BaseApp/Manufacturing/Document/CreateProdOrderLines.Codeunit.al\nindex a37c51e087a..e640086392f 100644\n--- a/App/Layers/W1/BaseApp/Manufacturing/Document/CreateProdOrderLines.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Manufacturing/Document/CreateProdOrderLines.Codeunit.al\n@@ -211,6 +211,7 @@ codeunit 99000787 \"Create Prod. Order Lines\"\n local procedure CreateProdOrderLine(ProdOrder: Record \"Production Order\"; VariantCode: Code[10]; var ErrorOccured: Boolean)\n var\n SalesHeader: Record \"Sales Header\";\n+ IsHandled: Boolean;\n begin\n DeleteLinesForProductionOrder(ProdOrder);\n \n@@ -221,20 +222,10 @@ codeunit 99000787 \"Create Prod. Order Lines\"\n case ProdOrder.\"Source Type\" of\n ProdOrder.\"Source Type\"::Item:\n begin\n- OnCreateProdOrderLineOnBeforeInitProdOrderLine(InsertNew);\n- InitProdOrderLine(ProdOrder.\"Source No.\", VariantCode, ProdOrder.\"Location Code\");\n- ProdOrderLine.Description := ProdOrder.Description;\n- ProdOrderLine.\"Description 2\" := ProdOrder.\"Description 2\";\n- ProdOrderLine.Validate(Quantity, ProdOrder.Quantity);\n- ProdOrderLine.UpdateDatetime();\n- if SalesLineIsSet then\n- CopyDimFromSalesLine(SalesLine, ProdOrderLine);\n- OnBeforeProdOrderLineInsert(ProdOrderLine, ProdOrder, SalesLineIsSet, SalesLine);\n- ProdOrderLine.Insert();\n- if ProdOrderLine.HasErrorOccured() then\n- ErrorOccured := true;\n-\n- OnAfterProdOrderLineInsert(ProdOrder, ProdOrderLine, NextProdOrderLineNo);\n+ IsHandled := false;\n+ OnCreateProdOrderLineOnBeforeCreateProdOrderLineFromItem(IsHandled, ErrorOccured, ProdOrder, VariantCode);\n+ if not IsHandled then\n+ CreateProdOrderLineFromItem(ProdOrder, VariantCode, ErrorOccured);\n end;\n ProdOrder.\"Source Type\"::Family:\n if not CopyFromFamily() then\n@@ -254,6 +245,24 @@ codeunit 99000787 \"Create Prod. Order Lines\"\n OnAfterCreateProdOrderLine(ProdOrder, VariantCode, ErrorOccured);\n end;\n \n+ local procedure CreateProdOrderLineFromItem(var ProductionOrder: Record \"Production Order\"; VariantCode: Code[10]; var ErrorOccured: Boolean)\n+ begin\n+ OnCreateProdOrderLineOnBeforeInitProdOrderLine(InsertNew);\n+ InitProdOrderLine(ProductionOrder.\"Source No.\", VariantCode, ProductionOrder.\"Location Code\");\n+ ProdOrderLine.Description := ProductionOrder.Description;\n+ ProdOrderLine.\"Description 2\" := ProductionOrder.\"Description 2\";\n+ ProdOrderLine.Validate(Quantity, ProductionOrder.Quantity);\n+ ProdOrderLine.UpdateDatetime();\n+ if SalesLineIsSet then\n+ CopyDimFromSalesLine(SalesLine, ProdOrderLine);\n+ OnBeforeProdOrderLineInsert(ProdOrderLine, ProductionOrder, SalesLineIsSet, SalesLine);\n+ ProdOrderLine.Insert();\n+ if ProdOrderLine.HasErrorOccured() then\n+ ErrorOccured := true;\n+\n+ OnAfterProdOrderLineInsert(ProductionOrder, ProdOrderLine, NextProdOrderLineNo);\n+ end;\n+\n local procedure DeleteLinesForProductionOrder(ProductionOrder: Record \"Production Order\")\n var\n IsHandled: Boolean;\n@@ -835,6 +844,11 @@ codeunit 99000787 \"Create Prod. Order Lines\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnCreateProdOrderLineOnBeforeCreateProdOrderLineFromItem(var IsHandled: Boolean; var ErrorOccured: Boolean; var ProductionOrder: Record \"Production Order\"; VariantCode: Code[10])\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnCreateProdOrderLineOnBeforeInitProdOrderLine(var InsertNew: Boolean)\n begin\n@@ -850,4 +864,3 @@ codeunit 99000787 \"Create Prod. Order Lines\"\n begin\n end;\n }\n-\n", "expected": [{"text": "The Source Type = Item branch of CreateProdOrderLine is refactored so the standard item-line creation logic is moved into a dedicated local procedure (e.g. CreateProdOrderLineFromItem), and a new IsHandled integration event guards the call to that helper.", "level": "critical"}, {"text": "A new integration event publisher is added to codeunit 99000787 \"Create Prod. Order Lines\" in the W1 BaseApp file CreateProdOrderLines.Codeunit.al.", "level": "critical"}, {"text": "The IsHandled pattern is used: IsHandled is initialized to false, the event is raised at the start of the Item branch, and the standard item-line creation (the extracted helper) runs only inside if not IsHandled then.", "level": "critical"}, {"text": "The event passes IsHandled (var), ErrorOccured (var), the Production Order record, and the variant code, so a subscriber can replace the item-line creation and report the outcome.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified (this codeunit exists only in W1).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement before the item-line creation (e.g. OnCreateProdOrderLineOnBeforeCreateProdOrderLineFromItem or a similarly placement-accurate name).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Production Order\").", "level": "expected"}]} +{"metadata": {"area": "sales"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30173", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Sales/Pricing/SalesLineDiscounts.Page.al b/App/Layers/W1/BaseApp/Sales/Pricing/SalesLineDiscounts.Page.al\nindex a6d028632b7..991eef2d274 100644\n--- a/App/Layers/W1/BaseApp/Sales/Pricing/SalesLineDiscounts.Page.al\n+++ b/App/Layers/W1/BaseApp/Sales/Pricing/SalesLineDiscounts.Page.al\n@@ -431,6 +431,7 @@ page 7004 \"Sales Line Discounts\"\n else\n Rec.SetRange(\"Starting Date\");\n \n+ OnSetRecFiltersOnBeforeCurrPageUpdate(Rec, ItemTypeFilter);\n CurrPage.Update(false);\n end;\n \n@@ -467,6 +468,8 @@ page 7004 \"Sales Line Discounts\"\n if not ItemDiscGr.FindFirst() then\n Clear(ItemDiscGr);\n end;\n+ else\n+ OnGetFilterDescriptionCaseElse(Rec, ItemTypeFilter, SourceTableName, Item);\n end;\n \n SalesSrcTableName := '';\n@@ -639,4 +642,26 @@ page 7004 \"Sales Line Discounts\"\n local procedure OnGetTypeFilterCaseElse(var SalesLineDiscount: Record \"Sales Line Discount\"; var TypeFilter: Integer)\n begin\n end;\n+\n+ /// \n+ /// Raises an event after the record filters have been applied and before the page is updated, allowing subscribers to apply additional filtering based on the current item type filter.\n+ /// \n+ /// The sales line discount record being filtered.\n+ /// The current item type filter value.\n+ [IntegrationEvent(true, false)]\n+ local procedure OnSetRecFiltersOnBeforeCurrPageUpdate(var SalesLineDiscount: Record \"Sales Line Discount\"; ItemTypeFilter: Option Item,\"Item Discount Group\",\"None\")\n+ begin\n+ end;\n+\n+ /// \n+ /// Raises an event to resolve the filter description when the item type filter is not Item or Item Discount Group, allowing subscribers to provide a custom source table name and item filtering.\n+ /// \n+ /// The sales line discount record being described.\n+ /// The current item type filter value.\n+ /// The source table name to display in the filter description.\n+ /// The item record used to build the filter description.\n+ [IntegrationEvent(true, false)]\n+ local procedure OnGetFilterDescriptionCaseElse(var SalesLineDiscount: Record \"Sales Line Discount\"; ItemTypeFilter: Option Item,\"Item Discount Group\",\"None\"; var SourceTableName: Text; var Item: Record Item)\n+ begin\n+ end;\n }\n", "expected": [{"text": "Two new integration event publishers are added to page 7004 \"Sales Line Discounts\" in the W1 BaseApp file SalesLineDiscounts.Page.al.", "level": "critical"}, {"text": "One event is raised in SetRecFilters after the record filters are applied and before CurrPage.Update, passing the Sales Line Discount record and the current item type filter.", "level": "critical"}, {"text": "The other event is raised in the else branch of the case ItemTypeFilter statement in GetFilterDescription (when the value is neither Item nor Item Discount Group), passing the record, the item type filter, the SourceTableName (var Text), and the Item record (var).", "level": "critical"}, {"text": "Both event publishers are declared as [IntegrationEvent(...)] local procedures with empty bodies.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified (this page exists only in W1).", "level": "critical"}, {"text": "The event names follow BC naming conventions for their placement (e.g. OnSetRecFiltersOnBeforeCurrPageUpdate and OnGetFilterDescriptionCaseElse or similarly placement-accurate names).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Sales Line Discount\", Item).", "level": "expected"}]} +{"metadata": {"area": "warehouse"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30292", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Warehouse/Activity/CreatePick.Codeunit.al b/App/Layers/W1/BaseApp/Warehouse/Activity/CreatePick.Codeunit.al\nindex 5896cc4f3f7..965d3cd9492 100644\n--- a/App/Layers/W1/BaseApp/Warehouse/Activity/CreatePick.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Warehouse/Activity/CreatePick.Codeunit.al\n@@ -1634,7 +1634,7 @@ codeunit 7312 \"Create Pick\"\n IsHandled: Boolean;\n begin\n IsHandled := false;\n- OnBeforeCreateWhseActivHeader(CurrWarehouseActivityHeader, LocationCode, FirstWhseDocNo, LastWhseDocNo, NoOfSourceDoc, NoOfLines, WhseDocCreated, IsHandled);\n+ OnBeforeCreateWhseActivHeader(CurrWarehouseActivityHeader, LocationCode, FirstWhseDocNo, LastWhseDocNo, NoOfSourceDoc, NoOfLines, WhseDocCreated, IsHandled, TempWarehouseActivityLine);\n if not IsHandled then begin\n CurrWarehouseActivityHeader.Init();\n CurrWarehouseActivityHeader.\"No.\" := '';\n@@ -4880,7 +4880,7 @@ codeunit 7312 \"Create Pick\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnBeforeCreateWhseActivHeader(var CurrWarehouseActivityHeader: Record \"Warehouse Activity Header\"; LocationCode: Code[10]; var FirstWhseDocNo: Code[20]; var LastWhseDocNo: Code[20]; var NoOfSourceDoc: Integer; var NoOfLines: Integer; var WhseDocCreated: Boolean; var IsHandled: Boolean)\n+ local procedure OnBeforeCreateWhseActivHeader(var CurrWarehouseActivityHeader: Record \"Warehouse Activity Header\"; LocationCode: Code[10]; var FirstWhseDocNo: Code[20]; var LastWhseDocNo: Code[20]; var NoOfSourceDoc: Integer; var NoOfLines: Integer; var WhseDocCreated: Boolean; var IsHandled: Boolean; var TempWarehouseActivityLine: Record \"Warehouse Activity Line\" temporary)\n begin\n end;\n \n", "expected": [{"text": "The existing integration event OnBeforeCreateWhseActivHeader in codeunit 7312 \"Create Pick\" (W1 BaseApp file CreatePick.Codeunit.al) is extended with a new parameter: a temporary \"Warehouse Activity Line\" record passed by reference (var ... temporary).", "level": "critical"}, {"text": "The call site in CreateWhseActivHeader is updated to pass the temporary warehouse activity line to the event.", "level": "critical"}, {"text": "The publisher remains [IntegrationEvent(false, false)] with an empty body, and all existing parameters are preserved.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified (this codeunit exists only in W1).", "level": "critical"}, {"text": "The new parameter uses the full table name without abbreviation (\"Warehouse Activity Line\") and is declared as var ... temporary.", "level": "expected"}]} +{"metadata": {"area": "sales"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30329", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Sales/History/CorrectPstdSalesInvYesNo.Codeunit.al b/App/Layers/W1/BaseApp/Sales/History/CorrectPstdSalesInvYesNo.Codeunit.al\nindex d3125c58eec..933c127f3c7 100644\n--- a/App/Layers/W1/BaseApp/Sales/History/CorrectPstdSalesInvYesNo.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Sales/History/CorrectPstdSalesInvYesNo.Codeunit.al\n@@ -82,8 +82,11 @@ codeunit 1322 \"Correct PstdSalesInv (Yes/No)\"\n ConfirmManagement: Codeunit \"Confirm Management\";\n CorrectPostedSalesInvoice: Codeunit \"Correct Posted Sales Invoice\";\n IsHandled: Boolean;\n+ ConfirmQuestion: Text;\n begin\n- if ConfirmManagement.GetResponse(CorrectPostedInvoiceFromSingleOrderQst, false) then begin\n+ ConfirmQuestion := CorrectPostedInvoiceFromSingleOrderQst;\n+ OnCancelPostedInvoiceAndOpenSalesOrderOnBeforeConfirm(SalesInvoiceHeader, SalesHeader, ConfirmQuestion);\n+ if ConfirmManagement.GetResponse(ConfirmQuestion, false) then begin\n if not CorrectPostedSalesInvoice.CancelPostedInvoice(SalesInvoiceHeader) then\n exit(false);\n \n@@ -133,5 +136,10 @@ codeunit 1322 \"Correct PstdSalesInv (Yes/No)\"\n local procedure OnCorrectInvoiceOnBeforeOpenSalesOrderPage(var SalesHeader: Record \"Sales Header\"; var IsHandled: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCancelPostedInvoiceAndOpenSalesOrderOnBeforeConfirm(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var SalesHeader: Record \"Sales Header\"; var ConfirmQuestion: Text)\n+ begin\n+ end;\n }\n \n", "expected": [{"text": "A new integration event publisher is added to codeunit 1322 \"Correct PstdSalesInv (Yes/No)\" in the W1 BaseApp file CorrectPstdSalesInvYesNo.Codeunit.al.", "level": "critical"}, {"text": "In CancelPostedInvoiceAndOpenSalesOrder, the confirmation text is moved into a local Text variable, the new event is raised to let subscribers override it, and that variable is passed to ConfirmManagement.GetResponse (instead of the constant CorrectPostedInvoiceFromSingleOrderQst directly).", "level": "critical"}, {"text": "The event passes the Sales Invoice Header (var), the Sales Header (var), and the confirmation question as a var Text parameter.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified (this codeunit exists only in W1).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement before the confirmation (e.g. OnCancelPostedInvoiceAndOpenSalesOrderOnBeforeConfirm or a similarly placement-accurate name).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Sales Invoice Header\", \"Sales Header\").", "level": "expected"}]} +{"metadata": {"area": "manufacturing"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30359", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Manufacturing/Routing/CalculateRoutingLine.Codeunit.al b/App/Layers/W1/BaseApp/Manufacturing/Routing/CalculateRoutingLine.Codeunit.al\nindex b7e5b4bceb2..9057637d560 100644\n--- a/App/Layers/W1/BaseApp/Manufacturing/Routing/CalculateRoutingLine.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Manufacturing/Routing/CalculateRoutingLine.Codeunit.al\n@@ -1364,7 +1364,7 @@ codeunit 99000774 \"Calculate Routing Line\"\n CalcExpectedCost(ProdOrderRoutingLine, TotalQtyPerOperation, TotalCapacityPerOperation);\n \n IsHandled := false;\n- OnBeforeScheduleRoutingLine(ProdOrderRoutingLine, CalcStartEndDate, IsHandled);\n+ OnBeforeScheduleRoutingLine(ProdOrderRoutingLine, CalcStartEndDate, IsHandled, Direction);\n if not IsHandled then\n if ProdOrderRoutingLine.\"Schedule Manually\" then\n CalculateRoutingLineFixed()\n@@ -2345,7 +2345,7 @@ codeunit 99000774 \"Calculate Routing Line\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnBeforeScheduleRoutingLine(var ProdOrderRoutingLine: Record \"Prod. Order Routing Line\"; var CalcStartEndDate: Boolean; var IsHandled: Boolean)\n+ local procedure OnBeforeScheduleRoutingLine(var ProdOrderRoutingLine: Record \"Prod. Order Routing Line\"; var CalcStartEndDate: Boolean; var IsHandled: Boolean; var Direction: Option Forward,Backward)\n begin\n end;\n \n", "expected": [{"text": "The existing integration event OnBeforeScheduleRoutingLine in codeunit 99000774 \"Calculate Routing Line\" (W1 BaseApp file CalculateRoutingLine.Codeunit.al) is extended with a new Direction parameter passed by reference (var).", "level": "critical"}, {"text": "The call site is updated to pass Direction (the Forward/Backward option) to the event before the scheduling decision.", "level": "critical"}, {"text": "The publisher remains [IntegrationEvent(false, false)] with an empty body and all existing parameters are preserved.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified (this codeunit exists only in W1).", "level": "critical"}, {"text": "The new Direction parameter is declared as a var Option (Forward,Backward) matching the local Direction variable type.", "level": "expected"}]} +{"metadata": {"area": "manufacturing"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30239", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/IT/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al b/App/Layers/IT/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al\nindex 4b288f99bfd..6aab163247e 100644\n--- a/App/Layers/IT/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al\n+++ b/App/Layers/IT/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al\n@@ -35,6 +35,11 @@ codeunit 99000760 \"Mfg. Item Jnl. Check Line\"\n IsHandled: Boolean;\n ShouldCheckItemNo: Boolean;\n begin\n+ IsHandled := false;\n+ OnBeforeOnRunOnCheckWarehouse(ItemJournalLine, CalledFromAdjustment, CalledFromInvtPutawayPick, IsHandled);\n+ if IsHandled then\n+ exit;\n+\n if (ItemJournalLine.\"Entry Type\" in [ItemJournalLine.\"Entry Type\"::Consumption, ItemJournalLine.\"Entry Type\"::Output]) and\n not (ItemJournalLine.\"Value Entry Type\" = ItemJournalLine.\"Value Entry Type\"::Revaluation) and\n not ItemJournalLine.OnlyStopTime()\n@@ -307,5 +312,10 @@ codeunit 99000760 \"Mfg. Item Jnl. Check Line\"\n local procedure OnBeforeCheckWarehouseLastOutputOperation(var ItemJournalLine: Record \"Item Journal Line\"; var Result: Boolean; var IsHandled: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeOnRunOnCheckWarehouse(var ItemJournalLine: Record \"Item Journal Line\"; CalledFromAdjustment: Boolean; CalledFromInvtPutawayPick: Boolean; var IsHandled: Boolean)\n+ begin\n+ end;\n }\n \ndiff --git a/App/Layers/W1/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al b/App/Layers/W1/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al\nindex b8006ff0ced..ec71687f144 100644\n--- a/App/Layers/W1/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Manufacturing/Inventory/Journal/MfgItemJnlCheckLine.Codeunit.al\n@@ -32,6 +32,11 @@ codeunit 99000760 \"Mfg. Item Jnl. Check Line\"\n IsHandled: Boolean;\n ShouldCheckItemNo: Boolean;\n begin\n+ IsHandled := false;\n+ OnBeforeOnRunOnCheckWarehouse(ItemJournalLine, CalledFromAdjustment, CalledFromInvtPutawayPick, IsHandled);\n+ if IsHandled then\n+ exit;\n+\n if (ItemJournalLine.\"Entry Type\" in [ItemJournalLine.\"Entry Type\"::Consumption, ItemJournalLine.\"Entry Type\"::Output]) and\n not (ItemJournalLine.\"Value Entry Type\" = ItemJournalLine.\"Value Entry Type\"::Revaluation) and\n not ItemJournalLine.OnlyStopTime()\n@@ -273,5 +278,10 @@ codeunit 99000760 \"Mfg. Item Jnl. Check Line\"\n local procedure OnBeforeCheckWarehouseLastOutputOperation(var ItemJournalLine: Record \"Item Journal Line\"; var Result: Boolean; var IsHandled: Boolean)\n begin\n end;\n+\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeOnRunOnCheckWarehouse(var ItemJournalLine: Record \"Item Journal Line\"; CalledFromAdjustment: Boolean; CalledFromInvtPutawayPick: Boolean; var IsHandled: Boolean)\n+ begin\n+ end;\n }\n \n", "expected": [{"text": "A new integration event publisher OnBeforeOnRunOnCheckWarehouse is added to codeunit 99000760 \"Mfg. Item Jnl. Check Line\".", "level": "critical"}, {"text": "The event is raised at the start of the OnRunOnCheckWarehouse procedure using the IsHandled pattern: IsHandled initialized to false, event raised, and if IsHandled then exit before the standard validation.", "level": "critical"}, {"text": "The event passes the Item Journal Line record (var), CalledFromAdjustment, CalledFromInvtPutawayPick, and IsHandled (var).", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The identical change is applied to both layer copies of the codeunit: the W1 BaseApp file and the IT layer file (exactly two .al files modified).", "level": "critical"}, {"text": "The event name follows BC naming conventions (e.g. OnBeforeOnRunOnCheckWarehouse).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Item Journal Line\").", "level": "expected"}]} +{"metadata": {"area": "sales"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30331", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al b/App/Layers/APAC/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\nindex 740dd47e9be..ae160cae3f5 100644\n--- a/App/Layers/APAC/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\n+++ b/App/Layers/APAC/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\n@@ -210,6 +210,7 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n procedure CreateCreditMemoCopyDocument(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var SalesHeader: Record \"Sales Header\"): Boolean\n var\n SalesHdr: Record \"Sales Header\";\n+ ConfirmQuestion: Text;\n begin\n OnBeforeCreateCreditMemoCopyDocument(SalesInvoiceHeader);\n TestNoFixedAssetInSalesInvoice(SalesInvoiceHeader);\n@@ -221,9 +222,12 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n end;\n SalesHdr.SetRange(\"Document Type\", SalesHdr.\"Document Type\"::Order);\n SalesHdr.SetRange(\"No.\", SalesInvoiceHeader.\"Order No.\");\n- if not SalesHdr.IsEmpty then\n- if not Confirm(CreateCreditMemoQst) then\n+ if not SalesHdr.IsEmpty then begin\n+ ConfirmQuestion := CreateCreditMemoQst;\n+ OnCreateCreditMemoCopyDocumentOnBeforeConfirm(SalesInvoiceHeader, SalesHdr, ConfirmQuestion);\n+ if not Confirm(ConfirmQuestion) then\n exit(false);\n+ end;\n \n CreateCopyDocument(SalesInvoiceHeader, SalesHeader, SalesHeader.\"Document Type\"::\"Credit Memo\", false);\n \n@@ -1418,6 +1422,17 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n begin\n end;\n \n+ /// \n+ /// Raised before confirming the creation of a corrective credit memo for an invoice posted from a sales order.\n+ /// \n+ /// The posted sales invoice being corrected.\n+ /// The originating sales order the invoice was posted from.\n+ /// The confirmation text to be shown. Subscribers can modify it.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCreateCreditMemoCopyDocumentOnBeforeConfirm(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var SalesHeader: Record \"Sales Header\"; var ConfirmQuestion: Text)\n+ begin\n+ end;\n+\n /// \n /// Raised before inserting the sales header during invoice correction.\n /// \ndiff --git a/App/Layers/ES/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al b/App/Layers/ES/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\nindex a145d8ec1e8..c34bfa57c32 100644\n--- a/App/Layers/ES/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\n+++ b/App/Layers/ES/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\n@@ -209,6 +209,7 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n procedure CreateCreditMemoCopyDocument(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var SalesHeader: Record \"Sales Header\"): Boolean\n var\n SalesHdr: Record \"Sales Header\";\n+ ConfirmQuestion: Text;\n begin\n OnBeforeCreateCreditMemoCopyDocument(SalesInvoiceHeader);\n TestNoFixedAssetInSalesInvoice(SalesInvoiceHeader);\n@@ -220,9 +221,12 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n end;\n SalesHdr.SetRange(\"Document Type\", SalesHdr.\"Document Type\"::Order);\n SalesHdr.SetRange(\"No.\", SalesInvoiceHeader.\"Order No.\");\n- if not SalesHdr.IsEmpty then\n- if not Confirm(CreateCreditMemoQst) then\n+ if not SalesHdr.IsEmpty then begin\n+ ConfirmQuestion := CreateCreditMemoQst;\n+ OnCreateCreditMemoCopyDocumentOnBeforeConfirm(SalesInvoiceHeader, SalesHdr, ConfirmQuestion);\n+ if not Confirm(ConfirmQuestion) then\n exit(false);\n+ end;\n \n CreateCopyDocument(SalesInvoiceHeader, SalesHeader, SalesHeader.\"Document Type\"::\"Credit Memo\", false);\n \n@@ -1396,6 +1400,17 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n begin\n end;\n \n+ /// \n+ /// Raised before confirming the creation of a corrective credit memo for an invoice posted from a sales order.\n+ /// \n+ /// The posted sales invoice being corrected.\n+ /// The originating sales order the invoice was posted from.\n+ /// The confirmation text to be shown. Subscribers can modify it.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCreateCreditMemoCopyDocumentOnBeforeConfirm(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var SalesHeader: Record \"Sales Header\"; var ConfirmQuestion: Text)\n+ begin\n+ end;\n+\n /// \n /// Raised before inserting the sales header during invoice correction.\n /// \ndiff --git a/App/Layers/W1/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al b/App/Layers/W1/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\nindex 6ffa10a368f..cfb8bc8bd2d 100644\n--- a/App/Layers/W1/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Sales/History/CorrectPostedSalesInvoice.Codeunit.al\n@@ -209,6 +209,7 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n procedure CreateCreditMemoCopyDocument(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var SalesHeader: Record \"Sales Header\"): Boolean\n var\n SalesHdr: Record \"Sales Header\";\n+ ConfirmQuestion: Text;\n begin\n OnBeforeCreateCreditMemoCopyDocument(SalesInvoiceHeader);\n TestNoFixedAssetInSalesInvoice(SalesInvoiceHeader);\n@@ -220,9 +221,12 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n end;\n SalesHdr.SetRange(\"Document Type\", SalesHdr.\"Document Type\"::Order);\n SalesHdr.SetRange(\"No.\", SalesInvoiceHeader.\"Order No.\");\n- if not SalesHdr.IsEmpty then\n- if not Confirm(CreateCreditMemoQst) then\n+ if not SalesHdr.IsEmpty then begin\n+ ConfirmQuestion := CreateCreditMemoQst;\n+ OnCreateCreditMemoCopyDocumentOnBeforeConfirm(SalesInvoiceHeader, SalesHdr, ConfirmQuestion);\n+ if not Confirm(ConfirmQuestion) then\n exit(false);\n+ end;\n \n CreateCopyDocument(SalesInvoiceHeader, SalesHeader, SalesHeader.\"Document Type\"::\"Credit Memo\", false);\n \n@@ -1396,6 +1400,17 @@ codeunit 1303 \"Correct Posted Sales Invoice\"\n begin\n end;\n \n+ /// \n+ /// Raised before confirming the creation of a corrective credit memo for an invoice posted from a sales order.\n+ /// \n+ /// The posted sales invoice being corrected.\n+ /// The originating sales order the invoice was posted from.\n+ /// The confirmation text to be shown. Subscribers can modify it.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnCreateCreditMemoCopyDocumentOnBeforeConfirm(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var SalesHeader: Record \"Sales Header\"; var ConfirmQuestion: Text)\n+ begin\n+ end;\n+\n /// \n /// Raised before inserting the sales header during invoice correction.\n /// \n", "expected": [{"text": "A new integration event publisher is added to codeunit 1303 \"Correct Posted Sales Invoice\".", "level": "critical"}, {"text": "In CreateCreditMemoCopyDocument, the confirmation text is moved into a local Text variable, the new event is raised to let subscribers override it, and that variable is passed to Confirm (instead of the constant CreateCreditMemoQst directly).", "level": "critical"}, {"text": "The event passes the Sales Invoice Header (var), the originating sales order record (var), and the confirmation question as a var Text parameter.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The identical change is applied to all three layer copies of the codeunit: W1, APAC, and ES (exactly three .al files modified).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement (e.g. OnCreateCreditMemoCopyDocumentOnBeforeConfirm or a similarly placement-accurate name).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Sales Invoice Header\", \"Sales Header\").", "level": "expected"}]} +{"metadata": {"area": "inventory"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30351", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 9818cb772e4..b595045651f 100644\n--- a/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4245,7 +4245,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n ExpirationDateChecked := true;\n end;\n IsHandled := false;\n- OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled);\n+ OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled, GlobalItemTrackingCode, ItemJnlLine2, SignFactor);\n if not IsHandled then\n if ItemJnlLine2.\"Entry Type\" = ItemJnlLine2.\"Entry Type\"::Transfer then\n if TempTrackingSpecification.\"Expiration Date\" = 0D then\n@@ -8210,7 +8210,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean)\n+ local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean; GlobalItemTrackingCode: Record \"Item Tracking Code\"; ItemJnlLine2: Record \"Item Journal Line\"; SignFactor: Integer)\n begin\n end;\n \ndiff --git a/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex d4f3dcda3f6..9432f4cdf72 100644\n--- a/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4217,7 +4217,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n ExpirationDateChecked := true;\n end;\n IsHandled := false;\n- OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled);\n+ OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled, GlobalItemTrackingCode, ItemJnlLine2, SignFactor);\n if not IsHandled then\n if ItemJnlLine2.\"Entry Type\" = ItemJnlLine2.\"Entry Type\"::Transfer then\n if TempTrackingSpecification.\"Expiration Date\" = 0D then\n@@ -8181,7 +8181,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean)\n+ local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean; GlobalItemTrackingCode: Record \"Item Tracking Code\"; ItemJnlLine2: Record \"Item Journal Line\"; SignFactor: Integer)\n begin\n end;\n \ndiff --git a/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 0faa5f3f7d9..2bd7d549b36 100644\n--- a/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4226,7 +4226,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n ExpirationDateChecked := true;\n end;\n IsHandled := false;\n- OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled);\n+ OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled, GlobalItemTrackingCode, ItemJnlLine2, SignFactor);\n if not IsHandled then\n if ItemJnlLine2.\"Entry Type\" = ItemJnlLine2.\"Entry Type\"::Transfer then\n if TempTrackingSpecification.\"Expiration Date\" = 0D then\n@@ -8191,7 +8191,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean)\n+ local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean; GlobalItemTrackingCode: Record \"Item Tracking Code\"; ItemJnlLine2: Record \"Item Journal Line\"; SignFactor: Integer)\n begin\n end;\n \ndiff --git a/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 285fe0ebb54..13cd24580ff 100644\n--- a/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4244,7 +4244,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n ExpirationDateChecked := true;\n end;\n IsHandled := false;\n- OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled);\n+ OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled, GlobalItemTrackingCode, ItemJnlLine2, SignFactor);\n if not IsHandled then\n if ItemJnlLine2.\"Entry Type\" = ItemJnlLine2.\"Entry Type\"::Transfer then\n if TempTrackingSpecification.\"Expiration Date\" = 0D then\n@@ -8208,7 +8208,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean)\n+ local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean; GlobalItemTrackingCode: Record \"Item Tracking Code\"; ItemJnlLine2: Record \"Item Journal Line\"; SignFactor: Integer)\n begin\n end;\n \ndiff --git a/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 6edf1a19b27..71d165bfd17 100644\n--- a/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4256,7 +4256,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n ExpirationDateChecked := true;\n end;\n IsHandled := false;\n- OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled);\n+ OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled, GlobalItemTrackingCode, ItemJnlLine2, SignFactor);\n if not IsHandled then\n if ItemJnlLine2.\"Entry Type\" = ItemJnlLine2.\"Entry Type\"::Transfer then\n if TempTrackingSpecification.\"Expiration Date\" = 0D then\n@@ -8526,7 +8526,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean)\n+ local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean; GlobalItemTrackingCode: Record \"Item Tracking Code\"; ItemJnlLine2: Record \"Item Journal Line\"; SignFactor: Integer)\n begin\n end;\n \ndiff --git a/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex dc001de1aa9..7f97dc40e6f 100644\n--- a/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4213,7 +4213,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n ExpirationDateChecked := true;\n end;\n IsHandled := false;\n- OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled);\n+ OnCheckExpirationDateOnBeforeAssignExpirationDate(TempTrackingSpecification, ExistingExpirationDate, IsHandled, GlobalItemTrackingCode, ItemJnlLine2, SignFactor);\n if not IsHandled then\n if ItemJnlLine2.\"Entry Type\" = ItemJnlLine2.\"Entry Type\"::Transfer then\n if TempTrackingSpecification.\"Expiration Date\" = 0D then\n@@ -8177,7 +8177,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n end;\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean)\n+ local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record \"Tracking Specification\" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean; GlobalItemTrackingCode: Record \"Item Tracking Code\"; ItemJnlLine2: Record \"Item Journal Line\"; SignFactor: Integer)\n begin\n end;\n \n", "expected": [{"text": "The existing integration event OnCheckExpirationDateOnBeforeAssignExpirationDate in codeunit 22 \"Item Jnl.-Post Line\" is extended with three new parameters: an \"Item Tracking Code\" record, an \"Item Journal Line\" record, and a SignFactor integer.", "level": "critical"}, {"text": "The call site in CheckExpirationDate is updated to pass the corresponding values (GlobalItemTrackingCode, ItemJnlLine2, SignFactor) to the event.", "level": "critical"}, {"text": "The publisher remains [IntegrationEvent(false, false)] with an empty body and all existing parameters (TempTrackingSpecification, ExistingExpirationDate, IsHandled) are preserved.", "level": "critical"}, {"text": "The identical change is applied to all six layer copies of the codeunit: W1, APAC, CH, ES, IT, and RU (exactly six .al files modified).", "level": "critical"}, {"text": "The new record parameters use full table names without abbreviations (\"Item Tracking Code\", \"Item Journal Line\").", "level": "expected"}]} +{"metadata": {"area": "inventory"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30352", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 9818cb772e4..363174f7ef0 100644\n--- a/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4230,6 +4230,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumOfEntries: Decimal;\n SumLot: Decimal;\n IsHandled: Boolean;\n+ SkipNewExpirationDateCheck: Boolean;\n begin\n IsHandled := false;\n OnBeforeCheckExpirationDate(\n@@ -4310,8 +4311,9 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumLot := SignFactor * ItemTrackingMgt.SumNewLotOnTrackingSpec(TempTrackingSpecification)\n else\n SumLot := SignFactor * TempTrackingSpecification.\"Quantity (Base)\";\n- OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification);\n- if (SumOfEntries > 0) and\n+ SkipNewExpirationDateCheck := false;\n+ OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification, SkipNewExpirationDateCheck);\n+ if (not SkipNewExpirationDateCheck) and (SumOfEntries > 0) and\n ((SumOfEntries <> SumLot) or (TempTrackingSpecification.\"New Lot No.\" <> TempTrackingSpecification.\"Lot No.\")\n or (TempTrackingSpecification.\"New Package No.\" <> TempTrackingSpecification.\"Package No.\"))\n then\n@@ -8200,7 +8202,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n #endif\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary)\n+ local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary; var SkipNewExpirationDateCheck: Boolean)\n begin\n end;\n \ndiff --git a/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex d4f3dcda3f6..a91a6117667 100644\n--- a/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4202,6 +4202,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumOfEntries: Decimal;\n SumLot: Decimal;\n IsHandled: Boolean;\n+ SkipNewExpirationDateCheck: Boolean;\n begin\n IsHandled := false;\n OnBeforeCheckExpirationDate(\n@@ -4282,8 +4283,9 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumLot := SignFactor * ItemTrackingMgt.SumNewLotOnTrackingSpec(TempTrackingSpecification)\n else\n SumLot := SignFactor * TempTrackingSpecification.\"Quantity (Base)\";\n- OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification);\n- if (SumOfEntries > 0) and\n+ SkipNewExpirationDateCheck := false;\n+ OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification, SkipNewExpirationDateCheck);\n+ if (not SkipNewExpirationDateCheck) and (SumOfEntries > 0) and\n ((SumOfEntries <> SumLot) or (TempTrackingSpecification.\"New Lot No.\" <> TempTrackingSpecification.\"Lot No.\")\n or (TempTrackingSpecification.\"New Package No.\" <> TempTrackingSpecification.\"Package No.\"))\n then\n@@ -8171,7 +8173,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n #endif\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary)\n+ local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary; var SkipNewExpirationDateCheck: Boolean)\n begin\n end;\n \ndiff --git a/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 0faa5f3f7d9..6088672a3d5 100644\n--- a/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4211,6 +4211,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumOfEntries: Decimal;\n SumLot: Decimal;\n IsHandled: Boolean;\n+ SkipNewExpirationDateCheck: Boolean;\n begin\n IsHandled := false;\n OnBeforeCheckExpirationDate(\n@@ -4291,8 +4292,9 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumLot := SignFactor * ItemTrackingMgt.SumNewLotOnTrackingSpec(TempTrackingSpecification)\n else\n SumLot := SignFactor * TempTrackingSpecification.\"Quantity (Base)\";\n- OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification);\n- if (SumOfEntries > 0) and\n+ SkipNewExpirationDateCheck := false;\n+ OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification, SkipNewExpirationDateCheck);\n+ if (not SkipNewExpirationDateCheck) and (SumOfEntries > 0) and\n ((SumOfEntries <> SumLot) or (TempTrackingSpecification.\"New Lot No.\" <> TempTrackingSpecification.\"Lot No.\")\n or (TempTrackingSpecification.\"New Package No.\" <> TempTrackingSpecification.\"Package No.\"))\n then\n@@ -8181,7 +8183,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n #endif\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary)\n+ local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary; var SkipNewExpirationDateCheck: Boolean)\n begin\n end;\n \ndiff --git a/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 285fe0ebb54..031fa95ca9a 100644\n--- a/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4229,6 +4229,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumOfEntries: Decimal;\n SumLot: Decimal;\n IsHandled: Boolean;\n+ SkipNewExpirationDateCheck: Boolean;\n begin\n IsHandled := false;\n OnBeforeCheckExpirationDate(\n@@ -4309,8 +4310,9 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumLot := SignFactor * ItemTrackingMgt.SumNewLotOnTrackingSpec(TempTrackingSpecification)\n else\n SumLot := SignFactor * TempTrackingSpecification.\"Quantity (Base)\";\n- OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification);\n- if (SumOfEntries > 0) and\n+ SkipNewExpirationDateCheck := false;\n+ OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification, SkipNewExpirationDateCheck);\n+ if (not SkipNewExpirationDateCheck) and (SumOfEntries > 0) and\n ((SumOfEntries <> SumLot) or (TempTrackingSpecification.\"New Lot No.\" <> TempTrackingSpecification.\"Lot No.\")\n or (TempTrackingSpecification.\"New Package No.\" <> TempTrackingSpecification.\"Package No.\"))\n then\n@@ -8198,7 +8200,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n #endif\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary)\n+ local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary; var SkipNewExpirationDateCheck: Boolean)\n begin\n end;\n \ndiff --git a/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex 6edf1a19b27..8997d1cacf5 100644\n--- a/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4241,6 +4241,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumOfEntries: Decimal;\n SumLot: Decimal;\n IsHandled: Boolean;\n+ SkipNewExpirationDateCheck: Boolean;\n begin\n IsHandled := false;\n OnBeforeCheckExpirationDate(\n@@ -4321,8 +4322,9 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumLot := SignFactor * ItemTrackingMgt.SumNewLotOnTrackingSpec(TempTrackingSpecification)\n else\n SumLot := SignFactor * TempTrackingSpecification.\"Quantity (Base)\";\n- OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification);\n- if (SumOfEntries > 0) and\n+ SkipNewExpirationDateCheck := false;\n+ OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification, SkipNewExpirationDateCheck);\n+ if (not SkipNewExpirationDateCheck) and (SumOfEntries > 0) and\n ((SumOfEntries <> SumLot) or (TempTrackingSpecification.\"New Lot No.\" <> TempTrackingSpecification.\"Lot No.\")\n or (TempTrackingSpecification.\"New Package No.\" <> TempTrackingSpecification.\"Package No.\"))\n then\n@@ -8516,7 +8518,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n #endif\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary)\n+ local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary; var SkipNewExpirationDateCheck: Boolean)\n begin\n end;\n \ndiff --git a/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al b/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\nindex dc001de1aa9..485f4b9ad17 100644\n--- a/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al\n@@ -4198,6 +4198,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumOfEntries: Decimal;\n SumLot: Decimal;\n IsHandled: Boolean;\n+ SkipNewExpirationDateCheck: Boolean;\n begin\n IsHandled := false;\n OnBeforeCheckExpirationDate(\n@@ -4278,8 +4279,9 @@ codeunit 22 \"Item Jnl.-Post Line\"\n SumLot := SignFactor * ItemTrackingMgt.SumNewLotOnTrackingSpec(TempTrackingSpecification)\n else\n SumLot := SignFactor * TempTrackingSpecification.\"Quantity (Base)\";\n- OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification);\n- if (SumOfEntries > 0) and\n+ SkipNewExpirationDateCheck := false;\n+ OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification, SkipNewExpirationDateCheck);\n+ if (not SkipNewExpirationDateCheck) and (SumOfEntries > 0) and\n ((SumOfEntries <> SumLot) or (TempTrackingSpecification.\"New Lot No.\" <> TempTrackingSpecification.\"Lot No.\")\n or (TempTrackingSpecification.\"New Package No.\" <> TempTrackingSpecification.\"Package No.\"))\n then\n@@ -8167,7 +8169,7 @@ codeunit 22 \"Item Jnl.-Post Line\"\n #endif\n \n [IntegrationEvent(false, false)]\n- local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary)\n+ local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record \"Tracking Specification\" temporary; var SkipNewExpirationDateCheck: Boolean)\n begin\n end;\n \n", "expected": [{"text": "The existing integration event OnCheckExpirationDateOnAfterCalcSumLot in codeunit 22 \"Item Jnl.-Post Line\" is extended with a new var Boolean parameter (SkipNewExpirationDateCheck).", "level": "critical"}, {"text": "The new variable is initialized to false, passed by reference to the event, and used to guard the subsequent TempTrackingSpecification.TestField(\"New Expiration Date\", ...) so the test is skipped when a subscriber sets it to true.", "level": "critical"}, {"text": "The publisher remains [IntegrationEvent(false, false)] with an empty body and all existing parameters (SumLot, SignFactor, TempTrackingSpecification) are preserved.", "level": "critical"}, {"text": "The identical change is applied to all six layer copies of the codeunit: W1, APAC, CH, ES, IT, and RU (exactly six .al files modified).", "level": "critical"}, {"text": "The new parameter is a var Boolean and the guard uses (not SkipNewExpirationDateCheck) in the existing condition.", "level": "expected"}]} +{"metadata": {"area": "sales"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30290", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 06e6272a1a7..8e05044e348 100644\n--- a/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8138,7 +8138,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Tax Document Type\" := BillToCustomer.\"Tax Document Type\";\n \"WHT Business Posting Group\" := BillToCustomer.\"WHT Business Posting Group\";\n@@ -10525,6 +10528,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 53566816419..429f2b28325 100644\n--- a/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8001,7 +8001,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10386,6 +10389,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al\nindex a80a925abb9..42178a1cb74 100644\n--- a/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8030,7 +8030,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10441,6 +10444,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 326436a8fa4..6053a97d510 100644\n--- a/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -7971,7 +7971,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10356,6 +10359,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al\nindex f8a90897fe1..cd065df9fe3 100644\n--- a/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8096,7 +8096,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10515,6 +10518,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 72e1b35541d..923f588242a 100644\n--- a/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -7971,7 +7971,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10356,6 +10359,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 67c35c5199a..c3dd0c7a4da 100644\n--- a/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -7975,7 +7975,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10360,6 +10363,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 326436a8fa4..6053a97d510 100644\n--- a/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -7971,7 +7971,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10356,6 +10359,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al\nindex d887a42dfa1..118a2c3d3d5 100644\n--- a/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8712,7 +8712,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n if not CheckVATExemption() then\n FieldError(\"Bill-to Customer No.\");\n@@ -11087,6 +11090,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al\nindex d6fa4ba1a64..e787e2de565 100644\n--- a/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8245,7 +8245,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10670,6 +10673,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al\nindex bca6498c706..f1468c38176 100644\n--- a/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -7995,7 +7995,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10382,6 +10385,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al\nindex ae96fcb30d1..410e3585184 100644\n--- a/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8062,7 +8062,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10447,6 +10450,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al\nindex b5f88f8f215..8a19f018735 100644\n--- a/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8480,7 +8480,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10875,6 +10878,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 0a6aed9c4aa..84e6688a243 100644\n--- a/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -8000,7 +8000,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10385,6 +10388,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \ndiff --git a/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al\nindex cb937f39808..352dac0e53f 100644\n--- a/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -7971,7 +7971,10 @@ table 36 \"Sales Header\"\n end else\n \"Payment Method Code\" := BillToCustomer.\"Payment Method Code\";\n \n- AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n+ IsHandled := false;\n+ OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled);\n+ if not IsHandled then\n+ AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer);\n \n \"Customer Posting Group\" := BillToCustomer.\"Customer Posting Group\";\n \"Currency Code\" := BillToCustomer.\"Currency Code\";\n@@ -10356,6 +10359,17 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before updating the VAT registration setup on a bill-to customer change in the sales header.\n+ /// \n+ /// The sales header record being modified.\n+ /// The bill-to customer record.\n+ /// Set to true to skip the standard update of the VAT registration setup.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record \"Sales Header\"; BillToCustomer: Record Customer; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n /// \n /// Raised after extended text is transferred for sales line recreation.\n /// \n", "expected": [{"text": "A new integration event publisher is added to table 36 \"Sales Header\".", "level": "critical"}, {"text": "In SetBillToCustomerAddressFieldsFromCustomer, the event is raised with the IsHandled pattern (IsHandled initialized to false, event raised) so that the call to AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer) runs only inside if not IsHandled then.", "level": "critical"}, {"text": "The event passes the Sales Header record (Rec, var), the Bill-to Customer record, and IsHandled (var).", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The identical change is applied to all 15 layer copies of the table (W1, APAC, BE, CH, CZ, ES, FI, FR, GB, IT, NA, NL, NO, RU, SE - exactly 15 .al files modified).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement (e.g. OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Sales Header\", Customer).", "level": "expected"}]} +{"metadata": {"area": "sales"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30330", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/W1/BaseApp/Sales/History/CancelPstdSalesInvYesNo.Codeunit.al b/App/Layers/W1/BaseApp/Sales/History/CancelPstdSalesInvYesNo.Codeunit.al\nindex 8d6e9ff8941..beb0247cfbc 100644\n--- a/App/Layers/W1/BaseApp/Sales/History/CancelPstdSalesInvYesNo.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Sales/History/CancelPstdSalesInvYesNo.Codeunit.al\n@@ -36,12 +36,15 @@ codeunit 1323 \"Cancel PstdSalesInv (Yes/No)\"\n CorrectPostedSalesInvoice: Codeunit \"Correct Posted Sales Invoice\";\n PageManagement: Codeunit \"Page Management\";\n IsHandled: Boolean;\n+ ConfirmQuestion: Text;\n begin\n IsHandled := false;\n OnCancelInvoiceOnBeforeTestCorrectInvoiceIsAllowed(SalesInvoiceHeader, IsHandled);\n if not IsHandled then\n CorrectPostedSalesInvoice.TestCorrectInvoiceIsAllowed(SalesInvoiceHeader, true);\n- if Confirm(CancelPostedInvoiceQst) then\n+ ConfirmQuestion := CancelPostedInvoiceQst;\n+ OnCancelInvoiceOnBeforeConfirm(SalesInvoiceHeader, ConfirmQuestion);\n+ if Confirm(ConfirmQuestion) then\n if CorrectPostedSalesInvoice.CancelPostedInvoice(SalesInvoiceHeader) then\n if Confirm(OpenPostedCreditMemoQst) then begin\n CancelledDocument.FindSalesCancelledInvoice(SalesInvoiceHeader.\"No.\");\n@@ -56,6 +59,11 @@ codeunit 1323 \"Cancel PstdSalesInv (Yes/No)\"\n exit(false);\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnCancelInvoiceOnBeforeConfirm(var SalesInvoiceHeader: Record \"Sales Invoice Header\"; var ConfirmQuestion: Text)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnCancelInvoiceOnBeforePostedSalesCreditMemo(var SalesCrMemoHeader: Record \"Sales Cr.Memo Header\"; var IsHandled: Boolean)\n begin\n", "expected": [{"text": "A new integration event publisher is added to codeunit 1323 \"Cancel PstdSalesInv (Yes/No)\" in the W1 BaseApp file CancelPstdSalesInvYesNo.Codeunit.al.", "level": "critical"}, {"text": "In CancelInvoice, the confirmation text is moved into a local Text variable, the new event is raised to let subscribers override it, and that variable is passed to Confirm (instead of the constant CancelPostedInvoiceQst directly).", "level": "critical"}, {"text": "The event passes the Sales Invoice Header (var) and the confirmation question as a var Text parameter.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The change is confined to the W1 layer: exactly one .al file is modified (this codeunit exists only in W1).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement before the confirmation (e.g. OnCancelInvoiceOnBeforeConfirm).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Sales Invoice Header\").", "level": "expected"}]} +{"metadata": {"area": "inventory"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30349", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/APAC/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex 61f8be36513..72f926814d9 100644\n--- a/App/Layers/APAC/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/APAC/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3665,6 +3665,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -4946,6 +4947,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\ndiff --git a/App/Layers/CH/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/CH/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex fd7b6dd63e6..37f9292108c 100644\n--- a/App/Layers/CH/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/CH/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3707,6 +3707,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -4988,6 +4989,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\ndiff --git a/App/Layers/ES/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/ES/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex b6355d6e017..3d77ca6253d 100644\n--- a/App/Layers/ES/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/ES/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3675,6 +3675,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -4956,6 +4957,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\ndiff --git a/App/Layers/FR/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/FR/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex 987d32bb010..95293d9a967 100644\n--- a/App/Layers/FR/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/FR/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3669,6 +3669,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -4950,6 +4951,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\ndiff --git a/App/Layers/GB/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/GB/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex 186b28273da..b9d6e31353d 100644\n--- a/App/Layers/GB/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/GB/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3669,6 +3669,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -4950,6 +4951,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\ndiff --git a/App/Layers/IT/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/IT/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex f1be508f583..8a4afd59cdf 100644\n--- a/App/Layers/IT/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/IT/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3739,6 +3739,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -5020,6 +5021,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\ndiff --git a/App/Layers/RU/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/RU/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex 72d4bf8c4cf..174e85b4ced 100644\n--- a/App/Layers/RU/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/RU/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3876,6 +3876,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -5157,6 +5158,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\ndiff --git a/App/Layers/W1/BaseApp/Inventory/Journal/ItemJournalLine.Table.al b/App/Layers/W1/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\nindex c3d165693c6..fe0b216e55d 100644\n--- a/App/Layers/W1/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n+++ b/App/Layers/W1/BaseApp/Inventory/Journal/ItemJournalLine.Table.al\n@@ -3659,6 +3659,7 @@ table 83 \"Item Journal Line\"\n /// Item tracking setup to use.\n procedure CheckTrackingIfRequired(ItemTrackingSetup: Record \"Item Tracking Setup\")\n begin\n+ OnBeforeCheckTrackingIfRequired(Rec, ItemTrackingSetup);\n if ItemTrackingSetup.\"Serial No. Required\" then\n TestField(\"Serial No.\");\n if ItemTrackingSetup.\"Lot No. Required\" then\n@@ -4940,6 +4941,11 @@ table 83 \"Item Journal Line\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnAfterCheckNewTrackingIfRequired(ItemJournalLine: Record \"Item Journal Line\"; ItemTrackingSetup: Record \"Item Tracking Setup\");\n begin\n", "expected": [{"text": "A new integration event publisher is added to table 83 \"Item Journal Line\".", "level": "critical"}, {"text": "The event is raised at the very start of the CheckTrackingIfRequired procedure, before the standard TestField checks.", "level": "critical"}, {"text": "The event passes the Item Journal Line record and the \"Item Tracking Setup\" record.", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The identical change is applied to all eight layer copies of the table: W1, APAC, CH, ES, FR, GB, IT, and RU (exactly eight .al files modified).", "level": "critical"}, {"text": "The event name follows BC naming conventions for a before-event on CheckTrackingIfRequired (e.g. OnBeforeCheckTrackingIfRequired) and is spelled correctly.", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Item Journal Line\", \"Item Tracking Setup\").", "level": "expected"}]} +{"metadata": {"area": "sales"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30335", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 06e6272a1a7..88117357571 100644\n--- a/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/APAC/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4676,7 +4676,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -10114,6 +10117,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 53566816419..d8a04fbb227 100644\n--- a/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/BE/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4615,7 +4615,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9975,6 +9978,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al\nindex a80a925abb9..1bb51388e3c 100644\n--- a/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/CH/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4658,7 +4658,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -10030,6 +10033,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 326436a8fa4..f0d0a48b696 100644\n--- a/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/CZ/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4602,7 +4602,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9945,6 +9948,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al\nindex f8a90897fe1..032c861c87f 100644\n--- a/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/ES/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4705,7 +4705,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -10104,6 +10107,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 72e1b35541d..efff3eec28f 100644\n--- a/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/FI/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4602,7 +4602,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9945,6 +9948,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 67c35c5199a..04e264c37f4 100644\n--- a/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/FR/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4606,7 +4606,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9949,6 +9952,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 326436a8fa4..f0d0a48b696 100644\n--- a/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/GB/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4602,7 +4602,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9945,6 +9948,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al\nindex d887a42dfa1..3b59c0f688b 100644\n--- a/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/IT/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4982,7 +4982,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -10676,6 +10679,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al\nindex d6fa4ba1a64..28e39de6e0d 100644\n--- a/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/NA/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4820,7 +4820,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -10259,6 +10262,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al\nindex bca6498c706..e5c489e5ce0 100644\n--- a/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/NL/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4626,7 +4626,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9971,6 +9974,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al\nindex ae96fcb30d1..dfea1c8ecdf 100644\n--- a/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/NO/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4683,7 +4683,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -10036,6 +10039,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al\nindex b5f88f8f215..42c27aa4557 100644\n--- a/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/RU/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -5019,7 +5019,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -10464,6 +10467,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al\nindex 0a6aed9c4aa..6dc1402513e 100644\n--- a/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/SE/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4631,7 +4631,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9974,6 +9977,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \ndiff --git a/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al b/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al\nindex cb937f39808..063b6b34730 100644\n--- a/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al\n+++ b/App/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al\n@@ -4602,7 +4602,10 @@ table 36 \"Sales Header\"\n ShouldCreateSalsesLine := not TempSalesLine.IsExtendedText();\n OnRecreateSalesLinesHandleSupplementTypesOnAfterCalcShouldCreateSalsesLine(TempSalesLine, ShouldCreateSalsesLine, SalesLine);\n if ShouldCreateSalsesLine then begin\n- CreateSalesLine(TempSalesLine);\n+ IsHandled := false;\n+ OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec);\n+ if not IsHandled then\n+ CreateSalesLine(TempSalesLine);\n ExtendedTextAdded := false;\n OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec);\n \n@@ -9945,6 +9948,18 @@ table 36 \"Sales Header\"\n begin\n end;\n \n+ /// \n+ /// Raised before a sales line is recreated from temporary storage, allowing subscribers to replace the standard sales line creation.\n+ /// \n+ /// Set to true to skip the standard CreateSalesLine call.\n+ /// The sales line record being recreated.\n+ /// The temporary sales line used as source.\n+ /// The parent sales header record.\n+ [IntegrationEvent(false, false)]\n+ local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record \"Sales Line\"; var TempSalesLine: Record \"Sales Line\" temporary; var SalesHeader: Record \"Sales Header\")\n+ begin\n+ end;\n+\n /// \n /// Raised after a sales line is recreated from temporary storage.\n /// \n", "expected": [{"text": "A new integration event publisher is added to table 36 \"Sales Header\".", "level": "critical"}, {"text": "In RecreateSalesLinesHandleSupplementTypes, the event is raised with the IsHandled pattern immediately before CreateSalesLine(TempSalesLine), so the standard CreateSalesLine call runs only inside if not IsHandled then.", "level": "critical"}, {"text": "The event passes IsHandled (var), the Sales Line record (var), the temporary Sales Line record (var ... temporary), and the Sales Header (Rec, var).", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The identical change is applied to all 15 layer copies of the table (W1, APAC, BE, CH, CZ, ES, FI, FR, GB, IT, NA, NL, NO, RU, SE - exactly 15 .al files modified).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement before CreateSalesLine (e.g. OnBeforeRecreateSalesLine).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Sales Line\", \"Sales Header\").", "level": "expected"}]} +{"metadata": {"area": "purchases"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30216", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/APAC/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex 10cd9282081..29e253cbba6 100644\n--- a/App/Layers/APAC/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/APAC/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6240,7 +6240,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10884,6 +10887,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/BE/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/BE/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex d18b1cde202..96d313b8da7 100644\n--- a/App/Layers/BE/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/BE/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6049,7 +6049,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10450,6 +10453,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/CH/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/CH/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex d7e0620747d..1776ea38104 100644\n--- a/App/Layers/CH/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/CH/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6010,7 +6010,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10438,6 +10441,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/ES/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/ES/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex 9e8964bb530..5088139b4be 100644\n--- a/App/Layers/ES/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/ES/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6128,7 +6128,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10618,6 +10621,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/FI/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/FI/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex c29ebb02d40..76cb98dc0f6 100644\n--- a/App/Layers/FI/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/FI/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6007,7 +6007,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10408,6 +10411,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/GB/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/GB/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex 3ee76260ac6..0e8aeaf3bc3 100644\n--- a/App/Layers/GB/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/GB/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6034,7 +6034,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10435,6 +10438,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/IT/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/IT/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex 08d01188af3..c85424613e9 100644\n--- a/App/Layers/IT/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/IT/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6197,7 +6197,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10800,6 +10803,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/NA/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/NA/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex 0277f162c4f..36058cff319 100644\n--- a/App/Layers/NA/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/NA/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6209,7 +6209,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -11150,6 +11153,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/RU/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/RU/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex 8e3e1a4cdfd..2e756f7f722 100644\n--- a/App/Layers/RU/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/RU/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6186,7 +6186,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -11181,6 +11184,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\ndiff --git a/App/Layers/W1/BaseApp/Purchases/Posting/PurchPost.Codeunit.al b/App/Layers/W1/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\nindex e31b4f5b0cd..712be0924a9 100644\n--- a/App/Layers/W1/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n+++ b/App/Layers/W1/BaseApp/Purchases/Posting/PurchPost.Codeunit.al\n@@ -6001,7 +6001,10 @@ codeunit 90 \"Purch.-Post\"\n \n if QtyToBeInvoiced <> 0 then begin\n PurchLine.\"Qty. to Invoice\" := QtyToBeInvoiced;\n- InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n+ IsHandled := false;\n+ OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY, IsHandled);\n+ if not IsHandled then\n+ InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY);\n end;\n end;\n end;\n@@ -10402,6 +10405,11 @@ codeunit 90 \"Purch.-Post\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine(var PurchaseLine: Record \"Purchase Line\"; QuantityToBeInvoiced: Decimal; var PurchaseHeader: Record \"Purchase Header\"; var PurchaseLineACY: Record \"Purchase Line\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnPostItemJnlLineWhseLineOnAfterPostRevert(var TempWhseJnlLine: Record \"Warehouse Journal Line\" temporary; PurchaseLine: Record \"Purchase Line\")\n begin\n", "expected": [{"text": "A new integration event publisher is added to codeunit 90 \"Purch.-Post\".", "level": "critical"}, {"text": "In PostItemJnlLineJobConsumption, the event is raised with the IsHandled pattern immediately before InvoicePostingInterface.PrepareJobLine(...), so the standard PrepareJobLine call runs only inside if not IsHandled then (after PurchLine.\"Qty. to Invoice\" is set).", "level": "critical"}, {"text": "The event passes the Purchase Line record (var), the quantity to be invoiced, the Purchase Header record (var), the additional-currency Purchase Line record (var), and IsHandled (var).", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The identical change is applied to all 10 layer copies of the codeunit: W1, APAC, BE, CH, ES, FI, GB, IT, NA, and RU (exactly 10 .al files modified).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement before PrepareJobLine (e.g. OnPostItemJnlLineJobConsumptionOnBeforePrepareJobLine).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Purchase Line\", \"Purchase Header\").", "level": "expected"}]} +{"metadata": {"area": "purchases"}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Impl-30222", "base_commit": "74479eba317847697a5acc52f4d50b811223d41c", "created_at": "2026-07-17T00:00:00Z", "environment_setup_version": "28.0", "project_paths": ["App/Layers/W1/BaseApp"], "patch": "diff --git a/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 0ee323cf079..9ef6d5e7fd9 100644\n--- a/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/APAC/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -317,8 +317,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -9159,6 +9163,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/BE/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/BE/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 7bba8ba669f..797f245cee9 100644\n--- a/App/Layers/BE/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/BE/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -308,8 +308,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8923,6 +8927,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/CH/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/CH/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 7fb72cd0c1d..00f80b700e5 100644\n--- a/App/Layers/CH/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/CH/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -309,8 +309,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8978,6 +8982,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex acc8db0903c..a5c3ddee580 100644\n--- a/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/DACH/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -305,8 +305,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8913,6 +8917,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/ES/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/ES/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 58bff43e62d..94c820d47a9 100644\n--- a/App/Layers/ES/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/ES/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -311,8 +311,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -9065,6 +9069,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/FI/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/FI/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 34e8437eb58..b6e2c182236 100644\n--- a/App/Layers/FI/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/FI/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -304,8 +304,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8956,6 +8960,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/GB/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/GB/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 940f3055ed6..6bb9906048b 100644\n--- a/App/Layers/GB/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/GB/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -304,8 +304,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8915,6 +8919,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/IT/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/IT/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex bd7e148eaa2..deb66952902 100644\n--- a/App/Layers/IT/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/IT/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -342,8 +342,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -9546,6 +9550,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/NA/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/NA/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 674ed7b1c67..74f3abd8875 100644\n--- a/App/Layers/NA/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/NA/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -305,8 +305,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -9104,6 +9108,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/NL/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/NL/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 350fcba0668..8de0164879d 100644\n--- a/App/Layers/NL/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/NL/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -308,8 +308,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8934,6 +8938,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/NO/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/NO/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex ec25e3fbe88..303c99c5a67 100644\n--- a/App/Layers/NO/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/NO/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -307,8 +307,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8939,6 +8943,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/RU/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/RU/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex a19aac702af..345fafe7f1e 100644\n--- a/App/Layers/RU/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/RU/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -309,8 +309,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -9573,6 +9577,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/SE/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/SE/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex 045bb60476b..fe0cba8fad6 100644\n--- a/App/Layers/SE/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/SE/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -304,8 +304,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8912,6 +8916,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\ndiff --git a/App/Layers/W1/BaseApp/Purchases/Document/PurchaseHeader.Table.al b/App/Layers/W1/BaseApp/Purchases/Document/PurchaseHeader.Table.al\nindex af236a023f3..647603312b8 100644\n--- a/App/Layers/W1/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n+++ b/App/Layers/W1/BaseApp/Purchases/Document/PurchaseHeader.Table.al\n@@ -304,8 +304,12 @@ table 38 \"Purchase Header\"\n \n if (xRec.\"Buy-from Vendor No.\" = \"Buy-from Vendor No.\") and\n (xRec.\"Pay-to Vendor No.\" <> \"Pay-to Vendor No.\")\n- then\n- RecreatePurchLines(PayToVendorTxt);\n+ then begin\n+ IsHandled := false;\n+ OnValidatePayToVendorNoOnBeforeRecreatePurchLines(Rec, xRec, IsHandled);\n+ if not IsHandled then\n+ RecreatePurchLines(PayToVendorTxt);\n+ end;\n \n if not SkipPayToContact then\n UpdatePayToCont(\"Pay-to Vendor No.\");\n@@ -8907,6 +8911,11 @@ table 38 \"Purchase Header\"\n begin\n end;\n \n+ [IntegrationEvent(false, false)]\n+ local procedure OnValidatePayToVendorNoOnBeforeRecreatePurchLines(var PurchaseHeader: Record \"Purchase Header\"; xPurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n [IntegrationEvent(false, false)]\n local procedure OnValidatePricesIncludingVATOnBeforePurchLineModify(var PurchaseHeader: Record \"Purchase Header\"; var PurchLine: Record \"Purchase Line\"; Currency: Record Currency; RecalculatePrice: Boolean);\n begin\n", "expected": [{"text": "A new integration event publisher is added to table 38 \"Purchase Header\".", "level": "critical"}, {"text": "In the OnValidate trigger of field \"Pay-to Vendor No.\", the event is raised with the IsHandled pattern immediately before RecreatePurchLines(PayToVendorTxt), so the standard RecreatePurchLines call runs only inside if not IsHandled then.", "level": "critical"}, {"text": "The event passes IsHandled (var), the Purchase Header record (Rec, var), and the previous Purchase Header record (xRec).", "level": "critical"}, {"text": "The event publisher is declared as [IntegrationEvent(false, false)] local procedure with an empty body.", "level": "critical"}, {"text": "The identical change is applied to all 14 layer copies of the table (W1, APAC, BE, CH, DACH, ES, FI, GB, IT, NA, NL, NO, RU, SE - exactly 14 .al files modified).", "level": "critical"}, {"text": "The event name follows BC naming conventions for its placement before RecreatePurchLines (e.g. OnValidatePayToVendorNoOnBeforeRecreatePurchLines or OnBeforeRecreatePurchLines).", "level": "expected"}, {"text": "Record parameters use full table names without abbreviations (\"Purchase Header\").", "level": "expected"}]} diff --git a/dataset/extensibility_request_triage.jsonl b/dataset/extensibility_request_triage.jsonl new file mode 100644 index 000000000..c5e7cd89f --- /dev/null +++ b/dataset/extensibility_request_triage.jsonl @@ -0,0 +1,17 @@ +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29447", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Event Request] Codeunit 5880 \"Phys. Invt. Order-Finish\"", "description": "### Why do you need this change?\n\nHi,\ni need an event in the Codeunit 5880 \"Phys. Invt. Order-Finish\" in the procedure \"CreateOrderTrackingBufferLines\" to modify the TempInvtOrderTrackingBuffer record. Each pass through the final loop finalizes one tracking buffer line together with its own DocNo and LineNo, and I need to read and adjust those values on the exact line being processed right before it is persisted \u2014 a single hook placed before or after the whole loop would no longer expose the individual line, its document/line context, or the computed quantities I have to react to.\n\n### Describe the request\n\nAdd a new event in the procedure \"CreateOrderTrackingBufferLines\", before modifying the record TempInvtOrderTrackingBuffer.\n\n procedure CreateOrderTrackingBufferLines(DocNo: Code[20]; LineNo: Integer)\n var\n ExpInvtOrderTracking: Record \"Exp. Invt. Order Tracking\";\n ItemTrackingSetup: Record \"Item Tracking Setup\";\n begin\n PhysInvtRecordLine.Reset();\n PhysInvtRecordLine.SetCurrentKey(\"Order No.\", \"Order Line No.\");\n PhysInvtRecordLine.SetRange(\"Order No.\", DocNo);\n PhysInvtRecordLine.SetRange(\"Order Line No.\", LineNo);\n PhysInvtRecordLine.SetFilter(\"Quantity (Base)\", '<>%1', 0);\n if PhysInvtRecordLine.Find('-') then\n repeat\n ItemTrackingSetup.\"Serial No.\" := PhysInvtRecordLine.\"Serial No.\";\n ItemTrackingSetup.\"Lot No.\" := PhysInvtRecordLine.\"Lot No.\";\n ItemTrackingSetup.\"Package No.\" := PhysInvtRecordLine.\"Package No.\";\n UpdateBufferRecordedQty(ItemTrackingSetup, PhysInvtRecordLine.\"Quantity (Base)\", LineNo);\n OnCreateOrderTrackingBufferLinesFromPhysInvtRecordLine(TempInvtOrderTrackingBuffer, PhysInvtRecordLine);\n until PhysInvtRecordLine.Next() = 0;\n\n ExpInvtOrderTracking.Reset();\n ExpInvtOrderTracking.SetRange(\"Order No\", DocNo);\n ExpInvtOrderTracking.SetRange(\"Order Line No.\", LineNo);\n if ExpInvtOrderTracking.Find('-') then\n repeat\n ItemTrackingSetup.\"Serial No.\" := ExpInvtOrderTracking.\"Serial No.\";\n ItemTrackingSetup.\"Lot No.\" := ExpInvtOrderTracking.\"Lot No.\";\n ItemTrackingSetup.\"Package No.\" := ExpInvtOrderTracking.\"Package No.\";\n UpdateBufferExpectedQty(ItemTrackingSetup, ExpInvtOrderTracking.\"Quantity (Base)\", LineNo);\n OnCreateOrderTrackingBufferLinesFromExpInvtOrderTracking(TempInvtOrderTrackingBuffer, ExpInvtOrderTracking);\n until ExpInvtOrderTracking.Next() = 0;\n\n TempInvtOrderTrackingBuffer.Reset();\n if TempInvtOrderTrackingBuffer.Find('-') then\n repeat\n TempInvtOrderTrackingBuffer.\"Qty. To Transfer\" :=\n TempInvtOrderTrackingBuffer.\"Qty. Recorded (Base)\" - TempInvtOrderTrackingBuffer.\"Qty. Expected (Base)\";\n TempInvtOrderTrackingBuffer.\"Outstanding Quantity\" := TempInvtOrderTrackingBuffer.\"Qty. To Transfer\";\n TempInvtOrderTrackingBuffer.Open := TempInvtOrderTrackingBuffer.\"Outstanding Quantity\" <> 0;\n\n // Start Event\n\n OnCreateOrderTrackingBufferLinesOnBeforeModifyTempInvtOrderTracking(TempInvtOrderTrackingBuffer, DocNo, LineNo);\n\n // End Event\n\n TempInvtOrderTrackingBuffer.Modify();\n until TempInvtOrderTrackingBuffer.Next() = 0;\n end;\n\n**New Event**\n```\n[IntegrationEvent(false, false)]\n local procedure OnCreateOrderTrackingBufferLinesOnBeforeModifyTempInvtOrderTracking(var TempInvtOrderTracking: Record \"Invt. Order Tracking\" temporary; DocNo: Code[20]; LineNo: Integer)\n begin\n end;\n```", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, event-request.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ Analysis complete - approved for implementation\n\n**Implementation:**\n\nAdd event publisher in procedure `CreateOrderTrackingBufferLines` before `TempInvtOrderTrackingBuffer.Modify()`:\n\n```al\nTempInvtOrderTrackingBuffer.Reset();\nif TempInvtOrderTrackingBuffer.Find('-') then\n repeat\n TempInvtOrderTrackingBuffer.\"Qty. To Transfer\" :=\n TempInvtOrderTrackingBuffer.\"Qty. Recorded (Base)\" - TempInvtOrderTrackingBuffer.\"Qty. Expected (Base)\";\n TempInvtOrderTrackingBuffer.\"Outstanding Quantity\" := TempInvtOrderTrackingBuffer.\"Qty. To Transfer\";\n TempInvtOrderTrackingBuffer.Open := TempInvtOrderTrackingBuffer.\"Outstanding Quantity\" <> 0;\n OnCreateOrderTrackingBufferLinesOnBeforeModifyTempInvtOrderTracking(TempInvtOrderTrackingBuffer, DocNo, LineNo);\n TempInvtOrderTrackingBuffer.Modify();\n until TempInvtOrderTrackingBuffer.Next() = 0;\n```\n\nAdd event publisher procedure at the end of the codeunit (before the closing brace):\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnCreateOrderTrackingBufferLinesOnBeforeModifyTempInvtOrderTracking(var TempInvtOrderTracking: Record \"Invt. Order Tracking\" temporary; DocNo: Code[20]; LineNo: Integer)\nbegin\nend;\n```", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29442", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[W1][Page][176][Standard Purchase Code Subform] Make procedure UpdateTypeText() not local", "description": "### Why do you need this change?\n\nProcedure UpdateTypeText() is in local scope and it updates field variable TypeAsText. In our page extension we want to call this same UpdateTypeText logic rather than duplicating it, so the TypeAsText formatting stays consistent with the base page instead of us copying the procedure into our own code.\n\n### Describe the request\n\nRemove \"local\" scope from procedure UpdateTypeText() in page 176 \"Standard Purchase Code Subform\";", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, request-for-external.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n\u2705 Analysis complete - approved for implementation\n\n**Implementation:**\n\nRemove `local` scope from procedure `UpdateTypeText()` in Page 176 \"Standard Purchase Code Subform\":\n\n```al\nprocedure UpdateTypeText()\nvar\n RecRef: RecordRef;\nbegin\n RecRef.GetTable(Rec);\n TypeAsText := TempOptionLookupBuffer.FormatOption(RecRef.Field(Rec.FieldNo(Type)));\nend;\n```", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29519", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Change Request] table 130 \"Incoming Document\" - field(18; Status; Option)", "description": "### Why do you need this change?\n\nWe have to extend the Enum for additional Document Status like Archived etc.\n\n### Describe the request\n\nHello, can you please change the field 18 \"Status\" in table 130 \"Incoming Document\" from option to an Enum?\n\n```\ntable 130 \"Incoming Document\";\n{\n.\n.\n.\n field(18; Status; Enum \"Incoming Document Status\")\n {\n Caption = 'Status';\n Editable = false;\n }\n```\n\n```\nenum 59002 \"Incoming Document Status\";\n{\n Extensible = true;\n\n value(0; New)\n {\n Caption = 'New';\n }\n value(1; Released)\n {\n Caption = 'Released';\n }\n value(2; Rejected)\n {\n Caption = 'Rejected';\n }\n value(3; Posted)\n {\n Caption = 'Posted';\n }\n value(4; Created)\n {\n Caption = 'Created';\n }\n value(5; Failed)\n {\n Caption = 'Failed';\n }\n value(6; \"Pending Approval\")\n {\n Caption = 'Pending Approval';\n }\n}\n```", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: Integration, enum-request.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ **Analysis complete - approved for implementation**\n\n---\n\n**Implementation:**\n\nConvert field 18 \"Status\" in table 130 \"Incoming Document\" from Option field to Enum \"Incoming Document Status\" (enum 59002). This enables partners to extend the status values (e.g., add \"Archived\") without modifying base code.\n\n```al\nenum 59002 \"Incoming Document Status\"\n{\n Extensible = true;\n\n value(0; New)\n {\n Caption = 'New';\n }\n value(1; Released)\n {\n Caption = 'Released';\n }\n value(2; Rejected)\n {\n Caption = 'Rejected';\n }\n value(3; Posted)\n {\n Caption = 'Posted';\n }\n value(4; Created)\n {\n Caption = 'Created';\n }\n value(5; Failed)\n {\n Caption = 'Failed';\n }\n value(6; \"Pending Approval\")\n {\n Caption = 'Pending Approval';\n }\n}\n```\n\n**Modified field in table 130:**\n\n```al\nfield(18; Status; Enum \"Incoming Document Status\")\n{\n Caption = 'Status';\n Editable = false;\n}\n```\n\n---\n\n**File:** `IncomingDocument.Table.al` (modified) and `IncomingDocumentStatus.Enum.al` (new)", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29557", "base_commit": "706e179dd811ac4c9ef3a474b53d64fa852ad420", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "Report 6 \"Trial Balance\" - add area(Content) to request page", "description": "### Why do you need this change?\n\nIn 4PS Construct we want to extend request page of report 6 \"Trial Balance\" with new field and it is not possible to do it.\n\n### Describe the request\n\nDear ALAppExtensions team,\n\nOn behalf of 4PS I would like to request adding Content area to request page of report 6 \"Trial Balance\".\nCurrently it not possible to add a new field to request page using report extension due to error \"The control 'Content' is not found in the target 'RequestOptionsPage'\".\n\n````\n requestpage\n {\n SaveValues = true;\n AboutTitle = 'About Trial Balance';\n AboutText = 'View a snapshot of your chart of accounts with a balance at date and net change in the specified period.';\n\n layout\n {\n area(Content) { } //new\n }\n\n actions\n {\n }\n }\n````", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets no labels (labels_to_set is empty).", "level": "critical"}, {"text": "The triage decision sets issue_state to 'closed'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\nThis request cannot be implemented.\n\n**Reason:** The target code (Report 6 \"Trial Balance\") is marked as obsolete and cannot be modified. The report has `ObsoleteState = Pending`, `ObsoleteTag = '28.0'`, and is wrapped in `#if not CLEAN28` preprocessor directive. The `ObsoleteReason` states: \"This report has been replaced by the report Trial Balance (Excel). This report will be removed in a future release.\"", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29576", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Event Requests] - Table 83 \"Item Journal Line\"", "description": "### Why do you need this change?\n\n1. Please add an event publisher before the if \"Qty. (Phys. Inventory)\" >= \"Qty. (Calculated\") block in the OnValidate trigger of _Qty. (Phys. Inventory)_.\nThe event should include an IsHandled flag so extensions can skip the standard Validate(\"Entry Type\") logic and assign the field value directly without triggering validation.\n2. Please add a new event publisher before the Validate(\"Setup Time\") and Validate(\"Run Time\") calls in the _Concurrent Capacity_ field – OnValidate trigger. The publisher should include an IsHandled parameter so extensions can override this logic and assign the values without executing the standard validation triggers.\n\n### Describe the request\n\n1. OnValidate trigger field 54 \"Qty. (Phys. Inventory)\"\n``````al\nfield(54; \"Qty. (Phys. Inventory)\"; Decimal)\n{\n ...\n trigger OnValidate()\n var\n IsHandled : Boolean; //New local variable\n begin\n ...\n\n PhysInvtEntered := true;\n Quantity := 0;\n //Code change - Start\n OnBeforeCheckQuantity(Rec, xRec, IsHandled);\n If not IsHandled then\n if \"Qty. (Phys. Inventory)\" >= \"Qty. (Calculated)\" then begin\n Validate(\"Entry Type\", \"Entry Type\"::\"Positive Adjmt.\");\n Validate(Quantity, \"Qty. (Phys. Inventory)\" - \"Qty. (Calculated)\");\n end else begin\n Validate(\"Entry Type\", \"Entry Type\"::\"Negative Adjmt.\");\n Validate(Quantity, \"Qty. (Calculated)\" - \"Qty. (Phys. Inventory)\");\n end;\n //Code change - End\n PhysInvtEntered := false;\n end;\n\n[IntegrationEvent(false, false)]\nlocal procedure OnBeforeCheckQuantity(var ItemJournalLine: Record \"Item Journal Line\"; xItemJournalLine: Record \"Item Journal Line\"; var IsHandled: Boolean)\nbegin\nend;\n``````\n2. OnValidate trigger field 5849 \"Concurrent Capacity\"\n``````al\nfield(5849; \"Concurrent Capacity\"; Decimal)\n{\n ...\n trigger OnValidate()\n var\n WorkCenter: Record \"Work Center\";\n ShopCalendarMgt: Codeunit \"Shop Calendar Management\";\n TotalTime: Integer;\n IsHandled : Boolean; //New local variable\n begin\n ...\n WorkCenter.Get(\"Work Center No.\");\n OnBeforeTimeValidation(Rec, xRec, IsHandled);\n if not IsHandled then begin\n Validate(\"Setup Time\", 0);\n Validate(\n \"Run Time\",\n Round(\n TotalTime / ShopCalendarMgt.TimeFactor(\"Cap. Unit of Measure Code\") *\n \"Concurrent Capacity\", WorkCenter.\"Calendar Rounding Precision\"));\n end;\n end;\n}\n\n[IntegrationEvent(false, false)]\nlocal procedure OnBeforeTimeValidation(var ItemJournalLine: Record \"Item Journal Line\"; xItemJournalLine: Record \"Item Journal Line\"; var IsHandled: Boolean)\nbegin\nend;\n``````", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: missing-info.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\nHi @exnihiloo,\n\nThanks for submitting this extensibility request. To help us process it efficiently, please update the issue to include the required information.\n\n📚 [Extensibility Guidelines](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-contribute-extensibility)\n\n**Missing Information:**\n\n- **Alternatives Evaluated**: Which existing events or patterns were tried and why are they insufficient?\n- **Justification for IsHandled**: Why is a standard event (without IsHandled) or alternative approach not feasible? What specific business logic must be skipped/replaced and why is bypass essential?\n- **Performance Considerations**: How frequently will these events be triggered? What is the expected performance impact?\n- **Data Sensitivity Review**: Are there any sensitive data exposure concerns? Please confirm data security implications.\n- **Multi-Extension Interaction**: What are the risks if multiple extensions subscribe to these events with IsHandled? How would conflicts be handled?\n\nPlease update the issue with the missing details. We'll automatically re-analyze once updated.", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29465", "base_commit": "706e179dd811ac4c9ef3a474b53d64fa852ad420", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[extensibility request] - Purch Post + Print - Event OnAfterConfirmPost", "description": "### Why do you need this change?\n\nCould PurchaseHeader be set as reference?\n\nVar PurchaseHeader:Record \"Purchase Header\"\n\n### Describe the request\n\nI need this because i want to modify purchase header.\n\n```\n[IntegrationEvent(false, false)]\n local procedure OnAfterConfirmPost(var PurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\n begin\n end;\n```\n\nWe have already looked at existing events in the purchase posting flow but none of them expose the PurchaseHeader at the right point after the confirm dialog.\nThe IsHandled flag is needed so we can fully replace the default post-confirmation logic when our extension requires different behavior.\nThis event is triggered only when a user explicitly confirms posting, so the performance impact is negligible.\nThe PurchaseHeader record does not expose sensitive data beyond what is already visible in the purchase posting UI.\nIf multiple extensions subscribe with IsHandled, the last one to set it to true wins, which is acceptable given the narrow use case.", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets no labels (labels_to_set is empty).", "level": "critical"}, {"text": "The triage decision sets issue_state to 'closed'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ **Good news! This is already implemented in the codebase.**\n\nThe requested change already exists. The `PurchaseHeader` parameter in the `OnAfterConfirmPost` event **already has the `var` modifier**:\n\n**Existing implementation:**\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnAfterConfirmPost(var PurchaseHeader: Record \"Purchase Header\"; var IsHandled: Boolean)\nbegin\nend;\n```\n\n**File:** `PurchPostPrint.Codeunit.al`\n\nYou can use this existing event in your extension to modify the Purchase Header after the post confirmation. Closing this issue as the request is already satisfied.", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29459-1", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Event Request] Codeunit 5880 \"Phys. Invt. Order-Finish\"", "description": "### Why do you need this change?\n\nHi,\ni need an event in the Codeunit 5880 \"Phys. Invt. Order-Finish\" in the procedure \"Code\"() to modify the PhysInvtOrderLine record before creating the reservation entries.\n\n### Describe the request\n\nAdd a new event in the procedure \"Code\"(), before the procedure \"CreateReservationEntries\".\n\n```\nprocedure \"Code\"()\nvar\n ItemTrackingSetup: Record \"Item Tracking Setup\";\n IsHandled: Boolean;\n begin\n ...\n if PhysInvtOrderLine.\"Use Item Tracking\" and\n not IsBinMandatoryNoWhseTracking(Item, PhysInvtOrderLine.\"Location Code\")\n then begin\n PhysInvtOrderLine.\"Pos. Qty. (Base)\" := 0;\n PhysInvtOrderLine.\"Neg. Qty. (Base)\" := 0;\n TempInvtOrderTrackingBuffer.Reset();\n TempInvtOrderTrackingBuffer.DeleteAll();\n CreateOrderTrackingBufferLines(PhysInvtOrderLine.\"Document No.\", PhysInvtOrderLine.\"Line No.\");\n\n // Start Event\n OnCodeOnBeforeCreateReservationEntriesAfterCreateOrderTrackingBufferLines(PhysInvtOrderLine);\n // End Event\n\n CreateReservationEntries(PhysInvtOrderLine.\"Document No.\", PhysInvtOrderLine.\"Line No.\", true, 0);\n end else\n ...\nend;\n```\n\nAdd the event publisher at the end of the codeunit:\n\n```\n[IntegrationEvent(false, false)]\nlocal procedure OnCodeOnBeforeCreateReservationEntriesAfterCreateOrderTrackingBufferLines(var PhysInvtOrderLine: Record \"Phys. Invt. Order Line\")\nbegin\nend;\n```\n\nInternal work item: [AB#614919](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/614919)", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: missing-info.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n⚠️ **Clarification needed**\n\n---\n\nWe noticed there is an existing event `OnBeforePhysInvtOrderLineModify` that fires before `PhysInvtOrderLine.Modify()` and provides access to the same `PhysInvtOrderLine` record.\n\n**Question:** Could you use the existing `OnBeforePhysInvtOrderLineModify` event for your scenario?\n\nIf not, please explain:\n1. What specific modifications do you need to make to `PhysInvtOrderLine`?\n2. Why must these modifications happen **before** `CreateReservationEntries` rather than before `Modify()`?\n3. How does the data flow to `CreateReservationEntries` affect your extension's behavior?\n\nThis helps us avoid adding duplicate events when an existing one could serve the same purpose.\n\n---\n\n**File:** `PhysInvtOrderFinish.Codeunit.al`", "level": "expected"}]} +{"metadata":{},"repo":"microsoftInternal/NAV","instance_id":"microsoftInternal__NAV-Ext_Request_Triage-29459-2","base_commit":"06e095c485e474431177e2170c0e22182bfdcc28","created_at":"2025-06-01","environment_setup_version":"27.0","project_paths":["App/Layers/W1/BaseApp"],"title":"[Event Request] Codeunit 5880 \"Phys. Invt. Order-Finish\"","description":"### Why do you need this change?\n\nHi,\ni need an event in the Codeunit 5880 \"Phys. Invt. Order-Finish\" in the procedure \"Code\"() to modify the PhysInvtOrderLine record before creating the reservation entries.\n\n### Describe the request\n\nAdd a new event in the procedure \"Code\"(), before the procedure \"CreateReservationEntries\".\n\n```\nprocedure \"Code\"()\nvar\n ItemTrackingSetup: Record \"Item Tracking Setup\";\n IsHandled: Boolean;\n begin\n ...\n if PhysInvtOrderLine.\"Use Item Tracking\" and\n not IsBinMandatoryNoWhseTracking(Item, PhysInvtOrderLine.\"Location Code\")\n then begin\n PhysInvtOrderLine.\"Pos. Qty. (Base)\" := 0;\n PhysInvtOrderLine.\"Neg. Qty. (Base)\" := 0;\n TempInvtOrderTrackingBuffer.Reset();\n TempInvtOrderTrackingBuffer.DeleteAll();\n CreateOrderTrackingBufferLines(PhysInvtOrderLine.\"Document No.\", PhysInvtOrderLine.\"Line No.\");\n\n // Start Event\n OnCodeOnBeforeCreateReservationEntriesAfterCreateOrderTrackingBufferLines(PhysInvtOrderLine);\n // End Event\n\n CreateReservationEntries(PhysInvtOrderLine.\"Document No.\", PhysInvtOrderLine.\"Line No.\", true, 0);\n end else\n ...\nend;\n```\n\nAdd the event publisher at the end of the codeunit:\n\n```\n[IntegrationEvent(false, false)]\nlocal procedure OnCodeOnBeforeCreateReservationEntriesAfterCreateOrderTrackingBufferLines(var PhysInvtOrderLine: Record \"Phys. Invt. Order Line\")\nbegin\nend;\n```\n\nInternal work item: [AB#614919](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/614919)","comments":"**Comment 1 (AleksandricMarko):**\n⚠️ **Clarification needed**\n\n---\n\nWe noticed there is an existing event `OnBeforePhysInvtOrderLineModify` that fires before `PhysInvtOrderLine.Modify()` and provides access to the same `PhysInvtOrderLine` record.\n\n**Question:** Could you use the existing `OnBeforePhysInvtOrderLineModify` event for your scenario?\n\nIf not, please explain:\n1. What specific modifications do you need to make to `PhysInvtOrderLine`?\n2. Why must these modifications happen **before** `CreateReservationEntries` rather than before `Modify()`?\n3. How does the data flow to `CreateReservationEntries` affect your extension's behavior?\n\nThis helps us avoid adding duplicate events when an existing one could serve the same purpose.\n\n---\n\n**File:** `PhysInvtOrderFinish.Codeunit.al`\n\n---\n\n**Comment 2 (Mariangela-DeMaio-EOS):**\nHi,\nthanks for the suggestion, I think I can use the event \"OnBeforePhysInvtOrderLineModify\".","current_labels":["missing-info"],"expected":[{"text":"The triage decision either sets no labels (labels_to_set is empty) or sets only the 'missing-info' label; both are acceptable.","level":"critical"},{"text":"The triage decision sets issue_state to 'closed'.","level":"critical"},{"text":"The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\nThanks for confirming!\n\nSince the existing `OnBeforePhysInvtOrderLineModify` event works for your scenario, no new event is needed. This event provides `var PhysInvtOrderLine` allowing you to modify the record before it's saved.\n\n**Existing event you can use:**\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnBeforePhysInvtOrderLineModify(var PhysInvtOrderLine: Record \"Phys. Invt. Order Line\")\nbegin\nend;\n```\n\n**File:** `PhysInvtOrderFinish.Codeunit.al`\n\nClosing this issue as your need is already satisfied by the existing event.","level":"expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29455", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Extend publisher] Codeunit 99000771 BOM Matrix Management - OnBeforeBuildMatrix", "description": "### Why do you need this change?\n\nThe problem occurs with the prod. bom version comparison when a production bom has another sub bom in the production bom lines. If this sub bom has multiple versions then it is possible hat the comparison shows not the components of the active certified version but of a newer one which is currently under development. In the case part of the \"BuildMatrix\" procedure is the following GetVersion call\n\nHowever, this does not filter for the current version or even consider the status, but only the start date, which can result in the active version not being returned.\n\n### Describe the request\n\nIt would be desirable if either the onbeforebuildmatrix event were adjusted and the VersionCode parameter were declared as a var parameter so that, if necessary, the active version could be set manually in this event.\n\nor if the GetVersion function were to return the current version directly, as on the \"Production BOM\" page using the exixting GetBOMVersion procedure of codeunit VersionManagement VersionManagement.GetBOMVersion(Rec.\"No.\", WorkDate(), true);\n\nInternal work item: [AB#614921](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/614921)", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, event-request.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ **Analysis complete - approved for implementation**\n\n---\n\n**Implementation:**\n\nExtend the existing `OnBeforeBuildMatrix` event publisher in Codeunit 99000771 \"BOM Matrix Management\" by changing the `VersionCode` parameter to a `var` parameter, allowing subscribers to override the version code.\n\n**Current code:**\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnBeforeBuildMatrix(ProductionBOMNo: Code[20]; VersionCode: Code[20]; Level: Integer; Quantity: Decimal; var IsHandled: Boolean)\nbegin\nend;\n```\n\n**Proposed change:**\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnBeforeBuildMatrix(ProductionBOMNo: Code[20]; var VersionCode: Code[20]; Level: Integer; Quantity: Decimal; var IsHandled: Boolean)\nbegin\nend;\n```\n\n---\n\n**File:** `BOMMatrixManagement.Codeunit.al`", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29454", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Request for External] table 7001 \"Price List Line\" - CheckAmountType", "description": "### Why do you need this change?\n\nCan you remove local from provedure CheckAmountType in table 7001 \"Price List Line\"?\n\n### Describe the request\n\nI need this to avoid making duplicate code, as I need to check the same scenario.\nInternal work item: [AB#614920](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/614920)", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, request-for-external.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ **Analysis complete - approved for implementation**\n\n---\n\n**Implementation:**\n\nChange the `CheckAmountType` procedure from `local` to public (remove the `local` keyword) in table 7001 \"Price List Line\".\n\n**Current code:**\n\n```al\nlocal procedure CheckAmountType(FldCaption: Text; AmountType: Enum \"Price Amount Type\")\nbegin\n if \"Amount Type\" = AmountType then\n Error(FieldNotAllowedForAmountTypeErr, FldCaption, FieldCaption(\"Amount Type\"), Format(\"Amount Type\"));\nend;\n```\n\n**Proposed change:**\n\n```al\nprocedure CheckAmountType(FldCaption: Text; AmountType: Enum \"Price Amount Type\")\nbegin\n if \"Amount Type\" = AmountType then\n Error(FieldNotAllowedForAmountTypeErr, FldCaption, FieldCaption(\"Amount Type\"), Format(\"Amount Type\"));\nend;\n```\n\n---\n\n**File:** `PriceListLine.Table.al`", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29453-1", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Event Request] page 7001 \"Price List Lines\" - OnAfterUpdateColumnVisibility", "description": "### Why do you need this change?\n\nCan you change IncludeSender to true?\n\n```\n //[IntegrationEvent(false, false)]\n [IntegrationEvent(true, false)]\n local procedure OnAfterUpdateColumnVisibility(PriceListHeader: Record \"Price List Header\"; var SourceTypeVisible: Boolean; var JobSourceTypeVisible: Boolean)\n begin\n end;\n```\n\n### Describe the request\n\n..\nInternal work item: [AB#615080](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/615080)", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: missing-info.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\nThank you for your request. To process this event modification, please provide the following missing information:\n\n**1. Complete Description:**\nThe \"Describe the request\" section is incomplete. Please provide:\n- What specific problem you're trying to solve\n- What information from the page you need to access\n- Why the current event parameters are insufficient\n- How you plan to use the page sender reference\n\n**2. Detailed Justification for IncludeSender=true:**\nChanging IncludeSender to true is a critical modification that requires careful justification. Please explain:\n- **Why subscribers MUST have access to the page instance** - what specific page properties, methods, or state do you need to access?\n- **Why the current event parameters (PriceListHeader, SourceTypeVisible, JobSourceTypeVisible) are insufficient** - what can't you accomplish with these?\n- **What alternatives you evaluated** - why can't you achieve your goal by adding parameters to the event instead?\n- **Specific use case** - describe the exact scenario where your extension needs page instance access\n\nPlease update the issue with this information so we can evaluate the request properly.", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29453-2", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Event Request] page 7001 \"Price List Lines\" - OnAfterUpdateColumnVisibility", "description": "### Why do you need this change?\n\nCan you change IncludeSender to true, so i can get information from the page and be able to set visibility for my own fields?\n\n```\n //[IntegrationEvent(false, false)]\n [IntegrationEvent(true, false)]\n local procedure OnAfterUpdateColumnVisibility(PriceListHeader: Record \"Price List Header\"; var SourceTypeVisible: Boolean; var JobSourceTypeVisible: Boolean)\n begin\n end;\n```\n\n### Describe the request\n\n..\nInternal work item: [AB#615080](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/615080)", "comments": "**Comment 1 (AleksandricMarko):**\nThank you for your request. To process this event modification, please provide the following missing information:\n\n**1. Complete Description:**\nThe \"Describe the request\" section is incomplete. Please provide:\n- What specific problem you're trying to solve\n- What information from the page you need to access\n- Why the current event parameters are insufficient\n- How you plan to use the page sender reference\n\n**2. Detailed Justification for IncludeSender=true:**\nChanging IncludeSender to true is a critical modification that requires careful justification. Please explain:\n- **Why subscribers MUST have access to the page instance** - what specific page properties, methods, or state do you need to access?\n- **Why the current event parameters (PriceListHeader, SourceTypeVisible, JobSourceTypeVisible) are insufficient** - what can't you accomplish with these?\n- **What alternatives you evaluated** - why can't you achieve your goal by adding parameters to the event instead?\n- **Specific use case** - describe the exact scenario where your extension needs page instance access\n\nPlease update the issue with this information so we can evaluate the request properly.\n\n---\n\n**Comment 2 (auzhhv):**\nHi Alexander,\n\n**1. Complete Description:**\nWe have added custom fields that should behave simular to other fields in the same page regarding visibility. These fields have custom criterias for visibility, but the points where our visibility variables should be updated is the same as the existing fields. The event is called OnAfterUpdateColumnVisibility, but it's not possible to update visibility for other fields because of access. As we cannot add custom variables to events, we need access to find and change other events on pages. We will add procedures in the a page extension to access our visibility variables. Ex. SetField1Visibility(NewVisibility) where Field1 is the name of our field.\n\n**2. Detailed Justification for IncludeSender=true:**\nWe must have access to the page to be able to set the our own visibility variables.\nIt makes no sense to put it other places as the name of the event applies that its the correct place to change visibility. And I have not found other places it makes sense to do the same. And this is probably the reason why this is the place base application has the same code.", "current_labels": ["missing-info"], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, event-request.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ **Analysis complete - approved for implementation**\n\n---\n\n**Implementation:**\n\nChange `IncludeSender` from `false` to `true` on the existing `OnAfterUpdateColumnVisibility` event in Page 7001 \"Price List Lines\" to allow subscribers to access the page instance.\n\n**Current code (around line 490):**\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnAfterUpdateColumnVisibility(PriceListHeader: Record \"Price List Header\"; var SourceTypeVisible: Boolean; var JobSourceTypeVisible: Boolean)\nbegin\nend;\n```\n\n**Proposed change:**\n\n```al\n[IntegrationEvent(true, false)]\nlocal procedure OnAfterUpdateColumnVisibility(PriceListHeader: Record \"Price List Header\"; var SourceTypeVisible: Boolean; var JobSourceTypeVisible: Boolean)\nbegin\nend;\n```\n\n---\n\n**File:** `PriceListLines.Page.al`", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29443", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[W1][Page][171][Standard Sales Code Subform] Make procedure UpdateTypeText() not local", "description": "### Why do you need this change?\n\nProcedure UpdateTypeText() is in local scope and it updates field variable TypeAsText. In our page extension we want to call this same UpdateTypeText logic rather than duplicating it, so the TypeAsText formatting stays consistent with the base page instead of us copying the procedure into our own code.\n\n### Describe the request\n\nRemove \"local\" scope from procedure UpdateTypeText() in page 171 \"Standard Sales Code Subform\"\nInternal work item: [AB#614045](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/614045)", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, request-for-external.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n\u2705 Analysis complete - approved for implementation\n\n**Implementation:**\n\nRemove `local` scope from procedure `UpdateTypeText()` in Page 171 \"Standard Sales Code Subform\":\n\n```al\nprocedure UpdateTypeText()\nvar\n RecRef: RecordRef;\nbegin\n RecRef.GetTable(Rec);\n TypeAsText := TempOptionLookupBuffer.FormatOption(RecRef.Field(Rec.FieldNo(Type)));\nend;\n```", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29441", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[W1][Page][289][Recurring Job Jnl.] Move variables to protected variables", "description": "### Why do you need this change?\n\nVariables used as field values need to be accessable so they could be updated after changes from new fields\n\n### Describe the request\n\nPlease move variables JobDescription and AccName to protected variables", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, request-for-external.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ Analysis complete - approved for implementation\n\n**Implementation:**\n\nMove `JobDescription` and `AccName` variables from `var` to `protected var` section in Page 289 \"Recurring Job Jnl.\":\n\n```al\nvar\n JobJnlManagement: Codeunit JobJnlManagement;\n ReportPrint: Codeunit \"Test Report-Print\";\n JobJnlReconcile: Page \"Job Journal Reconcile\";\n CurrentJnlBatchName: Code[10];\n\nprotected var\n JobDescription: Text[100];\n AccName: Text[100];\n ShortcutDimCode: array[8] of Code[20];\n```", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29755", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Event Request] codeunit 7322 \"Create Inventory Pick/Movement\" - procedure CreateInvtMvntWithoutSource", "description": "### Why do you need this change?\n\nCurrently there is no way to circumvent the \"There is nothing to handle\" error.\n\n### Alternatives Evaluated:\nThere are no alternatives for this issue. All events are either too early or too late.\n\n### Justification for IsHandled:\nWe need to be able to exit the procedure without the error.\n\n### Performance Considerations:\nCalled once per procedure execution, only if there is an error. Negligible impact.\n\n### Data Sensitivity Review:\nNo significantly sensitive data would be exposed through this event.\n\n### Multi-extension interaction:\nThere is no risk if multiple extensions are subscribed.\n\n### Describe the request\n\nCan you add an event when raising the error \"There is nothing to handle\"\n\n```AL\nprocedure CreateInvtMvntWithoutSource(var InternalMovementHeader: Record \"Internal Movement Header\")\n var\n InternalMovementLine: Record \"Internal Movement Line\";\n NewWarehouseActivityLine: Record \"Warehouse Activity Line\";\n RemQtyToPickBase: Decimal;\n //>>> New\n IsHandled: Boolean;\n //<<< New\n begin\n if not HideDialog then\n if not Confirm(CreateInvtMvmtQst, false) then\n exit;\n\n ...\n\n FindNextLineNo();\n\n repeat\n NewWarehouseActivityLine.Init();\n ...\n CreatePickOrMoveLine(NewWarehouseActivityLine, RemQtyToPickBase, RemQtyToPickBase, false);\n until InternalMovementLine.Next() = 0;\n\n if NextLineNo = 10000 then begin\n //>>> New\n OnCreateInvtMvntWithoutSourceOnBeforeNothingToHandleError(IsHandled);\n if IsHandled then\n exit;\n //<<< New\n Error(NothingToHandleMsg);\n end;\n\n MoveWhseComments(InternalMovementHeader.\"No.\", CurrWarehouseActivityHeader.\"No.\");\n\n if DeleteHandledInternalMovementLines(InternalMovementHeader.\"No.\") then begin\n InternalMovementHeader.Delete(true);\n if not HideDialog then\n Message(ActivityCreatedMsg, CurrWarehouseActivityHeader.Type, CurrWarehouseActivityHeader.\"No.\");\n end else\n if not HideDialog then\n Message(TrackingNotFullyAppliedMsg, CurrWarehouseActivityHeader.Type, CurrWarehouseActivityHeader.\"No.\");\n\n OnAfterCreateInvtMvntWithoutSource(CurrWarehouseActivityHeader, InternalMovementHeader);\n end;\n\n //>>> New\n [IntegrationEvent(false, false)]\n local procedure OnCreateInvtMvntWithoutSourceOnBeforeNothingToHandleError(var IsHandled: Boolean)\n begin\n end;\n //<<< New\n```\nInternal work item: [AB#623063](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/623063)", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: SCM, event-request.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ **Analysis complete - approved for implementation**\n\n---\n\n**Implementation:**\n\nAdd a new integration event in the `CreateInvtMvntWithoutSource` procedure to allow bypassing the \"nothing to handle\" error.\n\n**Location in `CreateInvtMvntWithoutSource` procedure (around line 1598):**\n\n```al\nif NextLineNo = 10000 then begin\n IsHandled := false;\n OnCreateInvtMvntWithoutSourceOnBeforeNothingToHandleError(IsHandled);\n if not IsHandled then\n Error(NothingToHandleMsg);\nend;\n```\n\n**Event Publisher (add at the end of the codeunit with other events):**\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnCreateInvtMvntWithoutSourceOnBeforeNothingToHandleError(var IsHandled: Boolean)\nbegin\nend;\n```\n\n---\n\n**File:** `CreateInventoryPickMovement.Codeunit.al`", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29767", "base_commit": "06e095c485e474431177e2170c0e22182bfdcc28", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Request for Extrenal] Report 8051 \"Contract Deferrals Release\"", "description": "### Why do you need this change?\n\nIn order to document the Contract Deferrals release for customer and vendor with additional data in G/L Entries, we require certain events.\n\n### Describe the request\n\nWe need an integration event at the procedure\n\nlocal procedure InsertTempGenJournalLine(DocumentNo: Code[20]; ContractNo: Code[20]; DeferralEntryNo: Integer; DimSetID: Integer; AccountNo: Code[20]; BalAccountNo: Code[20]; PostingAmount: Decimal; GenBusPostingGroup: Code[20]; GenProdPostingGroup: Code[20]; Partner: Enum \"Service Partner\")\nbegin\n LineNo += 1;\n TempGenJournalLine.\"Account No.\" := AccountNo;\n TempGenJournalLine.\"Bal. Account No.\" := BalAccountNo;\n TempGenJournalLine.\"Document No.\" := DocumentNo;\n TempGenJournalLine.\"Subscription Contract No.\" := ContractNo;\n TempGenJournalLine.\"Gen. Bus. Posting Group\" := GenBusPostingGroup;\n TempGenJournalLine.\"Gen. Prod. Posting Group\" := GenProdPostingGroup;\n TempGenJournalLine.\"Dimension Set ID\" := DimSetID;\n TempGenJournalLine.Amount := PostingAmount;\n TempGenJournalLine.\"Line No.\" := LineNo;\n TempGenJournalLine.\"Deferral Line No.\" := DeferralEntryNo;\n OnBeforeInsertTemGenJournalLine(TempGenJournalLine, Partner)\n TempGenJournalLine.Insert(false);\nend;\n\n[IntegrationEvent(false, false)]\nLocal procedure OnBeforeInsertTemGenJournalLine(var TempGenJournalLine: Record \"Gen. Journal Line\"; Partner: Enum \"Service Partner\")\n\nAnd need event at next procedure for transfer the data in TempGenJournalLine to GenJnlLine before post the data\n\nprocedure PostGenJnlLine(var TempGenJournalLine: Record \"Gen. Journal Line\" temporary; PostingDate: Date; SourceCodeSetupContractDeferralsRelease: Code[10])\n...\n OnBeforePostGenJnlLint(TempGenJournalLine, GenJnlLine);\n GenJnlPostLine.RunWithCheck(GenJnlLine);\n ...\n OnBeforePostGenJnlLine(TempGenJournalLine, GenJnlLine);\n GenJnlPostLine.RunWithCheck(GenJnlLine);\nend;\n\n[IntegrationEvent(false, false)]\nLocal procedure OnBeforePostGenJnlLint(TempGenJournalLine: Record \"Gen. Journal Line\"; var GenJournalLine: Record \"Gen. Journal Line\")\n\n### Sample implementation\n\nHere is roughly how we call it from our side:\n\n```javascript\nfunction releaseContractDeferrals(entries) {\n const lines = entries.map(e => ({ account: e.accountNo, amount: e.postingAmount }));\n return lines.filter(l => l.amount !== 0);\n}\n```\n", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets labels_to_set to exactly these managed labels and no others: agent-not-processable.", "level": "critical"}, {"text": "The triage decision sets issue_state to 'open'.", "level": "critical"}, {"text": "No advisory comment is posted to the requester (comment_to_post is empty).", "level": "expected"}]} +{"metadata": {}, "repo": "microsoftInternal/NAV", "instance_id": "microsoftInternal__NAV-Ext_Request_Triage-29789", "base_commit": "181dc724bef8901c9cab0c5a5a5a0e796958adc5", "created_at": "2025-06-01", "environment_setup_version": "27.0", "project_paths": ["App/Layers/W1/BaseApp"], "title": "[Event Request] Missing OnApplyStdCodesToPurchaseLinesOnAfterValidateType event", "description": "### Why do you need this change?\n\nTable 172 \"Standard Customer Sales Code\" exposes `OnApplyStdCodesToSalesLinesOnAfterValidateType` inside `ApplyStdCodesToSalesLines`. This event fires immediately after `Type` is validated on the newly inserted sales line, allowing extensions to react to or complement the type validation — for example, to set custom default values, apply additional field logic, or integrate with third-party modules that depend on the line type being resolved before further fields are set.\n\nTable 175 \"Standard Vendor Purchase Code\" has a symmetric procedure `ApplyStdCodesToPurchaseLines` but is missing the equivalent event. Extensions that extend both the sales and purchase recurring-code flow are unable to implement consistent behavior on the purchase side.\n\nThere is no existing event in `ApplyStdCodesToPurchaseLines` that fires at the same point in the flow (after type validation, before additional field assignments such as `No.` validation):\n\n| Existing event | Position | Sufficient? |\n|---|---|---|\n| `OnBeforeApplyStdCodesToPurchaseLines` | Before loop | Too early |\n| `OnApplyStdCodesToPurchaseLinesOnBeforeStdPurchLineFind` | Before per-line processing | Too early |\n| `OnApplyStdCodesToPurchaseLinesOnAfterPurchLineInsert` | After insert, all fields set | Too late |\n| `OnAfterApplyStdCodesToPurchaseLines` | After full loop | Too late |\n\n### Describe the request\n\n### EventRequest\n\n\n[W1][Table][175][Standard Vendor Purchase Code]\n[ApplyStdCodesToPurchaseLines]\n___\n\nAdd a missing integration event equivalent to\nOnApplyStdCodesToSalesLinesOnAfterValidateType in Table 172.\nThe event should fire immediately after PurchLine.Validate(Type)\ninside the ApplyStdCodesToPurchaseLines loop, passing the current\npurchase line and the standard purchase line as parameters.\n___\n```al\n\n[IntegrationEvent(false, false)]\nlocal procedure OnApplyStdCodesToPurchaseLinesOnAfterValidateType(\n var PurchaseLine: Record \"Purchase Line\";\n StandardPurchaseLine: Record \"Standard Purchase Line\")\nbegin\nend;\n```\n\n### Proposed placement (pseudocode context)\n\n```al\n// inside ApplyStdCodesToPurchaseLines loop:\nPurchLine.Validate(Type, StdPurchLine.Type);\nOnApplyStdCodesToPurchaseLinesOnAfterValidateType(PurchLine, StdPurchLine); // <-- new\nPurchLine.Validate(\"No.\", StdPurchLine.\"No.\");\n```\n\n### Symmetry reference — Table 172 existing event\n\n```al\n// Table 172 \"Standard Customer Sales Code\";\n[IntegrationEvent(false, false)]\nlocal procedure OnApplyStdCodesToSalesLinesOnAfterValidateType(\n var SalesLine: Record \"Sales Line\";\n StandardSalesLine: Record \"Standard Sales Line\")\nbegin\nend;\n```\n\nThe requested event is a direct purchase-side mirror of this existing sales-side event.\n\nInternal work item: [AB#624115](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/624115)", "comments": "", "current_labels": [], "expected": [{"text": "The triage decision sets no labels (labels_to_set is empty).", "level": "critical"}, {"text": "The triage decision sets issue_state to 'closed'.", "level": "critical"}, {"text": "The advisory comment posted to the requester reaches the same triage conclusion and provides guidance consistent with the following reference comment:\n\n✅ **Good news! This is already implemented in the codebase.**\n\nThe requested change already exists:\n\n**Existing implementation:**\n\n```al\n[IntegrationEvent(false, false)]\nlocal procedure OnApplyStdCodesToPurchaseLinesOnLoopStdPurchLine(var StandardVendorPurchaseCode: Record \"Standard Vendor Purchase Code\"; var StandardPurchaseLine: Record \"Standard Purchase Line\"; var PurchaseLine: Record \"Purchase Line\"; var PurchaseHeader: Record \"Purchase Header\"; var StandardPurchaseCode: Record \"Standard Purchase Code\"; var IsHandled: Boolean)\nbegin\nend;\n```\n\n**File:** `StandardVendorPurchaseCode.Table.al`\n\nYou can use this existing event in your extension. Closing this issue as the request is already satisfied.", "level": "expected"}]} diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30173/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30173/README.md new file mode 100644 index 000000000..6e0a29bc7 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30173/README.md @@ -0,0 +1,67 @@ +# Extensibility request: two integration events on page 7004 "Sales Line Discounts" + +## Why do you need this change? + +These event requests are related to an earlier case. In every request we will need the variable +`ItemTypeFilter` (currently an option, which we separately requested to change to an enum). + +### 1. Procedure SetRecFilters + +We need a new integration event in procedure `SetRecFilters`. We have extended page "Sales Line Discounts" +with additional fields backed by global variables. To properly control filtering and behavior, we need +access to the page instance (`var SalesLineDiscounts`) within the event, so we can implement +setter/getter procedures to read/write these global values during filter setup. + +Existing events (e.g. `OnOpenPage` / `OnAfterGetRecord`) do not provide sufficient control during filter +application, and table triggers are not suitable since the required logic depends on page-level global +variables and UI state. + +### 2. Procedure GetFilterDescription + +Introduce a new integration event in procedure `GetFilterDescription()` to extend the `case ItemTypeFilter` +logic — specifically to handle additional/custom types (equivalent to an `else` branch). The current +implementation supports only predefined `ItemTypeFilter` values (Item, Item Discount Group) and offers no +extensibility point for additional/custom types. The event should allow overriding `SourceTableName`, +applying custom filtering logic on `Item`, and extending behavior without modifying base code. + +## Describe the request + +### 1. Procedure SetRecFilters + +```al +procedure SetRecFilters() +begin + ... + OnSetRecFilters(ItemTypeFilter, Rec, SalesLineDiscounts); + CurrPage.Update(false); +end; + +[IntegrationEvent(true, false)] +local procedure OnSetRecFilters(ItemTypeFilter: Enum "Item Type Filter"; var SalesLineDiscount: Record "Sales Line Discount"; var SalesLineDiscounts: Page "Sales Line Discounts") +begin +end; +``` + +### 2. Procedure GetFilterDescription + +```al +case ItemTypeFilter of + ItemTypeFilter::Item: + ... + ItemTypeFilter::"Item Discount Group": + ... + else + OnItemTypeFilterElse(ItemTypeFilter, SourceTableName, Item); +end; + +[IntegrationEvent(true, false)] +local procedure OnItemTypeFilterElse(ItemTypeFilter: Enum "Item Type Filter"; var SourceTableName: Text; var Item: Record Item) +begin +end; +``` + +## Scope + +- File: `SalesLineDiscounts.Page.al` +- This page exists only in the W1 base layer, so the change lives in W1 alone (no country/region layer + counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30216/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30216/README.md new file mode 100644 index 000000000..c6482df7b --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30216/README.md @@ -0,0 +1,47 @@ +# Extensibility request: IsHandled event before PrepareJobLine in codeunit 90 "Purch.-Post".PostItemJnlLineJobConsumption + +## Why do you need this change? + +We need an event before `InvoicePostingInterface.PrepareJobLine(...)` in order to be able to assign a +different value to the field "Qty. to Invoice" according to a particular custom condition, as well as run a +different custom procedure instead of `PrepareJobLine`. At the moment there are no alternative events to +skip the procedure `PrepareJobLine` or run a custom procedure instead when a condition based on custom +fields is satisfied. + +### Why an IsHandled event is required + +Under particular custom conditions based on extension fields, we must completely replace the standard +`PrepareJobLine` execution with a custom implementation and assign a different value to +`PurchLine."Qty. to Invoice"`. If the standard code continues to execute after our custom logic, +`PrepareJobLine` would run twice or would overwrite the values calculated by our customization. The existing +`OnPostItemJnlLineJobConsumption` / `OnPostItemJnlLineJobConsumptionOnBeforeJobPost` events are raised too +early, and the downstream `OnBeforePostJobOnPurchaseLine` event is raised after `PrepareJobLine` has already +run, so the hook is needed immediately before that call. + +## Describe the request + +In procedure `PostItemJnlLineJobConsumption` of codeunit 90 "Purch.-Post", raise a new `IsHandled` event +immediately before `InvoicePostingInterface.PrepareJobLine(...)`: + +```al +if QtyToBeInvoiced <> 0 then begin + PurchLine."Qty. to Invoice" := QtyToBeInvoiced; + IsHandled := false; + OnBeforePrepareJobLinePrepareJobLine(IsHandled, PurchLine, QtyToBeInvoiced, PurchHeader, PurchLineACY); + if not IsHandled then + InvoicePostingInterface.PrepareJobLine(PurchHeader, PurchLine, PurchLineACY); +end; +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforePrepareJobLinePrepareJobLine(var IsHandled: Boolean; var PurchLine: Record "Purchase Line"; QtyToBeInvoiced: Decimal; var PurchHeader: Record "Purchase Header"; var PurchLineACY: Record "Purchase Line") +begin +end; +``` + +## Scope + +- File: `PurchPost.Codeunit.al` +- This codeunit is present in 10 layers (W1, APAC, BE, CH, ES, FI, GB, IT, NA, RU), each keeping its own + copy. The identical change must be applied to all 10 layer files. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30222/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30222/README.md new file mode 100644 index 000000000..1ca505fcd --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30222/README.md @@ -0,0 +1,51 @@ +# Extensibility request: IsHandled event before RecreatePurchLines in table 38 "Purchase Header" (Pay-to Vendor No. validation) + +## Why do you need this change? + +We need an event inside the `OnValidate` trigger of field "Pay-to Vendor No.", to be able to run the +procedure `RecreatePurchLines` only if a custom `if` statement is satisfied or, alternatively, run a custom +procedure. Managing an `IsHandled` variable is the only way to skip `RecreatePurchLines` (when a particular +custom condition is satisfied) and run a custom procedure instead. At the moment there are no other ways to +do that. + +### Why an IsHandled event is required + +Some custom purchase lines contain additional information that must be preserved when the Pay-to Vendor +changes; executing the standard `RecreatePurchLines(PayToVendorTxt)` would remove or recreate lines in a way +that is not compatible with the custom business process. The extension needs to evaluate a custom condition, +skip the standard call when satisfied, and execute an alternative recreation procedure. The existing +`OnValidatePaytoVendorNoBeforeRecreateLines` event is raised immediately before the call but provides no +`IsHandled` parameter, and the generic `OnBeforeRecreatePurchLinesHandler` inside `RecreatePurchLines()` +is too late and too broad, so the decision must be made at the caller level in this specific validation. + +The subscriber also needs `xRec` because the decision depends on comparing the document state before and +after the validation of "Pay-to Vendor No.". + +## Describe the request + +In the `OnValidate` trigger of field(4; "Pay-to Vendor No."; Code[20]) of table 38 "Purchase Header", raise +a new `IsHandled` event immediately before `RecreatePurchLines(PayToVendorTxt)`: + +```al +if (xRec."Buy-from Vendor No." = "Buy-from Vendor No.") and + (xRec."Pay-to Vendor No." <> "Pay-to Vendor No.") +then begin + IsHandled := false; + OnBeforeRecreatePurchLines(IsHandled, Rec, xRec); + if not IsHandled then + RecreatePurchLines(PayToVendorTxt); +end; +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeRecreatePurchLines(var IsHandled: Boolean; var Rec: Record "Purchase Header"; xRec: Record "Purchase Header") +begin +end; +``` + +## Scope + +- File: `PurchaseHeader.Table.al` +- This table is present in 14 layers (W1, APAC, BE, CH, DACH, ES, FI, GB, IT, NA, NL, NO, RU, SE), each + keeping its own copy. The identical change must be applied to all 14 layer files. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30223/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30223/README.md new file mode 100644 index 000000000..0ea7e9570 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30223/README.md @@ -0,0 +1,56 @@ +# Extensibility request: three extension points in table 39 "Purchase Line" + +## Why this change is needed + +Table 39 "Purchase Line" runs several pieces of standard logic that an extension currently cannot +skip or replace. In three places there is no event that lets a subscriber apply a custom condition +and, when it is met, run its own logic instead of the standard call: + +1. In the `"Location Code"` field `OnValidate` trigger, `PlanPriceCalcByField` is always called for + items when the location changes; there is no way to suppress it conditionally. +2. In `CopyFromItem`, `GetItemTranslation` is always called when the purchase header has a language + code; there is no way to skip it and run custom translation handling instead. +3. In `CheckWMS`, `CheckLocationOnWMS` is always called when `CurrFieldNo <> 0`; there is no way to + suppress that warehouse check conditionally. + +We need an extension point before each of these calls so subscribers can plug in custom handling in a +supported, upgrade-safe way. + +## Requested change + +Add three new integration events to table 39 "Purchase Line", each raised **before** the +corresponding standard call and each using the `IsHandled` pattern so a subscriber that sets +`IsHandled := true` skips the standard call: + +1. Before `PlanPriceCalcByField` in the `"Location Code"` `OnValidate` trigger. Pass the + `Purchase Line` record, `IsHandled` (var), the current field number, and the `xPurchaseLine` + record. +2. Before `GetItemTranslation` in `CopyFromItem`. Pass the `Purchase Line` record, the `Item` record, + and `IsHandled` (var). +3. Before `CheckLocationOnWMS` in `CheckWMS`. Pass the `Purchase Line` record, the current field + number, and `IsHandled` (var). `CheckWMS` needs a local `IsHandled: Boolean` variable added. + +Illustrative shape for one of the three (final event names and signatures must follow BC event +conventions): + +```al +local procedure CheckWMS() +var + IsHandled: Boolean; +begin + IsHandled := false; + // new integration event raised here, passing Purchase Line, CurrFieldNo, and IsHandled (var) + if not IsHandled then + if CurrFieldNo <> 0 then + CheckLocationOnWMS(); + // ... +end; +``` + +## Scope + +- Table 39 "Purchase Line" is kept as a separate copy in many country/region layers. All three + events must be added consistently to **every** layer that keeps a copy of this table: + - `App/Layers/W1/BaseApp/Purchases/Document/PurchaseLine.Table.al` + - and the same file in the APAC, BE, CH, DACH, ES, FI, GB, IT, NA, NL, NO, RU, and SE layers. +- That is 14 layer copies in total, so exactly 14 files change. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30239/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30239/README.md new file mode 100644 index 000000000..9548cbddc --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30239/README.md @@ -0,0 +1,40 @@ +# Extensibility request: OnBeforeOnRunOnCheckWarehouse event in codeunit 99000760 "Mfg. Item Jnl. Check Line" + +## Why do you need this change? + +We currently have two subscribers associated with the `OnRunOnCheckWarehouse` publisher event: one provided +by the standard Microsoft application and another implemented in our extension. When posting Consumption +transactions through our application, the warehouse validation logic executed in the local procedure +`OnRunOnCheckWarehouse` of codeunit 99000760 "Mfg. Item Jnl. Check Line" is preventing further processing, +as it is called by the MS standard event. + +To handle this scenario, we require a handler event to conditionally bypass this validation logic for +transactions from our application. Providing an extensibility point (publisher event) within +`OnRunOnCheckWarehouse` would allow extensions to skip the standard validation when necessary, while +preserving the existing behavior for all other scenarios. + +## Describe the request + +Introduce a new publisher `OnBeforeOnRunOnCheckWarehouse` event at the start of procedure +`OnRunOnCheckWarehouse` that allows extensions to skip the standard validation logic when required, using +the `IsHandled` pattern with an early exit: + +```al +IsHandled := false; +OnBeforeOnRunOnCheckWarehouse(ItemJournalLine, CalledFromAdjustment, CalledFromInvtPutawayPick, IsHandled); +if IsHandled then + exit; +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeOnRunOnCheckWarehouse(var ItemJournalLine: Record "Item Journal Line"; CalledFromAdjustment: Boolean; CalledFromInvtPutawayPick: Boolean; var IsHandled: Boolean) +begin +end; +``` + +## Scope + +- File: `MfgItemJnlCheckLine.Codeunit.al` +- This codeunit exists in both the W1 base layer and the IT layer (which keeps its own copy). The identical + change must be applied to both the W1 and IT layer files. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30290/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30290/README.md new file mode 100644 index 000000000..9daef35ef --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30290/README.md @@ -0,0 +1,39 @@ +# Extensibility request: OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader event in table 36 "Sales Header" + +## Why do you need this change? + +We need to bypass the standard Microsoft logic in this procedure because we have a separate feature that +already handles the assignment of the VAT Registration No. and the relevant posting groups. Without an +`IsHandled` pattern, the Microsoft logic is always executed and may overwrite or conflict with the values +set by our own feature. + +## Describe the request + +Add an `OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader` integration event before calling +`AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer)` in procedure +`SetBillToCustomerAddressFieldsFromCustomer`. The event should provide an `IsHandled` parameter so +subscribers can skip the mentioned call when they handle the logic themselves. + +```al +IsHandled := false; +OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, BillToCustomer, IsHandled); +if not IsHandled then + AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader(Rec, xRec, BillToCustomer); +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeUpdateSetupOnBillToCustomerChangeInSalesHeader(var SalesHeader: Record "Sales Header"; BillToCustomer: Record Customer; var IsHandled: Boolean) +begin +end; +``` + +There are already three events in this procedure, but none lets me skip only the +`AltCustVATRegFacade.UpdateSetupOnBillToCustomerChangeInSalesHeader` call while keeping the rest of the +standard logic. + +## Scope + +- File: `SalesHeader.Table.al` +- This table is present in 15 layers (W1, APAC, BE, CH, CZ, ES, FI, FR, GB, IT, NA, NL, NO, RU, SE), each + keeping its own copy. The identical change must be applied to all 15 layer files. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30292/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30292/README.md new file mode 100644 index 000000000..bd2ba0fe5 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30292/README.md @@ -0,0 +1,36 @@ +# Extensibility request: add a temporary Warehouse Activity Line parameter to OnBeforeCreateWhseActivHeader in codeunit 7312 "Create Pick" + +## Why do you need this change? + +Add a parameter `TempWhseActivLine: Record "Warehouse Activity Line" temporary` as a `var` in the event +`OnBeforeCreateWhseActivHeader` of codeunit 7312 "Create Pick". + +To support adding new items from an order to an existing warehouse pick for Sales Orders, Transfer Orders, +and Purchase Return Orders, we need access to a temporary instance of the Warehouse Activity Line record +during the pick creation process. Currently the `OnBeforeCreateWhseActivHeader()` event does not provide a +mechanism to pass or manipulate temporary warehouse activity lines. This will allow custom logic to prepare +and manage additional lines before the warehouse activity header is created. + +## Describe the request + +Enhance the existing `OnBeforeCreateWhseActivHeader` publisher signature by adding a +`var TempWhseActivLine: Record "Warehouse Activity Line" temporary` parameter, and pass the temporary +record instance when the event is raised in `CreateWhseActivHeader`: + +```al +IsHandled := false; +OnBeforeCreateWhseActivHeader(CurrWarehouseActivityHeader, TempWarehouseActivityLine, LocationCode, FirstWhseDocNo, LastWhseDocNo, NoOfSourceDoc, NoOfLines, WhseDocCreated, IsHandled); +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeCreateWhseActivHeader(var CurrWarehouseActivityHeader: Record "Warehouse Activity Header"; var TempWhseActivLine: Record "Warehouse Activity Line" temporary; LocationCode: Code[10]; var FirstWhseDocNo: Code[20]; var LastWhseDocNo: Code[20]; var NoOfSourceDoc: Integer; var NoOfLines: Integer; var WhseDocCreated: Boolean; var IsHandled: Boolean) +begin +end; +``` + +## Scope + +- File: `CreatePick.Codeunit.al` +- This codeunit exists only in the W1 base layer, so the change lives in W1 alone (no country/region layer + counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30329/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30329/README.md new file mode 100644 index 000000000..30bff27b2 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30329/README.md @@ -0,0 +1,44 @@ +# Extensibility request: override the confirmation question in codeunit 1322 "Correct PstdSalesInv (Yes/No)".CancelPostedInvoiceAndOpenSalesOrder + +## Why do you need this change? + +Following an earlier request (shipped in 28.2), an extension can now change what happens to the originating +sales order's quantities when a posted sales invoice is corrected/cancelled from the page actions. When that +behavior is changed, the standard confirmation text shown to the user before the action becomes inaccurate, +because it states that the original quantities will be reverted. We need a way to override the confirmation +question text without duplicating this procedure's logic. + +The confirmation is raised in `CancelPostedInvoiceAndOpenSalesOrder` of codeunit 1322 +"Correct PstdSalesInv (Yes/No)" via `ConfirmManagement.GetResponse(CorrectPostedInvoiceFromSingleOrderQst, ...)`. + +## Describe the request + +Introduce the confirmation text into a local variable, raise a new integration event that lets subscribers +override it, and then pass that variable to `ConfirmManagement.GetResponse`: + +```al +local procedure CancelPostedInvoiceAndOpenSalesOrder(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"): Boolean +var + ConfirmManagement: Codeunit "Confirm Management"; + CorrectPostedSalesInvoice: Codeunit "Correct Posted Sales Invoice"; + IsHandled: Boolean; + ConfirmQuestion: Text; +begin + ConfirmQuestion := CorrectPostedInvoiceFromSingleOrderQst; + OnCancelPostedInvoiceAndOpenSalesOrderOnBeforeConfirm(SalesInvoiceHeader, SalesHeader, ConfirmQuestion); + if ConfirmManagement.GetResponse(ConfirmQuestion, false) then begin + ... + end; +end; + +[IntegrationEvent(false, false)] +local procedure OnCancelPostedInvoiceAndOpenSalesOrderOnBeforeConfirm(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"; var ConfirmQuestion: Text) +begin +end; +``` + +## Scope + +- File: `CorrectPstdSalesInvYesNo.Codeunit.al` +- This codeunit exists only in the W1 base layer, so the change lives in W1 alone (no country/region layer + counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30330/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30330/README.md new file mode 100644 index 000000000..cdb125e68 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30330/README.md @@ -0,0 +1,34 @@ +# Extensibility request: override the confirmation question in codeunit 1323 "Cancel PstdSalesInv (Yes/No)".CancelInvoice + +## Why do you need this change? + +With an earlier request (shipped in 28.2), an extension can change what happens to the originating sales +order's quantities when a posted invoice is corrected/cancelled from the page actions. The standard +confirmation text before the cancellation becomes inaccurate when that behavior is overridden, because it +states that the original quantities will be reverted. Currently there is no event before this specific +`Confirm` call to adjust it (the existing `OnCancelInvoiceOnBeforeTestCorrectInvoiceIsAllowed` event only +gates the validation check, not this message). + +## Describe the request + +In procedure `CancelInvoice` of codeunit 1323 "Cancel PstdSalesInv (Yes/No)", move the confirmation text +into a local variable, raise a new integration event that lets subscribers override it, and then pass that +variable to `Confirm`: + +```al +ConfirmQuestion := CancelPostedInvoiceQst; +OnCancelInvoiceOnBeforeConfirm(SalesInvoiceHeader, ConfirmQuestion); +if Confirm(ConfirmQuestion) then + ... + +[IntegrationEvent(false, false)] +local procedure OnCancelInvoiceOnBeforeConfirm(var SalesInvoiceHeader: Record "Sales Invoice Header"; var ConfirmQuestion: Text) +begin +end; +``` + +## Scope + +- File: `CancelPstdSalesInvYesNo.Codeunit.al` +- This codeunit exists only in the W1 base layer, so the change lives in W1 alone (no country/region layer + counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30331/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30331/README.md new file mode 100644 index 000000000..eac0d05f5 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30331/README.md @@ -0,0 +1,35 @@ +# Extensibility request: override the confirmation question in codeunit 1303 "Correct Posted Sales Invoice".CreateCreditMemoCopyDocument + +## Why do you need this change? + +With an earlier request (shipped in 28.2), an extension can change what happens to the originating sales +order's quantities when a "Create Corrective Cr. Memo" action is called for a posted sales invoice. When an +extension changes that behavior, the standard confirmation text becomes inaccurate, because it states that +the original quantities will be reverted. The existing `OnBeforeCreateCreditMemoCopyDocument` event has no +`IsHandled`/text parameter, so it cannot be used to adjust this specific `Confirm`. + +## Describe the request + +In procedure `CreateCreditMemoCopyDocument` of codeunit 1303 "Correct Posted Sales Invoice", move the +confirmation text into a local variable, raise a new integration event that lets subscribers override it, +and then pass that variable to `Confirm`: + +```al +if not SalesHdr.IsEmpty then begin + ConfirmQuestion := CreateCreditMemoQst; + OnCreateCreditMemoCopyDocumentOnBeforeConfirm(SalesInvoiceHeader, SalesHdr, ConfirmQuestion); + if not Confirm(ConfirmQuestion) then + exit(false); +end; + +[IntegrationEvent(false, false)] +local procedure OnCreateCreditMemoCopyDocumentOnBeforeConfirm(var SalesInvoiceHeader: Record "Sales Invoice Header"; var SalesHeader: Record "Sales Header"; var ConfirmQuestion: Text) +begin +end; +``` + +## Scope + +- File: `CorrectPostedSalesInvoice.Codeunit.al` +- This codeunit is present in the W1, APAC and ES layers (each keeps its own copy). The identical change + must be applied to all three layer files (W1, APAC, ES). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30335/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30335/README.md new file mode 100644 index 000000000..7d196e897 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30335/README.md @@ -0,0 +1,50 @@ +# Extensibility request: OnBeforeRecreateSalesLine event in table 36 "Sales Header".RecreateSalesLinesHandleSupplementTypes + +## Why do you need this change? + +We need an event inside the procedure `RecreateSalesLinesHandleSupplementTypes`, in order to be able to add +a custom condition before running `CreateSalesLine()`. + +Managing an `IsHandled` parameter is the only way to skip `CreateSalesLine()` (when a particular custom +condition is satisfied) and execute an alternative custom implementation instead. At the moment there are no +existing extensibility points that allow partners to replace the sales line recreation logic at this +specific location. + +### Why an IsHandled event is required + +Under specific business conditions based on extension fields, our customization must completely replace the +standard `CreateSalesLine()` logic. If the standard code continues to execute after our custom logic, the +standard `CreateSalesLine()` would recreate the sales line again, overwriting the values and relationships +established by the custom implementation. The existing `OnBeforeCreateSalesLine()` event is not sufficient +because it is raised inside `CreateSalesLine()`, after the procedure has already been invoked; we need the +decision point at the caller level, before entering `CreateSalesLine()`. + +## Describe the request + +In procedure `RecreateSalesLinesHandleSupplementTypes` of table 36 "Sales Header", raise a new `IsHandled` +event immediately before `CreateSalesLine(TempSalesLine)`: + +```al +if ShouldCreateSalesLine then begin + IsHandled := false; + OnBeforeRecreateSalesLine(IsHandled, SalesLine, TempSalesLine, Rec); + if not IsHandled then + CreateSalesLine(TempSalesLine); + + ExtendedTextAdded := false; + OnAfterRecreateSalesLine(SalesLine, TempSalesLine, Rec); + ... +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeRecreateSalesLine(var IsHandled: Boolean; var SalesLine: Record "Sales Line"; var TempSalesLine: Record "Sales Line" temporary; var SalesHeader: Record "Sales Header") +begin +end; +``` + +## Scope + +- File: `SalesHeader.Table.al` +- This table is present in 15 layers (W1, APAC, BE, CH, CZ, ES, FI, FR, GB, IT, NA, NL, NO, RU, SE), each + keeping its own copy. The identical change must be applied to all 15 layer files. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30336/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30336/README.md new file mode 100644 index 000000000..90e9032bc --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30336/README.md @@ -0,0 +1,49 @@ +# Extensibility request: extension point in table 5409 "Prod. Order Routing Line" before ModifyCapNeedEntries + +## Why this change is needed + +In table 5409 "Prod. Order Routing Line", the `"No."` field `OnValidate` trigger transfers work +center or machine center fields (via `WorkCenterTransferFields` / `MachineCtrTransferFields`) and then +immediately updates capacity need entries by calling `ModifyCapNeedEntries`. + +An extension that needs to run custom logic **after** all the standard transfer logic has completed +but **before** capacity need entries are recalculated has no way to hook in: there is no event raised +at this exact point in the execution flow. In particular, a subscriber that needs to evaluate both the +current and previous values of the routing line (`Rec` and `xRec`) after the transfer, but before +`ModifyCapNeedEntries`, cannot do so today. + +## Requested change + +Add a new integration event to table 5409 "Prod. Order Routing Line", raised in the `"No."` field +`OnValidate` trigger **immediately after** the `case Type of` block (that performs the work/machine +center field transfer) and **immediately before** the `ModifyCapNeedEntries` call. The event should +pass the current record and the previous record (`xRec`) so subscribers can compare them. + +Illustrative shape (final event name and signature must follow BC event conventions): + +```al +case Type of + Type::"Work Center": + begin + WorkCenter.Get("No."); + WorkCenter.TestField(Blocked, false); + WorkCenterTransferFields(); + end; + Type::"Machine Center": + begin + MachineCenter.Get("No."); + MachineCenter.TestField(Blocked, false); + MachineCtrTransferFields(); + end; +end; +// new integration event raised here, passing Rec and xRec +ModifyCapNeedEntries(); +``` + +## Scope + +- Table 5409 "Prod. Order Routing Line" is kept as a separate copy in the W1 base layer and in the IT + layer. The change must be applied consistently to both copies: + - `App/Layers/W1/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al` + - `App/Layers/IT/BaseApp/Manufacturing/Document/ProdOrderRoutingLine.Table.al` +- No other layers keep a copy of this table, so exactly two files change. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30337/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30337/README.md new file mode 100644 index 000000000..8c7f9a647 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30337/README.md @@ -0,0 +1,77 @@ +# Extensibility request: replaceable Source Type = Item processing in codeunit 99000787 "Create Prod. Order Lines" + +## Why do you need this change? + +We need a new event in the `CreateProdOrderLine` procedure to allow partners to completely replace the +standard processing of the `Source Type = Item` branch under specific business conditions. + +Our customization needs to: + +- execute an alternative production order line creation process; +- determine the value of the `ErrorOccured` parameter according to custom logic; +- prevent the standard item-line creation from executing afterwards. + +The existing event `OnCreateProdOrderLineOnBeforeInitProdOrderLine(var InsertNew: Boolean)` is useful for +influencing the standard flow, but it is intentionally not designed to replace it. It does not provide an +`IsHandled` parameter to bypass the standard processing, nor access to the `ErrorOccured` parameter. + +### Why IsHandled is required + +Under specific business conditions our extension needs to completely replace the standard +`Source Type = Item` implementation. If the standard code continues executing after our custom logic, the +production order line would be initialized and inserted twice, and the value assigned to `ErrorOccured` by +the custom implementation could be overwritten. An `IsHandled` pattern is therefore required so subscribers +can intentionally replace the standard implementation. + +### Why ErrorOccured is required + +The subscriber must be able to set `ErrorOccured` because it represents the outcome of the production order +line creation. When replacing the standard implementation, the extension performs its own validations and +line creation and must be able to report whether the custom process succeeded or failed. + +## Describe the request + +In procedure `CreateProdOrderLine` of codeunit 99000787 "Create Prod. Order Lines" we request a new +integration event, for example: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeCreateProdOrderLineItem( + var IsHandled: Boolean; + var ErrorOccured: Boolean; + ProdOrder: Record "Production Order") +begin +end; +``` + +The event should be raised at the start of the `Source Type = Item` branch, guarding the standard +item-line creation with the `IsHandled` flag so that a subscriber can replace it and report the outcome via +`ErrorOccured`. + +### Refactoring acceptance + +We fully accept refactoring the current `Source Type = Item` implementation into a dedicated local +procedure if this better aligns with the extensibility guidelines. The proposed event can be raised +immediately before an extracted helper procedure containing the existing standard item-line creation logic, +for example: + +```al +ProdOrder."Source Type"::Item: + begin + IsHandled := false; + OnBeforeCreateProdOrderLineItem(IsHandled, ErrorOccured, ProdOrder); + if not IsHandled then + CreateProdOrderLineFromItem(ProdOrder, VariantCode, ErrorOccured); + end; +``` + +where `CreateProdOrderLineFromItem(...)` contains the current standard implementation +(`InitProdOrderLine`, field initialization, quantity validation, dimension copying, +`OnBeforeProdOrderLineInsert`, record insertion, and propagation of `ErrorOccured` through +`ProdOrderLine.HasErrorOccured()`). This refactoring does not change the intent of the request. + +## Scope + +- File: `CreateProdOrderLines.Codeunit.al` +- This codeunit exists only in the W1 base layer, so the change lives in W1 alone (no country/region layer + counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30339/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30339/README.md new file mode 100644 index 000000000..bb6fbf685 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30339/README.md @@ -0,0 +1,47 @@ +# Extensibility request: OnAfterCalcAvailableQtyBase event in table 7326 "Whse. Worksheet Line" + +## Why do you need this change? + +I need to be able to adjust the quantity that is calculated as being available (we have some stock +ring-fencing customisations). I.e. I want to be able to perform the usual availability calculation and +then reduce it by the quantity that has been ring-fenced. + +There is an `OnBeforeCalcAvailableQtyBase()` event with an `IsHandled` property, and I considered using +it by replicating the Microsoft availability calculation code and then adding my own — not ideal. I am +actually unable to use it because I would need to replicate Microsoft code that calls +`CreatePick.CalcTotalAvailQtyToPickForDirectedPutAwayPick(...)`, and that procedure has been marked +**Internal**, so it cannot be called from an extension. + +A much better option would be to have an `OnAfterCalcAvailableQtyBase()` event at the end of the +procedure where I could adjust the calculated available quantity. + +## Describe the request + +Add a new `OnAfterCalcAvailableQtyBase()` integration event called at the end of the +`CalcAvailableQtyBase()` procedure in table 7326 "Whse. Worksheet Line", so extensions can adjust the +final calculated `AvailableQty`. + +Location at the end of `CalcAvailableQtyBase()`: + +```al + AvailableQty := AvailQtyBase - QtyAssgndOnWkshBase + AssignedQtyOnReservedLines(); + end; + + OnAfterCalcAvailableQtyBase(Rec, AvailableQty); + end; +``` + +Event publisher (add with the other events): + +```al +[IntegrationEvent(false, false)] +local procedure OnAfterCalcAvailableQtyBase(var WhseWorksheetLine: Record "Whse. Worksheet Line"; var AvailableQty: Decimal) +begin +end; +``` + +## Scope + +- File: `WhseWorksheetLine.Table.al` +- This table exists only in the W1 base layer, so the change lives in W1 alone (no country/region layer + counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30346/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30346/README.md new file mode 100644 index 000000000..cb20896a7 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30346/README.md @@ -0,0 +1,37 @@ +# Extensibility request: make "Item Jnl.-Post Line".InsertPostValueEntryToGL externally callable + +## Why this change is needed + +Codeunit 22 "Item Jnl.-Post Line" exposes `PostInventoryToGL` as a public procedure, and extensions +already build on it to post additional value entries to the general ledger. The related helper +`InsertPostValueEntryToGL(ValueEntry: Record "Value Entry")` posts a single value entry to the G/L, +but it is declared `local`, so it cannot be called from an extension. + +An extension that posts additional variance (or other custom value entries) needs to reuse this exact +standard logic instead of duplicating it. There is currently no supported way to do that. + +## Requested change + +Widen the accessibility of the `InsertPostValueEntryToGL` method in codeunit 22 "Item Jnl.-Post Line" +so it can be called from outside the codeunit: change it from a `local procedure` to a (public) +`procedure`. The body and signature stay exactly the same. + +```al +// before +local procedure InsertPostValueEntryToGL(ValueEntry: Record "Value Entry") + +// after +procedure InsertPostValueEntryToGL(ValueEntry: Record "Value Entry") +``` + +## Scope + +- This codeunit is kept as a separate copy in several country/region layers. The accessibility change + must be applied consistently to **every** layer that has a copy, so the method is public everywhere: + - `App/Layers/W1/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al` + - `App/Layers/APAC/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al` + - `App/Layers/CH/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al` + - `App/Layers/ES/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al` + - `App/Layers/IT/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al` + - `App/Layers/RU/BaseApp/Inventory/Posting/ItemJnlPostLine.Codeunit.al` +- No other layers keep a copy of this codeunit, so only these six files change. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30349/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30349/README.md new file mode 100644 index 000000000..c9142055d --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30349/README.md @@ -0,0 +1,34 @@ +# Extensibility request: OnBeforeCheckTrackingIfRequired event in table 83 "Item Journal Line" + +## Why do you need this change? + +I need to provide better error messages for the user. The current error messages make references to the +"Item Journal Line" record — which is a temporary record for document postings (such as a Sales Order or +Purchase Invoice) — so the user cannot tell from the error message which document line or which specific +item is missing the item tracking (Serial/Lot). + +I therefore want to do the check myself and error with a better error message BEFORE the standard code, by +having an `OnBeforCheckTrackingIfRequired(Rec, ItemTrackingSetup)` event at the start. + +## Describe the request + +Add the following at the start of `CheckTrackingIfRequired`: + +```al +OnBeforCheckTrackingIfRequired(Rec, ItemTrackingSetup); +``` + +Event procedure: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeCheckTrackingIfRequired(ItemJournalLine: Record "Item Journal Line"; ItemTrackingSetup: Record "Item Tracking Setup"); +begin +end; +``` + +## Scope + +- File: `ItemJournalLine.Table.al` +- This table is present in the W1, APAC, CH, ES, FR, GB, IT and RU layers (each keeps its own copy). The + identical change must be applied to all eight layer files (W1, APAC, CH, ES, FR, GB, IT, RU). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30351/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30351/README.md new file mode 100644 index 000000000..848864728 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30351/README.md @@ -0,0 +1,45 @@ +# Extensibility request: add parameters to OnCheckExpirationDateOnBeforeAssignExpirationDate in codeunit 22 "Item Jnl.-Post Line" + +## Why do you need this change? + +I would like 3 additional parameters added to the `OnCheckExpirationDateOnBeforeAssignExpirationDate` event +so that I can create similar code/logic to the standard code (although slightly modified) and handle the +event using the existing `IsHandled` parameter. + +Note: as this is an integration event, adding extra parameters is not a breaking change, and existing +subscribers will be unaffected. + +## Describe the request + +Add the following 3 parameters to `OnCheckExpirationDateOnBeforeAssignExpirationDate` (raised in +`CheckExpirationDate`) and pass them from the caller: + +``` +GlobalItemTrackingCode +ItemJnlLine2 +SignFactor +``` + +Current event signature: + +```al +[IntegrationEvent(false, false)] +local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record "Tracking Specification" temporary; ExistingExpirationDate: Date; var IsHandled: Boolean) +begin +end; +``` + +New event signature: + +```al +[IntegrationEvent(false, false)] +local procedure OnCheckExpirationDateOnBeforeAssignExpirationDate(var TempTrackingSpecification: Record "Tracking Specification" temporary; ExistingExpirationDate: Date; GlobalItemTrackingCode: Record "Item Tracking Code"; ItemJnlLine2: Record "Item Journal Line"; SignFactor: Integer; var IsHandled: Boolean) +begin +end; +``` + +## Scope + +- File: `ItemJnlPostLine.Codeunit.al` +- This codeunit is present in the W1, APAC, CH, ES, IT and RU layers (each keeps its own copy). The + identical change must be applied to all six layer files (W1, APAC, CH, ES, IT, RU). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30352/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30352/README.md new file mode 100644 index 000000000..7069229e9 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30352/README.md @@ -0,0 +1,50 @@ +# Extensibility request: add SkipNewExpirationDateCheck to OnCheckExpirationDateOnAfterCalcSumLot in codeunit 22 "Item Jnl.-Post Line" + +## Why do you need this change? + +I would like an additional `var` parameter called `SkipNewExpirationDateCheck` added to the +`OnCheckExpirationDateOnAfterCalcSumLot` event, and if this is set to `true` by the subscriber, to then skip +the `TestField` on the "New Expiration Date" field. + +Note: as this is an integration event, adding extra parameters is not a breaking change, and existing +subscribers will be unaffected. + +## Describe the request + +Existing code: + +```al +OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification); +if (SumOfEntries > 0) and + ((SumOfEntries <> SumLot) or (TempTrackingSpecification."New Lot No." <> TempTrackingSpecification."Lot No.") + or (TempTrackingSpecification."New Package No." <> TempTrackingSpecification."Package No.")) +then + TempTrackingSpecification.TestField("New Expiration Date", ExistingExpirationDate); +``` + +Amended/proposed code: + +```al +SkipNewExpirationDateCheck := false; +OnCheckExpirationDateOnAfterCalcSumLot(SumLot, SignFactor, TempTrackingSpecification, SkipNewExpirationDateCheck); +if (not SkipNewExpirationDateCheck) and (SumOfEntries > 0) and + ((SumOfEntries <> SumLot) or (TempTrackingSpecification."New Lot No." <> TempTrackingSpecification."Lot No.") + or (TempTrackingSpecification."New Package No." <> TempTrackingSpecification."Package No.")) +then + TempTrackingSpecification.TestField("New Expiration Date", ExistingExpirationDate); +``` + +Updated event signature: + +```al +[IntegrationEvent(false, false)] +local procedure OnCheckExpirationDateOnAfterCalcSumLot(var SumLot: Decimal; SignFactor: Integer; var TempTrackingSpecification: Record "Tracking Specification" temporary; var SkipNewExpirationDateCheck: Boolean) +begin +end; +``` + +## Scope + +- File: `ItemJnlPostLine.Codeunit.al` +- This codeunit is present in the W1, APAC, CH, ES, IT and RU layers (each keeps its own copy). The + identical change must be applied to all six layer files (W1, APAC, CH, ES, IT, RU). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30359/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30359/README.md new file mode 100644 index 000000000..d51bf95c5 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30359/README.md @@ -0,0 +1,38 @@ +# Extensibility request: add a Direction parameter to OnBeforeScheduleRoutingLine in codeunit 99000774 "Calculate Routing Line" + +## Why do you need this change? + +We want to modify the behaviour of starting- and ending-datetime calculation in production order routing. +Therefore we need the parameter `Direction` as a `var` parameter in the parameter list of an existing +publisher event. + +## Describe the request + +In codeunit 99000774 "Calculate Routing Line", in the scheduling logic, add `Direction` as a `var` +parameter to the publisher `OnBeforeScheduleRoutingLine`, and pass it where the event is raised: + +```al +IsHandled := false; +OnBeforeScheduleRoutingLine(ProdOrderRoutingLine, CalcStartEndDate, IsHandled, Direction); +if not IsHandled then + if ProdOrderRoutingLine."Schedule Manually" then + CalculateRoutingLineFixed() + else + if Direction = Direction::Backward then + CalcRoutingLineBack(CalcStartEndDate) + else + CalcRoutingLineForward(CalcStartEndDate); +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeScheduleRoutingLine(var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var CalcStartEndDate: Boolean; var IsHandled: Boolean; var Direction: Option Forward,Backward) +begin +end; +``` + +## Scope + +- File: `CalculateRoutingLine.Codeunit.al` +- This codeunit exists only in the W1 base layer, so the change lives in W1 alone (no country/region layer + counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30361/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30361/README.md new file mode 100644 index 000000000..161c154e9 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30361/README.md @@ -0,0 +1,42 @@ +# Extensibility request: extension point in "Job Archive Management".AutoArchiveJob + +## Why this change is needed + +`Job Archive Management.AutoArchiveJob` decides how a job is archived based on +`Jobs Setup."Archive Jobs"`. Today it only handles the `Always` and `Question` options; any other +value silently does nothing, so there is no way for an extension to plug in custom archive handling +(for example, a partner wants a "Request Page" style flow that collects extra input from the user +during archiving). + +We need an extension point so subscribers can react when neither of the standard options applies. + +## Requested change + +Add an integration event in the `else` branch of the `case Jobs Setup."Archive Jobs"` statement in +`AutoArchiveJob`, so extensions can handle additional archive modes. The event should pass the `Job` +and `Jobs Setup` records to subscribers. + +Illustrative shape of the request (final event name and signature must follow BC event conventions): + +```al +procedure AutoArchiveJob(var Job: Record Job) +var + JobSetup: Record "Jobs Setup"; +begin + JobSetup.Get(); + case JobSetup."Archive Jobs" of + JobSetup."Archive Jobs"::Always: + StoreJob(Job, false); + JobSetup."Archive Jobs"::Question: + ArchiveJob(Job); + else + // new integration event raised here, passing Job and Jobs Setup + end; +end; +``` + +## Scope + +- File: `App/Layers/W1/BaseApp/Projects/Project/Archive/JobArchiveManagement.Codeunit.al` +- This codeunit exists only in the W1 base layer, so the change lives in W1 alone (no country/region + layer counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30362/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30362/README.md new file mode 100644 index 000000000..e5a77eb24 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30362/README.md @@ -0,0 +1,57 @@ +# Extensibility request: extension point in codeunit 5980 "Service-Post" before posted header creation + +## Why this change is needed + +The extensibility of codeunit 5980 "Service-Post" is limited compared to codeunit 80 "Sales-Post". +In "Sales-Post", an integration event lets partners override the standard logic that decides whether +a posted invoice or a posted credit memo should be created, using the `IsHandled` pattern. + +In "Service-Post" the equivalent decision is hardcoded: when a document is invoiced, the code decides +between preparing a posted service invoice header and a posted service credit memo header purely from +`Service Header."Document Type"`. Because there is no event before this logic, extensions cannot +support custom Service document types that need a different posting flow or a different posted +document. + +We need an extension point so partners can replace this decision in a supported, upgrade-safe way, +bringing Service-Post in line with Sales-Post. + +## Requested change + +Add a new integration event in codeunit 5980 "Service-Post", raised **immediately before** the +standard logic that determines whether a posted service invoice or posted service credit memo is +created (the `if Invoice then ...` block that calls `PrepareInvoiceHeader` / `PrepareCrMemoHeader`). + +Use the `IsHandled` pattern, matching the existing behavior in "Sales-Post": + +- Declare an `IsHandled: Boolean` local variable and initialize it to `false`. +- Raise the new event before the standard block, passing enough context for a subscriber to take + over: the `Service Header`, the `IsHandled` flag (by reference), and the posted invoice and credit + memo numbers (by reference). +- Guard the existing standard block with `if not IsHandled then`, so a subscriber that sets + `IsHandled := true` fully replaces the standard decision. + +Illustrative shape (final event name and signature must follow BC event conventions): + +```al +IsHandled := false; +// new integration event raised here, passing Service Header, IsHandled, and the posted document numbers +if not IsHandled then + if Invoice then + if ServiceHeader."Document Type" in [ServiceHeader."Document Type"::Order, ServiceHeader."Document Type"::Invoice] then begin + ServInvoiceNo := ServDocumentsMgt.PrepareInvoiceHeader(Window); + // ... + end else begin + ServCrMemoNo := ServDocumentsMgt.PrepareCrMemoHeader(Window); + // ... + end; +``` + +## Scope + +- This codeunit exists in the W1 base layer **and** in the IT and NA layers, which keep their own + copies of the same posting logic. The change must be applied consistently to every layer that has a + copy: + - `App/Layers/W1/BaseApp/Service/Posting/ServicePost.Codeunit.al` + - `App/Layers/IT/BaseApp/Service/Posting/ServicePost.Codeunit.al` + - `App/Layers/NA/BaseApp/Service/Posting/ServicePost.Codeunit.al` +- No other layers keep a copy of this codeunit, so only these three files change. diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30377/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30377/README.md new file mode 100644 index 000000000..953421401 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30377/README.md @@ -0,0 +1,51 @@ +# Extensibility request: extension point in report 10135 "Item Sales Statistics" before the "print only if sales" skip + +## Why this change is needed + +Report 10135 "Item Sales Statistics" (an NA local report) computes an item's `Sales (Qty.)` +FlowField in the `Item` data item's `OnAfterGetRecord` trigger, and, in the same statement block, +immediately decides whether to skip the record based on the report's `PrintOnlyIfSales` option: + +```al +CalcFields("Sales (Qty.)", "Sales (LCY)", "COGS (LCY)"); +if ("Sales (Qty.)" = 0) and PrintOnlyIfSales then + CurrReport.Skip(); +``` + +`PrintOnlyIfSales` is a private global with no accessible getter, and the skip decision is made +before any extension trigger runs. An extension that computes its own sales figure (for example an +alternate unit-of-measure total from its own ledger sums) therefore cannot influence whether a record +is printed: an item the extension considers to have real sales can be silently skipped, and an item +the extension considers zero-sales can still print. + +We need an extension point that exposes the already-computed skip decision so a subscriber can +override it in either direction. + +## Requested change + +Add a new integration event to report 10135 "Item Sales Statistics", raised in the `Item` data +item's `OnAfterGetRecord` trigger **immediately after** the existing `CalcFields` call and +**before** the `PrintOnlyIfSales` skip check. + +- Introduce a local `SkipRecord: Boolean` variable in the trigger. +- Assign it the existing skip condition (`("Sales (Qty.)" = 0) and PrintOnlyIfSales`). +- Raise the new event passing the `Item` record and `PrintOnlyIfSales`, and the `SkipRecord` flag by + reference so a subscriber can override it. +- Replace the original condition with `if SkipRecord then CurrReport.Skip();`. + +Illustrative shape (final event name and signature must follow BC event conventions): + +```al +SetRange("Variant Filter"); +CalcFields("Sales (Qty.)", "Sales (LCY)", "COGS (LCY)"); +SkipRecord := ("Sales (Qty.)" = 0) and PrintOnlyIfSales; +// new integration event raised here, passing Item, PrintOnlyIfSales, and SkipRecord (var) +if SkipRecord then + CurrReport.Skip(); +``` + +## Scope + +- File: `App/Layers/NA/BaseApp/Local/Inventory/Reports/ItemSalesStatistics.Report.al` +- This is an NA local report that exists only in the NA layer, so the change lives in the NA layer + alone (no other country/region layer counterparts to propagate to). diff --git a/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30380/README.md b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30380/README.md new file mode 100644 index 000000000..650f0a1a0 --- /dev/null +++ b/dataset/problemstatement/microsoftInternal__NAV-Ext_Request_Impl-30380/README.md @@ -0,0 +1,64 @@ +# Extensibility request: event in "Item Tracking Management".SplitPostedWhseRcptLine when no Whse. Item Entry Relation is found + +## Why do you need this change? + +We need an additional event in codeunit 6500 "Item Tracking Management". The event placement and logic +should be the same as within the procedure `SplitInternalPutAwayLine` — specifically, the event +`OnSplitInternalPutAwayLineOnNotFindWhseItemTrackingLine`. + +We need this event to perform custom splitting. + +This was already requested previously, but the whole request was declined because of the event inside +the loop. We agree that custom splitting can be handled before the loop, but we still need the new event +in case Warehouse Item Entry Relation is not found. + +**Performance Considerations:** +The proposed event `OnSplitPostedWhseReceiptLineOnNotFindWhseItemEntryRelation` is located outside the +`repeat...until` loop, in the `else` branch that executes only when `WhseItemEntryRelation.FindSet()` +returns no records for the given posted warehouse receipt line. It fires at most once per call to +`SplitPostedWhseRcptLine`, not once per iteration, so the added overhead is negligible — a single +conditional check and, if no subscriber is attached, no additional cost at all. + +**Multi-Extension Interaction:** +The event follows the standard `IsHandled` pattern already used elsewhere in this codeunit (e.g. +`OnBeforeSplitPostedWhseReceiptLine`). Once any subscriber sets `IsHandled` to `true`, the base logic +(`TempPostedWhseRcptLine := PostedWhseRcptLine; TempPostedWhseRcptLine.Insert();`) is skipped. + +## Describe the request + +Add a new integration event in `SplitPostedWhseRcptLine` when `WhseItemEntryRelation.FindSet()` does not +find any records, using the `IsHandled` pattern before the default insert of `PostedWhseRcptLine` into +`TempPostedWhseRcptLine`. + +Location in the `SplitPostedWhseRcptLine()` procedure: + +```al + end; + until WhseItemEntryRelation.Next() = 0 + else begin + IsHandled := false; + OnSplitPostedWhseReceiptLineOnNotFindWhseItemEntryRelation(PostedWhseRcptLine, TempPostedWhseRcptLine, IsHandled); + if not IsHandled then begin + TempPostedWhseRcptLine := PostedWhseRcptLine; + TempPostedWhseRcptLine.Insert(); + end; + end; + + OnAfterSplitPostedWhseReceiptLine(PostedWhseRcptLine, TempPostedWhseRcptLine); + end; +``` + +Event publisher (add at the end of the codeunit with the other events): + +```al +[IntegrationEvent(false, false)] +local procedure OnSplitPostedWhseReceiptLineOnNotFindWhseItemEntryRelation(PostedWhseRcptLine: Record "Posted Whse. Receipt Line"; var TempPostedWhseRcptLine: Record "Posted Whse. Receipt Line" temporary; var IsHandled: Boolean) +begin +end; +``` + +## Scope + +- File: `ItemTrackingManagement.Codeunit.al` +- This codeunit exists only in the W1 base layer, so the change lives in W1 alone (no country/region + layer counterparts to propagate to). diff --git a/scripts/BCBenchUtils.psm1 b/scripts/BCBenchUtils.psm1 index a8a02cf9c..e3baf0b2d 100644 --- a/scripts/BCBenchUtils.psm1 +++ b/scripts/BCBenchUtils.psm1 @@ -490,7 +490,7 @@ function Get-BCBenchDatasetPath { param( [Parameter(Mandatory = $true)] # Category validation lives only here: every caller resolves the dataset path through this function, so there's no need to duplicate ValidateSet on each caller. - [ValidateSet("bug-fix", "test-generation", "code-review", "nl2al")] + [ValidateSet("bug-fix", "test-generation", "code-review", "nl2al", "extensibility-request-implement", "extensibility-request-triage")] [string] $Category ) @@ -499,6 +499,8 @@ function Get-BCBenchDatasetPath { "test-generation" { $DatasetName = "bcbench.jsonl" } "code-review" { $DatasetName = "codereview.jsonl" } "nl2al" { $DatasetName = "nl2al.jsonl" } + "extensibility-request-implement" { $DatasetName = "extensibility_request_implement.jsonl" } + "extensibility-request-triage" { $DatasetName = "extensibility_request_triage.jsonl" } } [string] $projectRoot = Split-Path $PSScriptRoot -Parent diff --git a/src/bcbench/agent/shared/config.yaml b/src/bcbench/agent/shared/config.yaml index 42020ff3b..7fbeb9fd0 100644 --- a/src/bcbench/agent/shared/config.yaml +++ b/src/bcbench/agent/shared/config.yaml @@ -50,6 +50,33 @@ prompt: {{task}} {% endif %} + extensibility-request-implement-template: | + You are working with a Business Central (AL) code repository at {{repo_path}}. + + Task: Implement the extensibility request described below {% if include_project_paths %}in the following projects: {{project_paths}}{% endif %} + + Important constraints: + {% if not al_mcp %} + - Do NOT try to build or run tests, just provide the code changes needed + {% endif %} + - Do NOT commit any changes, and do NOT open a pull request + + Extensibility request: + {{task}} + + extensibility-request-triage-template: | + You are triaging a single Business Central extensibility request. The standard AL source is checked out at {{repo_path}}. + + Task: Triage the extensibility request below and produce your triage decision (managed labels, advisory comment, and open/closed state). + + Important constraints: + - This runs offline: read the request from the text below, do NOT call `gh` or touch any live issue. + - Do NOT build or run tests; base codebase analysis on the local `.al` files under {{repo_path}}. + - Write your final decision as valid JSON to `triage_result.json` in {{repo_path}}, containing exactly the Step 7 `Final_Output` object. Do not apply it through any host tool. + + Extensibility request: + {{task}} + code-review-template: | /review diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/agents/extensibility-request-implement.agent.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/agents/extensibility-request-implement.agent.md new file mode 100644 index 000000000..63b28bfd7 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/agents/extensibility-request-implement.agent.md @@ -0,0 +1,48 @@ +--- +name: extensibility-request-implement +description: >- + Implements a single Business Central extensibility request end to end by running the repository's + `extensibility-request-implement` skill — reading the request text, applying a guideline-aligned AL code change on the + W1 base layer, and propagating it to same-named country/region layer files. The request is provided + as plain text; nothing is fetched from an issue tracker. No build, no tests, no commit, no pull + request. Use when a task asks to implement or add an extensibility request / extension point / + integration event described in the provided text. Do not use for bug fixes. +target: github-copilot +tools: + - read + - edit + - search + - execute +--- + +# AI Extension Fix Agent + +You are the repository's autonomous extensibility-request implementation agent. You take one +extensibility request — provided as plain text — all the way to a set of local AL code changes, +entirely within the current workspace, by following the repository's **`extensibility-request-implement`** skill. + +## Step 1 — Load the full procedure (do this first) + +Open and read **`.github/skills/extensibility-request-implement/SKILL.md`** in full. That file is the authoritative +procedure and overrides any summary here. Do not edit any source file before you have read it +completely. + +## Step 2 — Run the skill + +Follow every step in SKILL.md exactly, using the extensibility request text from the current +conversation / prompt. + +## Rules + +- The extensibility request is provided as text. Never fetch anything from an issue tracker or any + external system, and never depend on labels or tickets. +- Make the smallest diff that implements the request (code surgery). +- Only edit `.al` files. Never edit `.json`, `.xml`, `.xlf`, or any other file type. +- W1-first layering: change W1, then propagate to same-named files in other layers. +- No build, no tests, no commit, no push, no pull request. Leave the changes as unstaged edits in the + working tree. +- Follow the event conventions in the skill's `guidelines.md` (event naming, parameter naming, + `IntegrationEvent` attribute, `IsHandled` handling). The guidelines override any implementation + detail suggested in the request text. +- If a mandatory part of the change cannot be met without touching a non-`.al` file, stop and report + failure instead of silently substituting defaults. diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/agents/extensibility-request-triage.agent.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/agents/extensibility-request-triage.agent.md new file mode 100644 index 000000000..a29648696 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/agents/extensibility-request-triage.agent.md @@ -0,0 +1,97 @@ +--- +name: extensibility-request-triage +description: >- + Triage a single Business Central extensibility request end to end: preprocess the thread, + check eligibility, classify the request type, validate input requirements, analyse + feasibility against the local AL source and codebase rules, assign a team, and emit the + managed labels + an advisory comment + an open/closed decision. Advisory only: a human + reviews before any action is taken. Runs offline against a local checkout. +target: github-copilot +tools: [read, search, execute, agent] +--- + +# Extensibility Request Triage Agent + +You triage **one** extensibility request and produce a triage result: managed labels, an +advisory comment, and an open/closed state. You never edit source code, and you never call +`gh` or touch a live issue. + +## Inputs + +Your prompt contains the request text (`REQUEST_TEXT`) and the AL source root (`CODE_ROOT`, +the repository you are working in). If either is missing, stop and report what is missing. + +The procedure lives next to this agent under +`TRIAGE_ROOT = .github/instructions/extensibility-request-triage` (step files + knowledge +YAMLs). Read each step file with the view tool when you reach it. + +## Run all steps 1–7 sequentially + +Carry state forward between steps — never refetch. Whenever a step sets a non-empty +`FailureLabel` (or `Success: false`), skip directly to **Step 7**. + +1. **Step 0 — Startup & input** + Read `TRIAGE_ROOT/step0-getting-started.md`, run the startup checks, and format the + provided request text into `GH_REQUEST`. If `Success: false`, stop and report. + +2. **Step 1 — Preprocess** + Read `TRIAGE_ROOT/step1-preprocess.md` and distill `GH_REQUEST` into + `DISTILLED_REQUEST`. + +3. **Step 2 — Eligibility Check** + Read `TRIAGE_ROOT/step2-eligibility-check.md` and evaluate eligibility. + - Output: `{"IsEligible": boolean, "FailureLabel": string, "FailureReason": string}` + - If not eligible, go to Step 7. + +4. **Step 3 — Request Type Classification** + Read `TRIAGE_ROOT/step3-request-types.md` and classify. + - Output: `{"Success": boolean, "TYPE": string, "SUBTYPE": string, "FailureLabel": string, "FailureReason": string}` + - Store `TYPE` and `SUBTYPE`. If `Success: false`, go to Step 7. + +5. **Step 4 — Requirements Check** + Read `TRIAGE_ROOT/step4-requirements-check.md`. Read the requirement YAMLs it names from + `TRIAGE_ROOT/input-requirements/` with the view tool. + - Output: `{"Success": boolean, "FailureLabel": string, "FailureReason": string}` + - If `Success: false`, go to Step 7. + +6. **Step 5 — Codebase Analysis** + Read `TRIAGE_ROOT/step5-codebase-analysis.md`. Load the rule YAMLs it names from + `TRIAGE_ROOT/codebase-rules/`, then analyse the **local** source under `CODE_ROOT`. + - **Keep codebase searches scoped and cheap**: derive the AL filename + (`CamelCaseName.ObjectType.al`) and `glob` for it first (e.g. + `glob("/**/SalesPost.Codeunit.al")`). Only if glob fails, run a single + targeted `grep` by object **name** (never numeric ID) for the object declaration. Never + run broad unscoped `**/*.al` content scans or search by numeric ID — they scan the whole + codebase and are very slow. If the object is not found after glob + one grep, return + `agent-not-processable` and go to Step 7. + - Output: `{"Success": boolean, "OBJECT_LIST": array, "SUGGESTED_IMPLEMENTATION": string, "FailureLabel": string, "FailureReason": string}` + - Store `OBJECT_LIST` and `SUGGESTED_IMPLEMENTATION`. If `Success: false`, go to Step 7. + +7. **Step 6 — Team Assignment** + Read `TRIAGE_ROOT/step6-team-assignment.md` and read + `TRIAGE_ROOT/team-configuration/team_namespace_mapping.yaml`. + - Output: `{"Success": boolean, "TEAM_LABEL": string, "FailureLabel": string, "FailureReason": string}` + - Store `TEAM_LABEL`. If `Success: false`, go to Step 7. + +8. **Step 7 — Finalize** + Read `TRIAGE_ROOT/step7-labels-comments.md`. Use all collected state to choose the labels, + the comment (from `TRIAGE_ROOT/comment-templates/comment_templates.yaml`), and the state. + + **CRITICAL — final action.** Write your decision as valid JSON to + `/triage_result.json`, containing **exactly** this structure — no other keys, + no renaming, no extra nesting: + ```json + { + "Final_Output": { + "labels_to_set": ["label1", "label2"], + "comment_to_post": "full comment text", + "request_state": "open", + "failureStep": "", + "failureReason": "" + } + } + ``` + Use the literal keys `labels_to_set`, `comment_to_post`, `request_state` — do NOT use + alternative names like `labels`, `state`, `issue_state`, `advisory_comment`, etc. Propose + only the managed labels defined by the step files; never invent labels. This file is your + only deliverable — if you do not write it, the triage is lost. diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_alternative_suggestions.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_alternative_suggestions.yaml new file mode 100644 index 000000000..7776c9cb2 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_alternative_suggestions.yaml @@ -0,0 +1,279 @@ +event_request_alternative_suggestions: + description: "Alternative suggestions for event-request type - recommending existing solutions or better approaches" + + rules: + - id: consecutive_events_not_allowed + applies_to: ["event-request"] + severity: "warning" + description: "Consecutive event calls (one directly after another) are not allowed" + action: "suggest_alternative" + rejection_note: "Adding a new event directly adjacent to an existing event is not allowed. The existing event should be updated with additional parameters instead." + guidance: | + **Note:** Two event calls placed consecutively (one directly after another with no meaningful code between them) are not acceptable. + Do **not** close the issue — instead, suggest updating the existing event with the additional parameters needed. + + **Rationale:** + - Consecutive events create unnecessary overhead (two event invocations instead of one) + - Subscribers would need to subscribe to multiple events for related data + - Increases complexity for extension developers + - Indicates the existing event is missing parameters rather than needing a new event + - Violates the principle of event consolidation + + **Detection Criteria:** + - New event requested immediately before an existing event (no code between) + - New event requested immediately after an existing event (no code between) + - Only whitespace, comments, or variable declarations between events + + **Example - What was requested (consecutive events, not acceptable):** + ```al + local procedure ProcessSalesOrder(var SalesHeader: Record "Sales Header") + begin + // Some processing code... + + // ❌ NOT ALLOWED - Two events directly next to each other + OnBeforeProcessSalesOrderNewEvent(SalesHeader, CustomerNo); // New event requested + OnBeforeProcessSalesOrder(SalesHeader, IsHandled); // Existing event + + if not IsHandled then begin + // Processing logic + end; + end; + ``` + + **Suggested Solution - Update the existing event instead:** + ```al + local procedure ProcessSalesOrder(var SalesHeader: Record "Sales Header") + begin + // Some processing code... + + // ✅ CORRECT - Existing event updated with new parameter + OnBeforeProcessSalesOrder(SalesHeader, IsHandled, CustomerNo); // Added CustomerNo parameter + + if not IsHandled then begin + // Processing logic + end; + end; + + // Updated event signature with new parameter at the END + [IntegrationEvent(false, false)] + local procedure OnBeforeProcessSalesOrder(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean; var CustomerNo: Code[20]) + begin + end; + ``` + + **What Counts as "Consecutive":** + - Direct adjacency (no code between events) + - Only whitespace between events + - Only comments between events + - Only variable declarations between events (no executable code) + + **What Does NOT Count as "Consecutive":** + - Meaningful executable code between events (assignments, function calls, conditionals) + - Different logical sections of a procedure + - Events in different procedures/triggers + + - id: similar_events_exist + applies_to: ["event-request"] + severity: "warning" + description: "Similar events already available nearby" + action: "suggest_alternative" + warning_message: "Similar event exists at this location: {event_name}. Consider adding parameters to the existing event instead of creating a new one." + requires_confirmation: true + guidance: | + When analyzing an event request, check if a similar event already exists at or near the requested location. + If found, suggest adding parameters to the existing event instead of creating a new one. + + **Detection Criteria:** + - Event fires at the same logical point (before/after same operation) + - Event provides access to the same or related records + + **Why Consolidate Events:** + - Reduces event overhead (fewer event invocations) + - Simplifies subscriber implementation (one event to subscribe to) + - Easier to maintain and understand + - Avoids confusion about which event to use + + **Example - Similar Event Exists:** + + Author requests: "Add OnBeforePostSalesLine event with CustomerNo parameter" + + Existing event found: + ```al + [IntegrationEvent(false, false)] + local procedure OnBeforePostSalesLine(var SalesLine: Record "Sales Line"; var IsHandled: Boolean) + begin + end; + ``` + + ✅ **Recommended Solution** - Add parameter to existing event: + ```al + [IntegrationEvent(false, false)] + local procedure OnBeforePostSalesLine(var SalesLine: Record "Sales Line"; var IsHandled: Boolean; CustomerNo: Code[20]) + begin + end; + ``` + + ❌ **Not Recommended** - Creating a new similar event: + ```al + // Don't create this - too similar to existing event + [IntegrationEvent(false, false)] + local procedure OnBeforePostSalesLineWithCustomer(var SalesLine: Record "Sales Line"; CustomerNo: Code[20]) + begin + end; + ``` + + **Agent Response Template:** + ``` + A similar event `{existing_event_name}` already exists at this location. + + Instead of creating a new event, we recommend adding the requested parameter(s) to the existing event: + - Current: `{current_signature}` + - Proposed: `{proposed_signature}` + + Do you agree with this approach? + ``` + + - id: existing_event_may_satisfy_request + applies_to: ["event-request"] + severity: "warning" + description: "An existing nearby event may satisfy the author's use case" + action: "request_clarification" + warning_message: "An existing event may satisfy this request. Clarification needed before adding a new event." + requires_confirmation: true + guidance: | + When analyzing an event request, check if an existing event at a different (but nearby) location + could satisfy the author's use case. This differs from `similar_events_exist` - here the event + is at a DIFFERENT location but might still achieve the same goal. + + **When to Apply This Rule:** + - Author wants to modify a record before a specific operation + - An existing event fires at a nearby point with the same record available + - It's unclear if the timing difference between events matters for the use case + + **Analysis Steps:** + 1. Identify the author's stated goal (what they want to modify and why) + 2. Check if an existing event provides access to the same record + 3. Analyze if the existing event fires at a point where modifications would achieve the same result + 4. Consider if data flow to subsequent procedures is affected by event placement + + **Example Scenario:** + + Author requests: "Add OnBeforeValidateQuantity event in Sales Line table to modify Quantity" + + Agent finds existing event: + ```al + // Existing event in OnValidate trigger for Quantity field + [IntegrationEvent(false, false)] + local procedure OnAfterValidateQuantity(var SalesLine: Record "Sales Line") + begin + end; + ``` + + **Questions to Ask:** + - Does the author need to modify Quantity BEFORE validation runs, or would modifying it AFTER work? + - Will the existing OnAfterValidateQuantity event allow the author to achieve their goal? + - What specific behavior requires the event to be BEFORE vs AFTER? + + **Agent Response Template:** + ``` + We noticed there is an existing event `OnAfterValidateQuantity` that fires after the Quantity field is validated. + + This event provides access to the same `Sales Line` record that you want to modify. + + **Question:** Could you use the existing `OnAfterValidateQuantity` event for your scenario? + + If not, please explain: + 1. What specific modifications do you need to make? + 2. Why must these modifications happen BEFORE validation rather than after? + 3. What behavior would differ if you used the existing event instead? + ``` + + - id: integration_event_include_sender_true + applies_to: ["event-request"] + severity: "blocking" + description: "IntegrationEvent with IncludeSender=true (first parameter is true)" + action: "suggest_alternative_or_request_justification" + requires_confirmation: true + warning_message: "The event signature has IncludeSender set to true. For codeunits, consider using 'this' keyword instead. For all object types, a detailed justification is required." + guidance: | + This rule has two behaviors depending on the object type: + + --- + + ## Behavior A: Codeunit Objects — Suggest 'this' Keyword + + When the event is in a **codeunit**, suggest replacing IncludeSender=true with an explicit 'this' parameter. + + **What is 'this' keyword:** + The 'this' keyword in AL provides a reference to the current instance of an object. It can be passed as a parameter to events, allowing subscribers to access the publisher object's public members. + + **Why 'this' is Better Than IncludeSender=true:** + - More explicit and intentional - developer consciously passes the reference + - Clearer code - the parameter is visible in the event call + + **Example - Using IncludeSender=true (Less Preferred):** + ```al + codeunit 50100 "Sales Order Processor" + { + // ❌ Less preferred - IncludeSender=true affects all subscribers + [IntegrationEvent(true, false)] + local procedure OnBeforeProcessOrder(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean) + begin + end; + } + ``` + **Example - Using 'this' Keyword (Preferred):** + ```al + codeunit 50100 "Sales Order Processor" + { + // ✅ Preferred - explicit 'this' parameter + [IntegrationEvent(false, false)] + local procedure OnBeforeProcessOrder(var SalesHeader: Record "Sales Header"; var IsHandled: Boolean; Sender: Codeunit "Sales Order Processor") + begin + end; + + local procedure ProcessOrder(var SalesHeader: Record "Sales Header") + var + IsHandled: Boolean; + begin + OnBeforeProcessOrder(SalesHeader, IsHandled, this); // Explicitly passing 'this' + end; + } + ``` + + **Agent Response Template (Codeunit):** + ``` + Instead of IncludeSender=true, we recommend using the 'this' keyword explicitly: + + **Suggested Implementation:** + - Pass 'this' when calling the event + + This approach is more explicit and follows modern AL patterns. + Do you agree with this approach, or do you have a specific reason for requiring IncludeSender=true? + ``` + + --- + + ## Behavior B: All Object Types — Require Detailed Justification + + For **all object types** (including codeunits, pages, reports, tables, etc.), if the author requests IncludeSender=true **without providing a justification**, ask for one before proceeding. + + **When to Apply:** + - The request includes IncludeSender=true (first parameter of [IntegrationEvent] is true) + - The author has not explained why IncludeSender is needed + + **What Counts as Sufficient Justification:** + - A concrete use case that requires the subscriber to call back into the publisher object + - Explanation of why an explicit parameter cannot be used instead + - Reference to a specific scenario where the subscriber needs the sender instance + + **Agent Response Template (Justification Required):** + ``` + The requested event has IncludeSender set to true. Before proceeding, please provide a detailed justification: + + 1. **Why is IncludeSender=true needed?** What does the subscriber need to do with the sender instance? + 2. **Why can't an explicit parameter be used instead?** (e.g., passing the relevant data directly as a parameter) + 3. **What is the concrete use case** that requires access to the publisher object from the subscriber? + + This information is needed to evaluate whether IncludeSender=true is the appropriate design choice here. + ``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_implementation.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_implementation.yaml new file mode 100644 index 000000000..0e8f7dc4f --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_implementation.yaml @@ -0,0 +1,331 @@ +event_request_implementation: + description: "Implementation rules for event-request type - best practices and patterns" + + rules: + - id: never_suggest_ishandled_unless_requested + applies_to: ["event-request"] + severity: "notice" + description: "Agent must NEVER suggest IsHandled events unless explicitly requested by the author" + action: "internal_warning" + rule: "When implementing event requests, ONLY add IsHandled parameter if the author EXPLICITLY requested it using keywords like 'IsHandled', 'bypass', 'skip', 'handled', or 'prevent execution'. Regular event requests get regular events WITHOUT IsHandled." + guidance: | + **CRITICAL RULE:** The agent must NEVER suggest or implement an IsHandled event pattern unless the author explicitly requested it. + + **Detection of IsHandled Request:** + The author must use one of these explicit indicators: + - "IsHandled" keyword in the request + - "bypass" or "skip" the standard logic + - "prevent" or "stop" execution + - "handled" parameter + - Explicitly mentions wanting to replace/override the standard behavior + + **If NOT explicitly requested:** + - Implement a REGULAR IntegrationEvent without IsHandled parameter + - Event should provide access to data for extension purposes + - Event should NOT include bypass/skip functionality + + **Example - Regular Event Request (NO IsHandled keywords):** + Author says: "Add an event publisher in TestPurchaseLines" + + ❌ WRONG - Agent adds IsHandled: + ```al + [IntegrationEvent(false, false)] + local procedure OnBeforeCheckIsCancellationSupported(...; var IsHandled: Boolean) + ``` + + ✅ CORRECT - Agent adds regular event: + ```al + [IntegrationEvent(false, false)] + local procedure OnBeforeCheckIsCancellationSupported(...; var IsCancellationSupported: Boolean) + ``` + + **Example - IsHandled Request (explicit keywords):** + Author says: "Add IsHandled event to bypass the credit limit check" + + ✅ CORRECT - Agent adds IsHandled: + ```al + [IntegrationEvent(false, false)] + local procedure OnBeforeCreditLimitCheck(...; var IsHandled: Boolean) + ``` + + **Rationale:** + - IsHandled events allow bypassing critical business logic + - They carry higher risk and require explicit author intent + - Regular events are safer and provide extensibility without bypass capability + - Agent should not escalate the scope of a request beyond what was asked + + - id: adding_var_to_event_parameter + applies_to: ["event-request"] + severity: "notice" + description: "Adding 'var' modifier to existing event parameter is allowed (non-breaking change)" + action: "internal_warning" + detailed_explanation: | + **Not applicable for NEW events.** + **Allowed Change:** Adding `var` to an existing event parameter is NOT a breaking change. + + **Rationale:** + - Existing subscribers continue to work (they just won't modify the value) + - Expands functionality without breaking backward compatibility + - Allows new subscribers to modify the parameter while old ones remain unaffected + + **Example - Allowed Change:** + + Original event: + ```al + [IntegrationEvent(false, false)] + local procedure OnAfterConfirmPost(PurchaseHeader: Record "Purchase Header"; var IsHandled: Boolean) + begin + end; + ``` + + Changed to (ALLOWED): + ```al + [IntegrationEvent(false, false)] + local procedure OnAfterConfirmPost(var PurchaseHeader: Record "Purchase Header"; var IsHandled: Boolean) + begin + end; + ``` + + - id: new_parameters_at_end + applies_to: ["event-request"] + severity: "notice" + description: "New parameters in existing events must be added at the end of the parameter list" + action: "internal_warning" + exception_note: "When adding new parameters to an existing event, ALWAYS place them at the end of the parameter list, regardless of where the author suggested placing them." + detailed_explanation: | + **Mandatory Rule:** New parameters in existing events must ALWAYS be added at the END of the parameter list. + + **Rationale:** + - Adding parameters in the middle of the signature would be hard to track + - It is easier to maintain and understand changes when new parameters are added at the end + + **Example - Correct Implementation:** + + Original event: + ```al + [IntegrationEvent(false, false)] + local procedure OnBeforeProcess(var Customer: Record Customer; var IsHandled: Boolean) + begin + end; + ``` + + Author requests to add `NewParam: Text` parameter. + + ❌ Wrong (even if author suggested this): + ```al + local procedure OnBeforeProcess(var Customer: Record Customer; NewParam: Text; var IsHandled: Boolean) + ``` + + ✅ Correct (always add at the end): + ```al + local procedure OnBeforeProcess(var Customer: Record Customer; var IsHandled: Boolean; NewParam: Text) + ``` + + - id: temporary_record_parameter_naming + applies_to: ["event-request"] + severity: "notice" + description: "Temporary record parameters must have 'Temp' prefix" + check_method: "parameter_naming_analysis" + action: "internal_warning" + rule: "When adding a temporary record parameter to an event, the parameter name MUST start with 'Temp' prefix." + guidance: | + **Mandatory Rule:** All temporary record parameters in event signatures must have the 'Temp' prefix. + + **Rationale:** + - Clearly indicates to subscribers that the record is temporary + - Prevents confusion about data persistence + - Follows BC naming conventions for temporary variables + - Makes code more readable and self-documenting + + **Example - Correct Implementation:** + + ❌ Wrong: + ```al + [IntegrationEvent(false, false)] + local procedure OnAfterProcess(var InvtOrderTracking: Record "Invt. Order Tracking" temporary) + begin + end; + ``` + + ✅ Correct: + ```al + [IntegrationEvent(false, false)] + local procedure OnAfterProcess(var TempInvtOrderTracking: Record "Invt. Order Tracking" temporary) + begin + end; + ``` + + - id: multiple_calls_same_event_location + applies_to: ["event-request"] + severity: "warning" + description: "Multiple event calls requested for the same location require separate event publishers" + action: "internal_warning" + rule: "When a request asks for multiple event calls at the same location (e.g., same procedure/trigger), each call MUST have its own separate event publisher with a unique name." + guidance: | + **Mandatory Rule:** If a request includes multiple calls for the same event location, each call must be implemented as a separate event publisher with a distinct name. + + **Rationale:** + - Each event should have a single, clear purpose + - Separate events allow subscribers to choose which specific event to handle + - Improves code readability and maintainability + - Allows for better performance optimization (subscribers only listen to events they need) + - Provides clearer semantics for what each event represents + + **Naming Convention for Separate Events:** + - Use descriptive suffixes that indicate the specific purpose + - Follow pattern: `On[Before/After][Operation][SpecificPurpose]` + - Examples: + - `OnBeforePostValidateCustomer` + - `OnBeforePostCheckInventory` + - `OnAfterInsertUpdateDimensions` + - `OnAfterInsertNotifyWarehouse` + + - id: event_naming_convention + applies_to: ["event-request"] + severity: "notice" + description: "Event names must follow the standard naming pattern regardless of author's suggested name" + action: "internal_warning" + rule: "Agent MUST apply the standard event naming convention even if the author proposes a different name." + guidance: | + **Mandatory Rule:** All events must follow the standard naming pattern based on their placement location. + + --- + + ## Option 1: Events at Beginning/End of Procedure or Trigger + + **Pattern:** `OnBefore/OnAfter[ProcedureName/TriggerName]` + + Use this pattern when the event fires at the very beginning or end of a procedure/trigger. + + | Location | Timing | Correct Event Name | + |----------|--------|-------------------| + | PostSalesLine procedure | Beginning | `OnBeforePostSalesLine` | + | PostSalesLine procedure | End | `OnAfterPostSalesLine` | + | OnInsert trigger | Beginning | `OnBeforeOnInsert` | + | OnModify trigger | End | `OnAfterOnModify` | + | OnDelete trigger | Beginning | `OnBeforeOnDelete` | + | OnPreReport trigger | Beginning | `OnBeforeOnPreReport` | + | OnPostReport trigger | End | `OnAfterOnPostReport` | + | "Sell-to Customer No." OnValidate | Beginning | `OnBeforeValidateSellToCustomerNo` | + | Quantity OnValidate | End | `OnAfterValidateQuantity` | + + --- + + ## Option 2: Events in the Middle of Procedure or Trigger + + **Pattern:** `On[ProcedureName/TriggerName]OnBefore/OnAfter[ActionContext]` + + Use this pattern when the event fires at a specific point inside the procedure/trigger (not at the beginning/end). + + | Location | Action Context | Correct Event Name | + |----------|----------------|-------------------| + | PostSalesLine procedure | Before validation | `OnPostSalesLineOnBeforeValidation` | + | PostSalesLine procedure | After calculation | `OnPostSalesLineOnAfterCalculation` | + | Code procedure | Before check | `OnCodeOnBeforeCheck` | + | OnInsert trigger | After init defaults | `OnOnInsertOnAfterInitDefaults` | + | OnModify trigger | Before validate location | `OnOnModifyOnBeforeValidateLocationCode` | + | "Sales Invoice Line" OnAfterGetRecord | After calc amounts | `OnSalesInvoiceLineOnAfterGetRecordOnAfterCalcAmounts` | + + --- + + **Summary:** + - **Beginning/End:** `OnBefore/OnAfter` + ProcedureName/TriggerName + - **Middle:** `On` + ProcedureName/TriggerName + `OnBefore/OnAfter` + ActionContext + + - id: event_parameter_naming_convention + applies_to: ["event-request"] + severity: "notice" + description: "Event parameters must follow naming conventions - no abbreviations for records, descriptive names for simple types" + action: "internal_warning" + rule: "When defining event publisher parameters, record parameters must use the full table name without spaces, and simple parameters must use descriptive names without abbreviations." + guidance: | + **Mandatory Rule:** Event parameters must follow strict naming conventions regardless of what the author suggests. + + ### Record Parameters - Remove Spaces, No Abbreviations + + For record parameters, use the table name with spaces removed. Do NOT use abbreviations. + + | Table Name (in AL) | Correct Parameter Name | ❌ Wrong | + |-------------------|------------------------|----------| + | "Sales Header" | `SalesHeader` | `SalesHdr`, `SH` | + | "Sales Line" | `SalesLine` | `SalesLn`, `SL` | + | "Item Ledger Entry" | `ItemLedgerEntry` | `ItemLedgEntry`, `ILE` | + | "G/L Entry" | `GLEntry` | `GLE`, `GenLedgEntry` | + | "Purchase Header" | `PurchaseHeader` | `PurchHdr`, `PH` | + | "Customer" | `Customer` | `Cust`, `C` | + | "Vendor" | `Vendor` | `Vend`, `V` | + + ### Simple Parameters - Use Descriptive Names + + | ❌ Wrong | ✅ Correct | + |----------|-----------| + | `DocNo` | `DocumentNo` | + | `Amt` | `Amount` or `TotalAmount` | + | `Qty` | `Quantity` | + | `Desc` | `Description` | + | `Date` | `PostingDate`, `DocumentDate`, etc. | + + - id: manual_binding_for_heavily_used_procedures + applies_to: ["event-request"] + severity: "notice" + description: "Events in heavily-used procedures should include manual binding guidance note" + action: "internal_warning" + rule: "When suggesting an event in a procedure that is heavily used across the codebase for different scenarios, include a note about EventSubscriberInstance property and manual binding considerations." + guidance: | + **Guidance Rule:** When an event is being added to a procedure that is called from multiple + places across the codebase for different business scenarios, the agent should include a note + in the comment about manual binding considerations. + + **Rationale:** + - Procedures can be called from multiple places for different purposes/scenarios + - Event subscribers bound with automatic binding will fire on EVERY call + - This may cause unintended side effects if subscriber was meant for a specific scenario + - Manual binding gives control over when the subscriber is active + + **Documentation Note Template:** + ``` + 📝 **Note:** This procedure is called from multiple places in the codebase for different scenarios. + If you need your event subscriber to fire only in specific scenarios, consider using + [Manual Event Subscription Binding](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/properties/devenv-eventsubscriberinstance-property#manual-binding). + ``` + + - id: ishandled_with_onafter_event_pattern + applies_to: ["event-request"] + severity: "notice" + description: "IsHandled events must preserve OnAfter event execution; OnAfter requests must check for existing IsHandled patterns" + action: "internal_warning" + rule: "When adding an IsHandled event that skips the whole procedure, or when adding OnAfter event to a procedure with existing IsHandled, ensure OnAfter event executes in ALL conditions (even when IsHandled=true)." + guidance: | + **Mandatory Rule:** OnBefore and OnAfter procedure events must BOTH execute in all conditions. When IsHandled=true skips the main procedure code, the OnAfter event must still fire. + + **Scenarios Covered:** + + **Scenario 1: Adding IsHandled event when OnAfter exists** + When someone requests an IsHandled event to skip the whole procedure AND there is already an OnAfter event (or one should be added), implement using `if not IsHandled then begin ... end;` pattern instead of `if IsHandled then exit;` + + **Scenario 2: Adding OnAfter event (regular event without IsHandled) when IsHandled with exit exists** + When someone requests an OnAfter[ProcedureName] event (regular event, no IsHandled parameter) AND there is already an OnBefore with `if IsHandled then exit;`, the agent MUST: + 1. Identify the existing `if IsHandled then exit;` pattern + 2. Refactor it to `if not IsHandled then begin ... end;` pattern + 3. Add the OnAfter event AFTER the `end;` of the if block + + **Example - CORRECT Pattern (OnAfter always fires):** + ```al + procedure GetReservationQty(var QtyReserved: Decimal; var QtyReservedBase: Decimal) + var + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeGetReservationQty(Rec, QtyReserved, QtyReservedBase, IsHandled); + + // ✅ CORRECT - Using if not IsHandled then begin...end pattern + if not IsHandled then begin + CalcFields("Reserved Quantity", "Reserved Qty. (Base)"); + QtyReserved := "Reserved Quantity"; + QtyReservedBase := "Reserved Qty. (Base)"; + end; + + // ✅ OnAfter ALWAYS executes, regardless of IsHandled value + OnAfterGetReservationQty(Rec, QtyReserved, QtyReservedBase); + end; + ``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_alternative_suggestions.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_alternative_suggestions.yaml new file mode 100644 index 000000000..6a0f197f0 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_alternative_suggestions.yaml @@ -0,0 +1,132 @@ +event_request_ishandled_alternative_suggestions: + description: "Alternative suggestion rules specific to event-request/ishandled subtype - check if existing functionality can satisfy the request" + + rules: + - id: ishandled_replaceable_with_regular_event + applies_to: ["event-request-ishandled"] + severity: "suggestion" + description: "IsHandled event may be replaceable with a regular event when the skipped logic is simple" + action: "suggest_alternative" + requires_author_confirmation: true + guidance: | + When an IsHandled event is requested and the logic inside the `if not IsHandled then` block is + simple (light assignments, a few calculations, no branching complexity), consider suggesting a + regular event placed **after** the existing logic instead of wrapping it in an IsHandled pattern. + + **Rationale:** + - IsHandled events allow subscribers to completely skip the base logic, which is a strong override + - For simple logic, it is better to let the base code run and allow subscribers to adjust the result afterward + - Regular after-events are simpler to subscribe to and less prone to misuse + - Avoids unnecessary complexity for extension developers + + **Detection — when this suggestion applies:** + - The `if not IsHandled then` block contains only a few lines of light logic (assignments, simple calculations) + - There are no complex branches, database operations, or error handling inside the IsHandled block + - The result/output variables are accessible after the block and can be passed to a regular event + + **Example 1 — IsHandled requested:** + ```al + local procedure GetFromBinContentQty(LocCode: Code[10]; FromBinCode: Code[20]; ItemNo: Code[20]; + Variant: Code[20]; UoMCode: Code[10]; WhseItemTrackingLine: Record "Whse. Item Tracking Line"): Decimal + var + BinContent: Record "Bin Content"; + Result: Decimal; + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeGetFromBinContentQty(LocCode, FromBinCode, ItemNo, Variant, UoMCode, WhseItemTrackingLine, Result, IsHandled); + if not IsHandled then begin + BinContent.Get(LocCode, FromBinCode, ItemNo, Variant, UoMCode); + BinContent.SetTrackingFilterFromWhseItemTrackingLine(WhseItemTrackingLine); + BinContent.CalcFields("Quantity (Base)"); + Result := BinContent."Quantity (Base)"; + end; + exit(Result); + end; + ``` + + **Suggested alternative — regular after-event:** + ```al + local procedure GetFromBinContentQty(LocCode: Code[10]; FromBinCode: Code[20]; ItemNo: Code[20]; + Variant: Code[20]; UoMCode: Code[10]; WhseItemTrackingLine: Record "Whse. Item Tracking Line"): Decimal + var + BinContent: Record "Bin Content"; + Result: Decimal; + begin + BinContent.Get(LocCode, FromBinCode, ItemNo, Variant, UoMCode); + BinContent.SetTrackingFilterFromWhseItemTrackingLine(WhseItemTrackingLine); + BinContent.CalcFields("Quantity (Base)"); + Result := BinContent."Quantity (Base)"; + OnAfterGetFromBinContentQty(LocCode, FromBinCode, ItemNo, Variant, UoMCode, WhseItemTrackingLine, Result); + exit(Result); + end; + ``` + + **Example 2 — IsHandled requested:** + ```al + local procedure UpdateToQtyToPick(QtyAvailableBase: Decimal; ToQtyPerUOM: Decimal; + var ToQtyToPick: Decimal; var ToQtyToPickBase: Decimal; + TotalQtyToPick: Decimal; TotalQtyToPickBase: Decimal) + var + IsHandled: Boolean; + begin + IsHandled := false; + OnUpdateToQtyToPickOnBeforeSetToQtyToPickBase(QtyAvailableBase, ToQtyPerUOM, ToQtyToPick, + ToQtyToPickBase, TotalQtyToPick, TotalQtyToPickBase, IsHandled); + if not IsHandled then begin + ToQtyToPickBase := QtyAvailableBase; + if ToQtyToPickBase > TotalQtyToPickBase then + ToQtyToPickBase := TotalQtyToPickBase; + end; + ToQtyToPick := Round(ToQtyToPickBase / ToQtyPerUOM, UnitOfMeasureManagement.QtyRndPrecision()); + end; + ``` + + **Suggested alternative — regular after-event:** + ```al + local procedure UpdateToQtyToPick(QtyAvailableBase: Decimal; ToQtyPerUOM: Decimal; + var ToQtyToPick: Decimal; var ToQtyToPickBase: Decimal; + TotalQtyToPick: Decimal; TotalQtyToPickBase: Decimal) + begin + ToQtyToPickBase := QtyAvailableBase; + if ToQtyToPickBase > TotalQtyToPickBase then + ToQtyToPickBase := TotalQtyToPickBase; + + OnUpdateToQtyToPickOnAfterCalculateToQtyToPickBase(QtyAvailableBase, ToQtyPerUOM, + ToQtyToPick, ToQtyToPickBase, TotalQtyToPick, TotalQtyToPickBase); + + ToQtyToPick := Round(ToQtyToPickBase / ToQtyPerUOM, UnitOfMeasureManagement.QtyRndPrecision()); + end; + ``` + + **Example 3 — IsHandled requested before exit:** + ```al + WarehouseActivityLine.SetTrackingFilterFromWhseItemTrackingSetupIfNotBlank(WhseItemTrackingSetup); + OnCalcQtyAssignedToPickOnAfterSetFilters(WarehouseActivityLine, WhseItemTrackingSetup); + WarehouseActivityLine.CalcSums(WarehouseActivityLine."Qty. Outstanding (Base)"); + + IsHandled := false; + OnCalcQtyAssignedToPickOnBeforeExit(WarehouseActivityLine, WhseItemTrackingSetup, QtyAssignedToPick, IsHandled); + if IsHandled then + exit(QtyAssignedToPick); + + exit(WarehouseActivityLine."Qty. Outstanding (Base)" + CalcBreakbulkOutstdQty(WarehouseActivityLine, WhseItemTrackingSetup)); + ``` + + **Suggested alternative — regular after-event on the result:** + ```al + OnCalcQtyAssignedToPickOnAfterSetFilters(WarehouseActivityLine, WhseItemTrackingSetup); + WarehouseActivityLine.CalcSums(WarehouseActivityLine."Qty. Outstanding (Base)"); + + Result := WarehouseActivityLine."Qty. Outstanding (Base)" + CalcBreakbulkOutstdQty(WarehouseActivityLine, WhseItemTrackingSetup); + OnAfterCalcQtyAssignedToPick(WarehouseActivityLine, WhseItemTrackingSetup, Result); + exit(Result); + ``` + + **Key principle:** Let the base logic run, then allow subscribers to adjust the result via a + regular event — rather than letting subscribers skip the base logic entirely. + + **Author confirmation required:** + Before implementing as a regular event, confirm with the author: + - Is being able to completely skip the base logic a requirement? + - Or is adjusting/overriding the result after the base logic sufficient? diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_blockers.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_blockers.yaml new file mode 100644 index 000000000..74bc8476e --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_blockers.yaml @@ -0,0 +1,71 @@ +event_request_ishandled_blockers: + description: "Blocker rules specific to event-request/ishandled subtype - auto-reject scenarios for IsHandled events" + + rules: + - id: ishandled_in_ondelete_trigger + applies_to: ["event-request/ishandled"] + severity: "blocking" + description: "IsHandled event at the beginning of OnDelete trigger" + action: "auto_reject" + warning_message: "IsHandled events at the beginning of OnDelete triggers are not allowed. Bypassing delete operations entirely can cause data inconsistency and orphaned records." + guidance: | + Instead of using an IsHandled event at the beginning of the OnDelete trigger, consider these alternatives: + + **Recommended approaches:** + 1. Use standard OnBeforeDelete event (without IsHandled) to add custom logic before deletion + 2. Use OnAfterDelete event to handle post-deletion operations + 3. If you need to prevent deletion conditionally, use TestField or Error statements before the delete operation + + This approach: + - ✅ Allows extensions to add logic before/after deletion + - ✅ Maintains data consistency + - ✅ Prevents orphaned records + - ✅ Follows standard extensibility patterns + + - id: ishandled_modification_to_existing_event + applies_to: ["event-request/ishandled"] + severity: "blocking" + description: "Request to add IsHandled pattern to an existing event" + action: "auto_reject" + warning_message: "Adding IsHandled pattern to existing events is not allowed. This fundamentally changes the purpose of the event and can break existing subscribers." + guidance: | + **⛔ BLOCKER: Cannot Add IsHandled to Existing Events** + + When an author requests to add IsHandled functionality to an event that already exists in the codebase, this must be **automatically rejected**. + + **Why This Is Not Allowed:** Changes the fundamental purpose and behavior of the existing event + + **Detection Criteria:** + The agent must check if: + 1. An event with the requested name already exists in the codebase + 2. The request is to add IsHandled pattern to this existing event + 3. The event doesn't currently support IsHandled functionality + + **Agent Response Template:** + ``` + ⛔ **Request Rejected: Cannot Add IsHandled to Existing Event** + + The event '[EventName]' already exists without IsHandled functionality. Adding IsHandled to an existing event is not allowed as it changes the event's purpose. + + **Recommended Alternatives:** + 1. **Add a NEW event** with IsHandled before or after the existing event + - Example: OnBeforeDoSomething() with IsHandled, followed by the existing OnDoSomething() + + 2. **Use alternatives to IsHandled pattern:** + - Add parameters to influence behavior without bypassing + - Use conditional event triggering + - Refactor code to be more extensible + + 3. **Create a separate request** for one of the above alternatives + + **Example:** + If you want to allow bypassing logic around the existing event, create a NEW event: + - Existing: `OnAfterCalculateTotal()` + - New: `OnBeforeCalculateTotal(var IsHandled: Boolean)` (raised before existing event) + ``` + + **Important Notes:** + - Even if the change seems "minor", it's still not allowed + - This rule applies to ALL existing events, regardless of age or usage + - The author should create a NEW request with an alternative approach + - Point the author to alternative suggestion rules for guidance on better patterns diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_implementation.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_implementation.yaml new file mode 100644 index 000000000..b7c094788 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_implementation.yaml @@ -0,0 +1,45 @@ +event_request_ishandled_implementation: + description: "Implementation rules specific to event-request/ishandled subtype" + + rules: + - id: ishandled_initialization_required + applies_to: ["event-request/ishandled"] + severity: "blocking" + description: "IsHandled parameter must be initialized to false before calling the event" + action: "internal_warning" + rule: "When adding a new event with IsHandled parameter or adding IsHandled parameter to an existing event, ALWAYS initialize IsHandled to false before the event call." + guidance: | + **Mandatory Rule:** The IsHandled parameter must ALWAYS be initialized to `false` immediately before calling an event that has an IsHandled parameter. + + **Rationale:** + - Ensures predictable behavior - IsHandled starts as false (not handled) + - Prevents undefined/garbage values from affecting event flow + - Subscribers expect IsHandled to be false when they receive the event + - Makes code intent clear - the default is to NOT skip the code block + - Follows BC standard patterns for IsHandled events + + **Correct Pattern:** + ```al + var + IsHandled: Boolean; + begin + IsHandled := false; + OnBeforeSalesPost(SalesHeader, IsHandled); + if IsHandled then + exit; + + // Standard logic here + end; + ``` + + **❌ Wrong - Missing initialization:** + ```al + var + IsHandled: Boolean; + begin + // ❌ WRONG - IsHandled not initialized! + OnBeforeSalesPost(SalesHeader, IsHandled); + if IsHandled then + exit; + end; + ``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_warnings.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_warnings.yaml new file mode 100644 index 000000000..e1c10ef38 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_ishandled_warnings.yaml @@ -0,0 +1,174 @@ +event_request_ishandled_warnings: + description: "Warning rules specific to event-request/ishandled subtype" + + rules: + - id: ishandled_in_loops + applies_to: ["event-request/ishandled"] + severity: "warning" + description: "IsHandled event placement inside loops" + action: "suggest_alternative" + warning_message: "IsHandled events inside loops can cause severe performance degradation as the bypass logic is evaluated on every iteration." + guidance: | + Instead of placing an IsHandled event inside the loop, consider adding a regular event **before the loop** after all filtering is complete. + + **Recommended approach:** + 1. Apply all necessary filters to the record (e.g., `DetailedVendorLedgEntry.SetRange(...)`) + 2. Add a regular IntegrationEvent AFTER filtering, before the loop starts + 3. Subscribers can then modify the filtered set without bypassing critical code inside the loop + + This approach: + - ✅ Avoids repeated IsHandled checks in the loop + - ✅ Allows extensions to modify filtering logic + - ✅ Maintains performance (event fires once, not per iteration) + - ✅ Achieves the same extensibility goal + + - id: ishandled_unsafe_code_block + applies_to: ["event-request/ishandled"] + severity: "warning" + description: "IsHandled event with unsafe/critical code block" + action: "request_author_confirmation" + warning_message: "The code block that would be bypassed contains critical operations that may not be safe to skip. Please confirm you understand the risks before proceeding." + requires_confirmation: true + guidance: | + **Warning Rule:** When an IsHandled event is requested, the agent MUST analyze the code block that would be bypassed to identify unsafe or critical operations. If such operations are found, the agent must warn the author and ask for explicit confirmation before proceeding — but must NOT auto-reject the request. + + **What Makes a Code Block Risky to Bypass (triggers a warning):** + + 1. **Database Operations Without Compensation** + - INSERT/MODIFY/DELETE operations that other code depends on + - Operations that maintain referential integrity + - Ledger entry creation or posting operations + + 2. **State Management Operations** + - Status field updates that control workflow + - Lock/unlock operations + - Commit statements that other operations depend on + + 3. **Critical Validation Logic** + - Security checks or permission validations + - Data integrity validations + - Business rule enforcements that prevent data corruption + + --- + + **Example 1 - RISKY: Bypassing Ledger Entry Creation** + Author requests: "Add IsHandled event to skip the G/L posting in PostSalesInvoice" + ```al + local procedure PostSalesInvoice(var SalesHeader: Record "Sales Header") + var + GenJnlLine: Record "Gen. Journal Line"; + begin + // ⚠️ RISKY TO BYPASS - These operations maintain financial integrity + GenJnlLine.Init(); + GenJnlLine."Document Type" := GenJnlLine."Document Type"::Invoice; + GenJnlLine."Account Type" := GenJnlLine."Account Type"::Customer; + GenJnlLine.Amount := SalesHeader."Amount Including VAT"; + GenJnlPostLine.RunWithCheck(GenJnlLine); // Creates G/L entries + + SalesHeader.Status := SalesHeader.Status::Posted; + SalesHeader.Modify(); + end; + ``` + **Why Risky:** Skipping this block would: + - Leave financial records incomplete (invoice without G/L entries) + - Cause imbalanced ledgers + - Break audit trails + - Potentially mark document as posted without actual posting + + --- + **Example 2 - RISKY: Bypassing Referential Integrity Operations** + Author requests: "Add IsHandled event to skip dimension validation in PostPurchaseLine" + + ```al + local procedure PostPurchaseLine(var PurchLine: Record "Purchase Line") + begin + // ⚠️ RISKY TO BYPASS - Maintains referential integrity + DimensionMgt.CheckDimValuePosting( + TableArray, NoArray, + PurchLine."Dimension Set ID"); + + // Creates dimension set entries linked to the posted document + PostedDimSetID := DimensionMgt.CreateDimSetFromJobTaskDim( + PurchLine."Job No.", + PurchLine."Job Task No.", + PurchLine."Dimension Set ID"); + + PurchInvLine."Dimension Set ID" := PostedDimSetID; + PurchInvLine.Insert(); + end; + ``` + **Why Risky:** Skipping this block would: + - Allow invalid dimension combinations to be posted + - Create posted documents without proper dimension linkage + - Break dimension reporting and analysis + --- + + **Agent Analysis Checklist:** + + | Check | Question | If YES | + |-------|----------|--------| + | Database writes | Does the block INSERT/MODIFY/DELETE records that other code depends on? | ⚠️ Warn author | + | Ledger operations | Does it create ledger entries or posting operations? | ⚠️ Warn author | + | Status changes | Does it update status fields that control workflow? | ⚠️ Review carefully | + | External calls | Does it call external services or APIs? | ⚠️ Review carefully | + | Validation | Does it perform security or integrity validations? | ⚠️ Warn author | + | Number series | Does it consume document numbers? | ⚠️ Warn author | + | Only assignments | Does it only assign/calculate values without side effects? | ✅ Safe | + + --- + **Agent Response Template (when risky code is detected):** + ``` + ⚠️ **Warning: The code block that would be bypassed contains potentially critical operations.** + + Bypassing this block may have unintended consequences: + [list specific risky operations found in the block] + + **Please confirm:** Are you aware of these risks and do you still want to proceed with adding this IsHandled event? + + If yes, please reply with a confirmation and explain how your extension will handle the bypassed logic correctly. + ``` + + --- + **Alternative Suggestions to Mention:** + 1. **Use regular event instead** - Allows extensions to ADD logic without BYPASSING + 2. **Add event at different location** - Before or after the critical block + 3. **Request parameter access** - Add parameters to existing events to modify behavior + 4. **Suggest code refactoring** - Extract safe portions into separate procedures + + - id: ishandled_large_code_block_extraction + applies_to: ["event-request/ishandled"] + severity: "warning" + description: "Large code blocks with IsHandled should be extracted to separate procedures" + action: "suggest_alternative" + rule: "When adding an IsHandled event that surrounds a larger block of code, consider extracting that code into a separate procedure and adding an OnBefore[ProcedureName] event with IsHandled." + guidance: | + **Recommended Practice:** When an IsHandled event would wrap a significant block of code (typically 5+ lines with related logic), the agent should evaluate whether extracting that code into a separate procedure would be a better approach. + + **Rationale:** + - Improves code organization and readability + - Creates a clear, named operation that can be bypassed + - Makes the code's intent more explicit + - Easier for subscribers to understand what they're bypassing + - Better maintainability and testability + - Follows single responsibility principle + + **Agent Analysis Required:** + Before suggesting extraction, the agent MUST verify: + 1. **Code Cohesion:** Are the values/variables in the code block logically connected? + 2. **Single Purpose:** Does the code block perform a single, identifiable operation? + 3. **Meaningful Name:** Can a descriptive procedure name be assigned that clearly describes the operation? + 4. **Parameter Set:** Can the required inputs/outputs be clearly defined as procedure parameters? + + **When NOT to Extract:** + - Code block is very small (1-3 lines) + - Variables in the block are not logically related + - No meaningful procedure name can be derived + - Extraction would require passing too many unrelated parameters + + **Decision Criteria for Agent:** + | Factor | Extract | Keep Inline | + |--------|---------|-------------| + | Lines of code | 5+ lines | 1-4 lines | + | Variables related | Yes, single purpose | No, mixed concerns | + | Meaningful name possible | Yes | No clear name | + | Parameter count | Reasonable (1-5) | Too many (6+) | diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_warnings.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_warnings.yaml new file mode 100644 index 000000000..82fb80ff0 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/event_request_warnings.yaml @@ -0,0 +1,109 @@ +event_request_warnings: + description: "Warning rules for event-request type - require justification or clarification" + + rules: + - id: recordref_parameter_in_event + applies_to: ["event-request"] + severity: "warning" + description: "RecordRef parameter used in event signature" + action: "request_clarification" + rejection_reason: "RecordRef parameters should not be added to events without good reason." + guidance: | + RecordRef is a dynamic reference type that should not be used as event parameters without good justification. Potentially risks include: + - Reduces type safety (subscribers can pass any record type) + - Makes subscribers more complex (requires dynamic type casting) + - In some cases allows attackers to access critical/private data and perform unauthorized operations + + **Required Justification:** + 1. Explain exactly how RecordRef will be used by subscribers + 2. Justify why record parameter is insufficient + 3. Explain what else did you consider before choosing RecordRef + + - id: xrec_parameter_in_event + applies_to: ["event-request"] + severity: "warning" + description: "xRec parameter used in event signature" + action: "request_justification" + rejection_note: "xRec parameter requires detailed justification as its behavior depends on the calling context." + guidance: | + xRec is a special variable that contains the previous values of a record before modification. + Using xRec in event parameters requires careful consideration because: + + **Context Dependency:** + - xRec behavior varies significantly depending on where it's used (pages vs other objects) + - In table or codeunit: Contains the record values before the current operation + - In page: May contain unexpected or stale data depending on calling context + - Can be unreliable if called from different contexts than expected + + **Required Justification:** + 1. Explain exactly how xRec will be used by subscribers + 2. Justify why current record (Rec) is insufficient + 3. Explain why previous values are specifically needed + 4. Confirm understanding of xRec behavior in the specific context + + **Risk:** xRec may contain unexpected or stale data depending on the calling context, + leading to unreliable subscriber behavior. + + - id: events_in_loops + applies_to: ["event-request"] + severity: "warning" + description: "Event placement inside loops (performance impact)" + action: "request_justification" + warning_message: "The proposed event location is inside a loop, which may cause performance issues if subscribers perform heavy operations." + requires_confirmation: true + guidance: | + **Applicability:** This rule applies only when a **new event** is being added inside a loop. + If the request is to add parameters to an **existing event** that is already inside a loop, skip this rule — the placement decision was already made. + + Events placed inside loops can cause significant performance issues because: + - The event fires on EVERY iteration of the loop + - Subscribers may perform database operations, calculations, or external calls + - Performance degrades linearly (or worse) with the number of iterations + - Can cause timeouts on large datasets + + **Detection:** + - Event location is inside `repeat...until`, `while...do`, `for...to`, or `foreach` loops + - Event location is inside a `FindSet()` or `FindFirst()` loop pattern + + **Performance Impact Example:** + ```al + // ❌ WARNING - Event fires for EVERY line (could be 1000+ times) + if SalesLine.FindSet() then + repeat + OnProcessSalesLine(SalesLine); // Fires on every iteration! + ProcessLine(SalesLine); + until SalesLine.Next() = 0; + ``` + + **Better Alternatives:** + + **Option 1 - Event BEFORE the loop (modify filters/setup):** + ```al + // ✅ BETTER - Event fires ONCE before loop starts + OnBeforeProcessSalesLines(SalesHeader, SalesLine); + if SalesLine.FindSet() then + repeat + ProcessLine(SalesLine); + until SalesLine.Next() = 0; + ``` + + **Option 2 - Event AFTER the loop (post-processing):** + ```al + // ✅ BETTER - Event fires ONCE after all lines processed + if SalesLine.FindSet() then + repeat + ProcessLine(SalesLine); + until SalesLine.Next() = 0; + OnAfterProcessSalesLines(SalesHeader, TotalAmount); + ``` + + **When Events in Loops May Be Acceptable:** + - Small, guaranteed dataset (e.g., max 5-10 iterations) + - Subscribers are expected to perform only lightweight operations + - No alternative location achieves the same extensibility goal + - Author explicitly justifies the need + + **Required Justification from Author:** + 1. Why must the event fire on each iteration? + 2. What operations will subscribers perform? + 3. Have you considered alternatives (before/after loop)? diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/general_blockers.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/general_blockers.yaml new file mode 100644 index 000000000..d5e244925 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/general_blockers.yaml @@ -0,0 +1,227 @@ +general_blockers: + description: "Blocker rules that apply to ALL request types - auto-reject scenarios" + + rules: + - id: obsolete_code + applies_to: ["all"] + severity: "blocking" + description: "Target code marked as obsolete or inside NOT CLEAN region" + check_patterns: ["[Obsolete", "ObsoleteState", "ObsoleteReason", "NOT CLEAN"] + action: "auto_reject" + rejection_reason: "The target code is marked as obsolete and cannot be modified" + guidance: | + Code marked with `[Obsolete]` attribute or inside `// NOT CLEAN` regions is deprecated + and should not be modified. Such code is scheduled for removal or replacement. + + **Example 1 - Obsolete procedure:** + ```al + [Obsolete('Use GetCustomerBalance instead', '25.0')] + procedure GetBalance(CustomerNo: Code[20]): Decimal + begin + // This procedure is obsolete - do not add events here + exit(Customer.GetBalanceLCY()); + end; + ``` + + **Example 2 - NOT CLEAN region:** + ```al + #if not CLEAN28 + local procedure LegacyCalculation(var Amount: Decimal) + begin + // This procedure is obsolete - do not add events here + Amount := Amount * 1.25; + end; + #endif + ``` + + - id: public_signature_change + applies_to: ["all"] + severity: "blocking" + description: "Requires public procedure signature change" + action: "auto_reject" + rejection_reason: "Implementation would require changing a public procedure signature, which may break existing code" + exceptions: "Adding name to existing exit parameter to public procedures is allowed" + guidance: | + Changing the signature of public procedures is not allowed as it may break existing code. + Extensions and other dependent code rely on the exact signature of public procedures. + + **What is NOT Allowed:** + - Adding new parameters + - Removing existing parameters + - Changing parameter types + - Changing parameter order + - Changing return type + - Adding/removing `var` modifier on parameters + + **What IS Allowed:** + - Adding a name to an unnamed return value (non-breaking change) + + **Example 1 - NOT Allowed (adding parameter):** + ```al + // Before: + procedure PostSalesDocument(var SalesHeader: Record "Sales Header") + begin + // ... + end; + + // ❌ NOT Allowed - adding new parameter breaks existing callers: + procedure PostSalesDocument(var SalesHeader: Record "Sales Header"; Preview: Boolean) + begin + // ... + end; + ``` + + **Example 2 - NOT Allowed (changing parameter type):** + ```al + // Before: + procedure GetCustomerName(CustomerNo: Code[20]): Text[100] + begin + // ... + end; + + // ❌ NOT Allowed - changing Code[20] to Code[50] breaks existing callers: + procedure GetCustomerName(CustomerNo: Code[50]): Text[100] + begin + // ... + end; + ``` + + **Example 3 - Allowed (naming return value):** + ```al + // Before: + procedure IsInvoiceDocType(): Boolean + begin + exit(true); + end; + + // ✅ Allowed - adding name to return value is non-breaking: + procedure IsInvoiceDocType() Result: Boolean + begin + Result := true; + end; + ``` + + - id: sensitive_data_exposure + applies_to: ["all"] + severity: "blocking" + description: "Implementation would expose sensitive or private data" + action: "auto_reject" + rejection_reason: "The implementation would expose sensitive/private data that should not be made accessible" + guidance: | + This rule applies to any request that would result in exposing sensitive data: + - Making a procedure public that accesses/handles sensitive data + - Adding event parameters that expose private/sensitive information + - Changing access levels on variables containing sensitive data + + **Examples of Sensitive Data:** + - DotNet components + - SecretText variables + - Personal identifiable information (PII) + - Financial credentials or tokens + - Password hashes or authentication data + - Internal system configuration + - Audit trail data that should remain protected + + **NOT Considered Sensitive (do not block):** + Standard business entity data for contacts, customers, and vendors is not sensitive and exposing it is acceptable. This includes: + - Names, addresses, phone numbers + - VAT registration numbers + - Email addresses and other contact information + - Other standard fields on Customer, Vendor, and Contact records + + - id: multi_change_policy + applies_to: ["all"] + severity: "blocking" + description: "All-or-nothing rule for requests with multiple changes" + rejection_reason: "If ANY proposed change is blocked or infeasible, REJECT ALL changes in the request" + guidance: | + When a request contains multiple changes (e.g., multiple events, multiple procedure modifications), + all changes must be feasible for the request to be approved. If any single change is blocked, + the entire request must be rejected. + + **Rationale:** + - Multiple changes in a single request are often interdependent + - Partial implementation may leave the system in an inconsistent state + - The author expects all requested changes to be implemented together + - Prevents confusion about which parts were implemented + + **How to Detect Multi-Change Requests:** + - Request mentions multiple procedures/triggers + - Request asks for events in multiple locations + - Request includes numbered items (1, 2, 3...) or bullet points + - Request uses "and" to connect multiple actions + + **Example - Multi-Change Request:** + ``` + Please add the following events: + 1. OnBeforePost in Codeunit 80 "Sales-Post" + 2. OnAfterValidate in Table 36 "Sales Header" + 3. OnBeforeInsert in Table 37 "Sales Line" + ``` + + **If change #2 is blocked (e.g., obsolete code):** + - ❌ Do NOT implement changes #1 and #3 only + - ✅ Reject the entire request + - ✅ Explain which change is blocked and why + - ✅ Ask author to submit separate requests or modify the blocked change + + - id: potential_security_risk + applies_to: ["all"] + severity: "blocking" + description: "Potential exposure of sensitive data or security mechanisms based on keywords" + action: "include_human" + rejection_reason: "The requested change involves code handling sensitive security data. This requires manual security review." + guidance: | + **Security Risk Assessment:** + Procedures or events containing keywords related to security, authentication, or sensitive data require careful review. + + **Risky Keywords:** + - password, secret, token, key + - permission, user, login, auth + - credential, encryption + + **Validation Steps:** + 1. Check if the code exposes raw secrets or credentials. + 2. Check if the change allows bypassing security checks (e.g., permission validation). + 3. If the code is purely for logic (e.g., "ValidateUserStatus") and doesn't expose secrets, it MAY be safe. + 4. If in doubt, flag for manual review (apply 'agent-not-processable' label). + + - id: entity_not_found + applies_to: ["all"] + severity: "blocking" + description: "Target procedure/trigger/variable not found in expected location" + action: "request_clarification" + rejection_reason: "The specified procedure/trigger/variable was not found in the codebase" + exceptions: "In case of adding NEW code (e.g., an event) inside a trigger, it is acceptable that the trigger does not yet exist" + guidance: | + If the agent cannot locate the specified procedure, trigger, or variable in the expected + object or location, it must request clarification from the author. + + **Possible Reasons for Not Found:** + - Typographical errors in names + - Incorrect object type or ID specified + - The codebase has changed since the request was made + - The requested entity does not exist + + **Exception - Triggers That Don't Exist Yet:** + When the request is to add a new event inside a trigger (e.g., OnInsert, OnAfterGetRecord, OnValidate, OnOpenPage, etc.), + it is acceptable if the trigger does not currently exist in the object. The agent should: + - Create the trigger with the requested event inside it + - This is a valid implementation pattern in AL + + **Example - Acceptable (trigger doesn't exist):** + ```al + // Request: "Add OnBeforeInsert event in Table 18 Customer" + // Table 18 currently has no OnInsert trigger + + // ✅ Agent creates the trigger with the event: + trigger OnInsert() + begin + OnBeforeInsertCustomer(Rec); + end; + ``` + + **Agent Actions (when NOT an exception):** + 1. Apply `missing-info` label + 2. Do NOT apply team label or `agent-analyzed` + 3. Post a comment asking the author to verify the target location and names diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/request_for_external_implementation.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/request_for_external_implementation.yaml new file mode 100644 index 000000000..deb678dc3 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/codebase-rules/request_for_external_implementation.yaml @@ -0,0 +1,126 @@ +request_for_external_implementation: + description: "Implementation rules specific to request-for-external type - ensure alignment with coding standards" + + rules: + - id: procedure_access_level + applies_to: ["request-for-external"] + severity: "notice" + description: "Making procedure publically accessible" + action: "internal_warning" + guidance: | + **Implementation:** Making a procedure externally accessible implies: + - Removing `local` scope modifier (if present) or + - Removing `[Scope('OnPrem')]` attribute (if present) + + **Example 1 - Removing local scope:** + ```al + // Before: + local procedure CalculateDiscount(var SalesLine: Record "Sales Line") + begin + // Internal logic + end; + + // After: + procedure CalculateDiscount(var SalesLine: Record "Sales Line") + begin + // Now accessible externally + end; + ``` + + **Example 2 - Removing OnPrem scope:** + ```al + // Before: + [Scope('OnPrem')] + procedure ValidateCustomer(CustomerNo: Code[20]) + begin + // OnPrem only logic + end; + + // After: + procedure ValidateCustomer(CustomerNo: Code[20]) + begin + // Now accessible externally + end; + ``` + + - id: variable_access_level + applies_to: ["request-for-external"] + severity: "notice" + description: "Variable access level change from var to protected var" + action: "internal_warning" + guidance: | + When changing variable access level from 'var' to 'protected var', ensure proper code structure: + + **Structure Requirements:** + 1. If object currently has NO 'protected var' section, create it AFTER all 'var' declarations + 2. Place all protected var declarations in the dedicated 'protected var' section + 3. Proper order: var declarations → protected var section → procedure declarations + + **Example - Correct Structure:** + ```al + codeunit 50100 "Example Codeunit" + { + var + ExistingGlobalVar: Text[100]; + AnotherGlobalVar: Integer; + + protected var + NewlyProtectedVar1: Text[50]; + NewlyProtectedVar2: Boolean; + + procedure MyProcedure() + begin + // ... + end; + } + ``` + + **Common Mistake - Don't do this:** + ```al + // ❌ Wrong - mixing protected var with var + var + ExistingGlobalVar: Text[100]; + protected var + NewlyProtectedVar1: Text[50]; + var + AnotherGlobalVar: Integer; // Wrong location + ``` + + **Migration Steps:** + 1. Identify all variables that need to be protected + 2. Check if object already has 'protected var' section + 3. If no protected var section exists: Add 'protected var' section after all 'var' declarations + 4. Move/add variables to protected var section + 5. Ensure no var declarations appear after protected var section + + - id: local_to_protected_upgrade_to_public + applies_to: ["request-for-external"] + severity: "notice" + description: "Request to make local procedure protected should result in public scope instead" + action: "internal_warning" + guidance: | + When an author requests changing a `local` procedure to `protected`, the correct implementation + for external systems is to make it `public` (i.e., remove the `local` modifier entirely). + + **Implementation:** + - Remove the `local` keyword from the procedure declaration + - Do NOT add any `protected` modifier + + **Example:** + ```al + // Requested: make procedure "protected" + // Before: + local procedure CalculateTax(var Amount: Decimal) + begin + // logic + end; + + // ✅ Correct implementation - make it public, not protected: + procedure CalculateTax(var Amount: Decimal) + begin + // logic + end; + ``` + + **Note:** If the object is a base codeunit with an extension pair and `protected` is genuinely + intended for internal inheritance use, escalate for manual review rather than auto-implementing. diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/comment-templates/comment_templates.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/comment-templates/comment_templates.yaml new file mode 100644 index 000000000..a4204493d --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/comment-templates/comment_templates.yaml @@ -0,0 +1,517 @@ +comment_templates: + + # ============================================================================= + # APPROVAL COMMENTS (Success Path) + # ============================================================================= + # + # ⚠️ CRITICAL RULES FOR APPROVAL COMMENTS: + # 1. Follow the template EXACTLY - do not add extra sections + # 2. NEVER add "Analysis Summary", "Team Assignment", or similar metadata + # 3. NEVER add "Correction Required" or explain differences from author's proposal + # 4. NEVER add footer like "This issue has been analyzed by Argus Agent" + # 5. NEVER add "Thank you" messages or greetings + # 6. Show ONLY what will be implemented - no explanations of WHY + # 7. If request passed validation, just show the final implementation code + # ============================================================================= + + approved_event_request: + description: "Posted when event-request issue passes analysis and is ready for further processing" + applies_to: ["event-request"] + template: | + ✅ **Analysis complete - ready for further processing** + + --- + + **Implementation:** + + {implementation_description} + + {code_location_description} + + ```al + {code_with_event_call} + ``` + + **Event Publisher (add at the end of the codeunit with other events):** + + ```al + {event_publisher_code} + ``` + + --- + + **File:** `{filename_only}` + + placeholders: + implementation_description: + description: "Brief description of what needs to be added/changed" + example: "Add a new integration event in the `Code()` procedure of Codeunit 5880 \"Phys. Invt. Order-Finish\" before `TempInvtOrderTrackingBuffer.Modify()` in the loop." + + code_location_description: + description: "Description of where in the code the change goes" + example: "**Location in `Code()` procedure (around line 149):**" + + code_with_event_call: + description: "Code snippet showing event call in context" + example: | + TempInvtOrderTrackingBuffer.Reset(); + if TempInvtOrderTrackingBuffer.Find('-') then + repeat + TempInvtOrderTrackingBuffer."Qty. To Transfer" := + TempInvtOrderTrackingBuffer."Qty. Recorded (Base)" - TempInvtOrderTrackingBuffer."Qty. Expected (Base)"; + OnCodeOnBeforeModifyInvtOrderTracking(TempInvtOrderTrackingBuffer); // <-- New event + TempInvtOrderTrackingBuffer.Modify(); + until TempInvtOrderTrackingBuffer.Next() = 0; + + event_publisher_code: + description: "The event publisher procedure to add" + example: | + [IntegrationEvent(false, false)] + local procedure OnCodeOnBeforeModifyInvtOrderTracking(var TempInvtOrderTracking: Record "Invt. Order Tracking" temporary) + begin + end; + + filename_only: + description: "Just the filename without full path (e.g., PhysInvtOrderFinish.Codeunit.al)" + format: "{ObjectName}.{ObjectType}.al" + example: "PhysInvtOrderFinish.Codeunit.al" + + approved_request_for_external: + description: "Posted when request-for-external issue passes analysis and is ready for further processing" + applies_to: ["request-for-external"] + template: | + ✅ **Analysis complete - ready for further processing** + + --- + + **Implementation:** + + {implementation_description} + + **Current code:** + + ```al + {current_code} + ``` + + **Proposed change:** + + ```al + {proposed_code} + ``` + + --- + + **File:** `{filename_only}` + + approved_enum_request: + description: "Posted when enum-request issue passes analysis and is ready for further processing" + applies_to: ["enum-request"] + template: | + ✅ **Analysis complete - ready for further processing** + + --- + + **Implementation:** + + {implementation_description} + + ```al + {enum_code} + ``` + + --- + + **File:** `{filename_only}` + + # ============================================================================= + # ALREADY IMPLEMENTED COMMENTS + # ============================================================================= + + already_implemented: + description: "Posted when ALL requested changes already exist in the codebase (100% match)" + applies_to: ["all"] + template: | + ✅ **Good news! This is already implemented in the codebase.** + + The requested change already exists: + + **Existing implementation:** + + ```al + {existing_code} + ``` + + **File:** `{filename_only}` + + You can use this existing {implementation_type} in your extension. Closing this issue as the request is already satisfied. + + placeholders: + existing_code: + description: "The existing code that matches the request" + example: | + [IntegrationEvent(false, false)] + local procedure OnAfterConfirmPost(var PurchaseHeader: Record "Purchase Header"; var IsHandled: Boolean) + begin + end; + + filename_only: + description: "Just the filename without full path" + example: "PurchPostPrint.Codeunit.al" + + implementation_type: + description: "Type of implementation (event, procedure, enum value, etc.)" + examples: + - "event" + - "procedure" + - "enum value" + + partial_already_implemented: + description: "Posted when some requests already exist and others are ready for further processing" + applies_to: ["all"] + template: | + ✅ **Analysis complete - partial implementation already exists** + + --- + + **Already Implemented (no changes needed):** + + {already_implemented_section} + + --- + + **Ready for Further Processing:** + + {pending_implementation_section} + + --- + + **Note:** This issue will remain open for the pending implementation(s). The existing code above is already available for use in your extension. + + placeholders: + already_implemented_section: + description: "Section showing all changes that already exist" + format: | + For each already_implemented_change: + + **{n}. {description}** + + ```al + {existing_code} + ``` + + **File:** `{filename_only}` + example: | + **1. Add var to PurchaseHeader parameter** + + ```al + [IntegrationEvent(false, false)] + local procedure OnAfterConfirmPost(var PurchaseHeader: Record "Purchase Header"; var IsHandled: Boolean) + begin + end; + ``` + + **File:** `PurchPostPrint.Codeunit.al` + + pending_implementation_section: + description: "Section showing all changes ready for further processing (follows standard approval format)" + format: | + For each pending_change: + + **{n}. {implementation_description}** + + {code_location_description} + + ```al + {code_with_event_call} + ``` + + **Event Publisher:** + + ```al + {event_publisher_code} + ``` + + **File:** `{filename_only}` + + # ============================================================================= + # MISSING INFORMATION COMMENTS + # ============================================================================= + + missing_info_requirements: + description: "Posted when issue fails requirements validation" + applies_to: ["all"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. To help us process it efficiently, please update the issue to include the required information. + + 📚 [Extensibility Guidelines](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-contribute-extensibility) + + **Missing Information:** + + {missing_items_list} + + Please update the issue with the missing details. We'll automatically re-analyze once updated. + + placeholders: + author_name: + description: "GitHub username of issue author" + missing_items_list: + description: "Bullet list of missing requirements" + format: "- **{requirement_name}**: {explanation}" + + + missing_info_procedure_not_found: + description: "Posted when target procedure/trigger not found in codebase" + applies_to: ["event-request"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. + + We couldn't locate the specified procedure/trigger in the codebase. + + **Target:** `{target_procedure}` in `{target_object}` + + {suggestion_section} + + Please verify the procedure/trigger name and update the issue. We'll automatically re-analyze once updated. + + placeholders: + target_procedure: + description: "The procedure name that wasn't found" + target_object: + description: "The object where procedure was expected" + suggestion_section: + description: "Fuzzy match suggestions if available" + format: | + **Did you mean one of these?** + {fuzzy_matches} + + missing_info_multiple_types: + description: "Posted when issue contains multiple distinct request types" + applies_to: ["all"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. + + This issue contains multiple distinct request types: + + {detected_types_list} + + Please create separate issues for each type to ensure proper processing and tracking. + + We'll automatically process each issue once they are created separately. + + # ============================================================================= + # ALTERNATIVE SUGGESTION COMMENTS + # ============================================================================= + + suggest_alternative_event: + description: "Posted when similar event already exists" + applies_to: ["event-request"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. + + A similar event already exists in this location that may serve your purpose: + + **Existing event:** `{existing_event_name}` + + ```al + {existing_event_signature} + ``` + + **Location:** `{filename_only}` + + Would this existing event work for your scenario? If not, please explain why a new event is needed. + + Please respond with confirmation or additional details. We'll re-analyze once you respond. + + suggest_alternative_approach: + description: "Posted when alternative approach is recommended" + applies_to: ["event-request"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. + + {alternative_explanation} + + **Recommended approach:** + + {recommended_approach} + + Please confirm if this alternative approach works for your scenario, or explain why the original approach is required. + + suggest_alternative_location: + description: "Posted when agent finds the solution should be in a different location than requested" + applies_to: ["event-request", "enum-request", "request-for-external"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. After analyzing the codebase, I found that the functionality you're looking to extend is actually located in a different place than specified. + + **Your request:** {original_request_summary} + + **What I found:** {analysis_finding} + + **Suggested alternative:** + + {suggested_implementation} + + ```al + {suggested_code} + ``` + + **File:** `{filename_only}` + + {manual_binding_note} + + Please confirm if this alternative approach works for your scenario, + or let us know if you specifically need the change as originally requested and why. + + placeholders: + original_request_summary: + description: "Brief summary of what the author originally requested" + example: "Add OnAfterIsCancellationSupported event in TestPurchaseLines procedure of Codeunit 1313" + analysis_finding: + description: "Explanation of what the codebase analysis revealed" + example: "The `IsCancellationSupported()` logic is actually implemented in Table 123 \"Purch. Inv. Line\", which is called from `TestPurchaseLines`." + suggested_implementation: + description: "Description of the recommended implementation" + example: "Add the event in Table 123 \"Purch. Inv. Line\" in the `IsCancellationSupported()` procedure" + suggested_code: + description: "Code showing the suggested implementation" + filename_only: + description: "Filename where the suggested implementation would go" + manual_binding_note: + description: "Optional note about manual binding for public procedures" + format: | + 📝 **Note:** This event is in a public procedure that may be called from multiple places. If you need to control when your event subscriber fires, consider using [Manual Event Subscription Binding](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/properties/devenv-eventsubscriberinstance-property#manual-binding). + when_to_include: "Include when target procedure is public (not local or internal)" + + # ============================================================================= + # REJECTION COMMENT + # ============================================================================= + + rejected_request: + description: "Posted when request is rejected due to blocking rules" + applies_to: ["all"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. Unfortunately, this request cannot be implemented. + + **Reason:** {rejection_reason} + + placeholders: + rejection_reason: + description: "Brief explanation of why the request was rejected" + examples: + - "The target code is marked as obsolete and cannot be modified" + - "Implementation would require changing a public procedure signature, which may break existing code" + - "The implementation would expose sensitive/private data that should not be made accessible" + + # ============================================================================= + # STALE ISSUE COMMENT + # ============================================================================= + + stale_issue_closure: + description: "Posted when closing stale issue (30+ days with missing-info)" + applies_to: ["all"] + template: | + No recent activity, so this issue will now be closed. + If you still need this request, please create a new issue and include the required updated information. + + # ============================================================================= + # BCAPPS REDIRECT COMMENT + # ============================================================================= + + bcapps: + description: "Posted when requested code lives in the BCApps contribution repo" + applies_to: ["all"] + template: | + Hi @{author_name}, + + Thank you for your extensibility request. + + The code you'd like to change is part of the **BCApps** repository. To have this change made, it needs to go through a contribution. + Please refer to the contribution guide for instructions on how to proceed: https://github.com/microsoft/BCApps/blob/main/CONTRIBUTING.md + +# ============================================================================= +# FORMATTING RULES +# ============================================================================= + +formatting_rules: + + filename_format: + description: "How to format file references in comments" + rule: "Use only the filename, not the full path" + format: "{ObjectName}.{ObjectType}.al" + examples: + correct: "PhysInvtOrderFinish.Codeunit.al" + incorrect: "App\\Layers\\W1\\BaseApp\\Inventory\\Counting\\Document\\PhysInvtOrderFinish.Codeunit.al" + + code_blocks: + description: "How to format code in comments" + language: "al" + format: | + ```al + {code} + ``` + + headers: + description: "Use bold markdown for section headers" + format: "**{header_text}:**" + + status_icons: + approved: "✅" + missing_info: "⚠️" + blocked: "❌" + stale: "⏱️" + reprocessing: "🔄" + + manual_binding_note: + description: "When to include manual binding guidance in comments" + rule: "Include this note when adding events to procedures that are heavily used across the codebase for different scenarios" + format: | + 📝 **Note:** This procedure is called from multiple places in the codebase for different scenarios. If you need your event subscriber to fire only in specific scenarios, consider using [Manual Event Subscription Binding](https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/properties/devenv-eventsubscriberinstance-property#manual-binding). + when_to_include: + - "Procedure is called from multiple places in the codebase" + - "Procedure serves different business scenarios (e.g., used by both Sales and Purchase flows)" + - "Procedure is a utility/helper function used broadly" + - "Public procedures that can be called from extensions" + - "The event doesn't have inherent filtering (e.g., specific document type parameter)" + when_to_exclude: + - "Procedure has very limited/specific usage (only called from one logical flow)" + - "Event parameters naturally filter the scenario (e.g., specific document type parameter)" + - "Event is in a page/report trigger where context is clear" + - "Procedure is only relevant to one business process" + +# ============================================================================= +# EXCLUDED ELEMENTS +# ============================================================================= + +excluded_from_comments: + description: "Elements that should NOT be included in approval comments" + items: + - "Thank you for your extensibility request. The requested event is feasible and has been analyzed." + - "Team: {team_name}" + - "Full file paths (use filename only)" + - "Internal implementation rules or guidelines (e.g., 'new parameters must be added at the end')" + - "Notes about BC extensibility guidelines or best practices" + - "Any rule explanations - rules should be applied silently, not explained in comments" + - "Suggestions for additional changes outside the scope of the request (e.g., 'the same change could be applied to other similar objects')" + - "Recommendations to extend the request to other files, objects, or pages not explicitly mentioned by the author" + - "Explanations of where files were found or not found (e.g., 'I searched in X location', 'I could not find the file in Y', 'the file was located at Z')" + + rationale: + thank_you_message: "For approval comments only: redundant because the approval header is sufficient" + team_assignment: "Team is indicated by label, not needed in comment" + full_paths: "Clutters the comment - filename is sufficient for identification" + internal_rules: "Implementation rules are for agent behavior, not user-facing documentation. Apply rules silently without explaining them in comments." + out_of_scope_suggestions: "Agent must only address exactly what is requested. Never suggest additional changes to other objects, files, or pages that were not explicitly asked for by the author. The goal is to satisfy the request as stated, not to expand its scope." diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/enum_request_requirements.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/enum_request_requirements.yaml new file mode 100644 index 000000000..7a4fc21c5 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/enum_request_requirements.yaml @@ -0,0 +1,51 @@ +# Enum Request Specific Requirements Configuration +# Defines ADDITIONAL requirements specific to enum-request type issues +# NOTE: General requirements (title, description, meaningful justification) are defined in ea_config_general_requirements.yaml +# Version: 1.0 + +enum_request: + enabled: true + description: "Requests to add new enum values or create new enums - ADDITIONAL requirements beyond general ones" + detection_keywords: ["enum", "option", "option value", "enumextension"] + max_iterations: 3 + + subtypes: + new_enum: + enabled: true + description: "Request to create entirely new enum" + + additional_requirements: + - id: enum_definition + name: "Enum Definition" + description: "Complete enum definition with all proposed values and captions" + mandatory: true + validation_hints: ["enum", "definition", "values", "caption", "ordinal"] + + - id: usage_context + name: "Usage Context" + description: "Where and how the enum will be used (tables, pages, reports)" + mandatory: true + validation_hints: ["usage", "context", "table", "field", "page"] + + extend_enum: + enabled: true + description: "Request to add values to existing enum" + + additional_requirements: + - id: target_enum + name: "Target Enum" + description: "Exact enum name/ID and proposed new values with captions" + mandatory: true + validation_hints: ["enum", "name", "id", "value", "caption"] + + - id: compatibility_check + name: "Compatibility Check" + description: "Confirmation that new values won't break existing functionality" + mandatory: true + validation_hints: ["compatibility", "existing", "functionality", "break", "impact"] + +# Validation Messages +validation_messages: + insufficient_detail: "The provided information is insufficient for processing. Please add:" + #requirement_reference: "For more details, see our Extensible Enums documentation: {link}" + resubmit_after_update: "Please update the issue with the missing details. We'll automatically re-analyze once updated." diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/event_request_requirements.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/event_request_requirements.yaml new file mode 100644 index 000000000..7041bc796 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/event_request_requirements.yaml @@ -0,0 +1,231 @@ +# Event Request Specific Requirements Configuration +# Defines ADDITIONAL requirements specific to event-request type issues +# NOTE: General requirements (title, description, meaningful justification) are defined in ea_config_general_requirements.yaml +# Version: 1.2 + +event_request: + enabled: true + description: "Event publisher/subscriber requests - ADDITIONAL requirements beyond general ones" + detection_keywords: ["publisher", "subscriber", "IntegrationEvent", "event", "OnBefore", "OnAfter"] + + # ⚠️ CRITICAL RULE: Agent must NEVER suggest IsHandled unless EXPLICITLY requested + ishandled_detection_rule: + description: "IsHandled sub-type is ONLY detected when author EXPLICITLY requests it or there is a clear intent to bypass/skip existing logic using the event, regardless of whether the specific keyword is used" + explicit_keywords_required: ["IsHandled", "bypass", "skip", "prevent execution", "handled parameter"] + behavior: | + - If the intent is to bypass/skip existing logic using the event, regardless of whether the specific keyword is used → classify as "IsHandled" sub-type + - Agent must NEVER infer or suggest IsHandled on its own + - Regular events do NOT include IsHandled parameter + - This prevents escalating request scope beyond what author asked + + # Common requirements for ALL event request subtypes + common_requirements: + - id: proposed_code_change + name: "Proposed Code Change" + description: "Exact code addition/modification with surrounding context (5-10 lines)" + mandatory: true + exceptions: + - condition: "Adding parameter to existing event" + description: "When request is to add a parameter to an existing event" + mandatory: false + requirements: + - "Clearly specify parameter name" + - "Identify target event name" + - "Specify exact location (codeunit/procedure name and number)" + - "Optional: Specify parameter type if not obvious from context" + example: "Add parameter 'ServShipmentNo' to existing event 'OnAfterFinalizePostingOnBeforeCommit' in procedure PostWithLines of Codeunit 5980 'Service-Post'" + rationale: "For parameter additions, the implementation is straightforward - add the parameter to both the event declaration and invocation. Explicit code is helpful but not mandatory if the request is clear." + validation_hints: ["code", "procedure", "trigger", "event", "publisher", "context"] + #documentation_link: "{link}" + + subtypes: + ishandled: + enabled: true + description: "IsHandled event requests with bypass functionality - ONLY when EXPLICITLY requested" + detection_patterns: ["IsHandled", "is handled", "Handled", "bypass", "skip", "prevent execution"] + detection_rule: "MUST be EXPLICITLY requested by author - agent cannot suggest or infer" + max_iterations: 5 + + additional_requirements: + - id: invocation_example + name: "Invocation Example (recommended)" + description: "How event will be raised and sample subscriber implementation" + mandatory: false + validation_hints: ["invoke", "subscriber", "example", "sample", "usage"] + + - id: alternatives_evaluated + name: "Alternatives Evaluated" + description: "Which existing events/patterns were tried and why insufficient" + mandatory: true + validation_hints: ["alternative", "tried", "existing", "evaluated", "why not"] + + - id: justification_for_ishandled + name: "Justification for IsHandled" + description: "Why standard event/redesign/contribution isn't feasible" + mandatory: true + validation_hints: ["justify", "why", "ishandled", "necessary", "bypass"] + quality_standards: + reject_generic_statements: true + unacceptable_patterns: + - "we need to bypass" + - "customers requested" + - "important for business" + - "flexibility required" + requires_specifics: + - "Specific technical reason why standard event insufficient" + - "Concrete business logic that must be skipped/replaced" + - "Exact scenario where bypass is essential" + - "Why alternative approaches won't work" + good_example: "We must bypass the standard credit limit check because our vertical industry (healthcare) requires real-time insurance pre-authorization instead. The standard check would block orders that are actually covered by insurance, but there's no way to inject our authorization logic before the credit check fails." + bad_example: "We need IsHandled to implement our custom logic." + + - id: performance_considerations + name: "Performance Considerations" + description: "Expected execution frequency and potential impact assessment" + mandatory: true + validation_hints: ["performance", "frequency", "impact", "execution", "load"] + flexible_validation: true + validation_notes: | + A brief answer is sufficient for this requirement. + + The author should acknowledge expected frequency or impact, but the agent should not require a detailed performance analysis unless the request suggests a real performance risk. + + Examples of sufficient answers: + - "Runs only during posting, so impact should be minimal." + - "Called often, but subscriber logic will be lightweight." + + - id: data_sensitivity_review + name: "Data Sensitivity Review" + description: "Confirmation of no sensitive data exposure or rationale if necessary" + mandatory: true + validation_hints: ["sensitive", "data", "security", "exposure", "private"] + flexible_validation: true + validation_notes: | + A short confirmation is sufficient for this requirement. + + The author should state whether sensitive data is involved, but the agent should not ask for extra detail unless the request appears to expose confidential, personal, or security-relevant information. + + Examples of sufficient answers: + - "No sensitive data involved." + - "Only standard document fields are exposed." + + - id: multi_extension_interaction + name: "Multi-Extension Interaction" + description: "Risks if multiple extensions subscribe and conflict mitigation" + mandatory: true + validation_hints: ["multi", "extension", "conflict", "subscribe", "risk"] + flexible_validation: true + validation_notes: | + A brief acknowledgement is sufficient for this requirement. + + The author should mention whether they see a conflict risk, but the agent should not be strict here unless the requested behavior clearly changes control flow or could create extension ordering conflicts. + + Examples of sufficient answers: + - "No major conflict expected; subscriber will only add logic." + - "Potential overlap is acceptable because this only reads available values." + + - id: no_ishandled_on_existing_events + name: "Adding IsHandled to Existing Events" + description: | + Adding an IsHandled pattern to an existing event is NOT allowed. + This fundamentally changes the purpose of the event. + Any request asking to introduce IsHandled into an already-published event must be rejected. + action: reject + detection_patterns: ["add IsHandled to existing", "IsHandled to current event", "modify event to include IsHandled", "extend event with IsHandled", "introduce IsHandled"] + rejection_message: | + This request cannot be approved. Adding an IsHandled pattern to an existing event is not allowed + because it fundamentally changes the event's purpose and contract. Existing subscribers rely on + the current behavior, and introducing IsHandled would break them by allowing the base logic to + be bypassed — something the original subscribers were never designed to account for. + + If you need bypass/handled functionality, please request a NEW separate event with IsHandled + instead of modifying the existing one. + + - id: reject_ishandled_only_for_locktable + name: "Reject IsHandled Used Only to Skip LockTable" + description: "If an IsHandled event is requested solely to skip a LockTable call and nothing else, the request must be rejected. Skipping only LockTable via IsHandled undermines data integrity and concurrency control without providing meaningful extensibility value." + mandatory: true + validation_hints: ["IsHandled", "LockTable", "skip", "concurrency", "data integrity"] + quality_standards: + reject_when_only_locktable: true + rationale: | + - LockTable ensures data consistency during concurrent operations + - Skipping LockTable alone via IsHandled creates race conditions and data corruption risks + - IsHandled is intended for replacing or extending business logic, not for bypassing infrastructure-level safeguards + - If the goal is to skip LockTable, the request lacks meaningful extensibility purpose + detection_criteria: + - "The request describes using IsHandled to skip only a LockTable call" + - "The code block protected by IsHandled contains only LockTable and no other logic" + - "The justification mentions only LockTable skipping without broader business logic replacement" + unacceptable_patterns: + - "We need IsHandled to skip LockTable" + - "We want to avoid the LockTable call" + - "IsHandled to bypass LockTable for performance" + - "We need to skip the lock on the table" + bad_examples: + - description: "IsHandled event that only skips LockTable" + code: | + IsHandled := false; + OnBeforePostSalesInvoice(SalesHeader, IsHandled); + if not IsHandled then + SalesHeader.LockTable(); + reason: "The IsHandled event only gates a LockTable call - no business logic is being replaced or extended" + good_examples: + - description: "IsHandled event that skips a meaningful block of logic including LockTable" + code: | + IsHandled := false; + OnBeforePostSalesInvoice(SalesHeader, IsHandled); + if not IsHandled then begin + SalesHeader.LockTable(); + ValidatePostingDate(SalesHeader); + CheckCustomerCreditLimit(SalesHeader); + end; + reason: "The IsHandled event gates LockTable along with other business logic - this is a valid extensibility scenario" + action: "auto_reject" + polite_message_template: | + Thank you for your request! + + After reviewing your submission, we noticed that the proposed IsHandled event is intended to skip only a LockTable call without replacing or extending any business logic. + + LockTable is a critical infrastructure mechanism that ensures data consistency during concurrent operations. Skipping it alone via IsHandled does not provide meaningful extensibility value and introduces risks such as race conditions and data corruption. + + If you need to replace or extend the business logic surrounding the LockTable call, please resubmit your request with a broader scope that includes the relevant business logic you wish to customize. + + Thank you for your understanding! + + regular: + enabled: true + description: "Regular event requests without IsHandled" + max_iterations: 3 + + additional_requirements: + - id: use_case_example + name: "Use Case Example" + description: "How event will be used and business scenario" + mandatory: false + validation_hints: ["use case", "example", "scenario", "business", "how"] + + - id: meaningful_justification_for_parameter_addition + name: "Meaningful Justification (Relaxed for Parameter Additions)" + description: "For adding parameters to existing events, justification requirements are relaxed" + applies_to: "parameter_addition_to_existing_event" + mandatory: false + rationale: | + When adding a parameter to an existing event, the justification is inherently clear: + - The user needs access to data that exists in scope + - The data is not currently exposed through the event + - This is a straightforward extensibility enhancement + + The requirement "I need parameter X" is sufficient UNLESS there are specific concerns: + - Security/sensitive data exposure + - Performance implications + - Breaking changes + + Agent should only request additional justification if one of these concerns applies. + validation_approach: "Evaluate for potential issues, not just completeness of justification" + +# Requirement Validation Messages +validation_messages: + insufficient_detail: "The provided information is insufficient for processing. Please add:" + requirement_reference: "For more details, see our Extensibility Guidelines: {link}" + resubmit_after_update: "Please update the issue with the missing details. We'll automatically re-analyze once updated." diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/extensibility_enhancement_requirements.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/extensibility_enhancement_requirements.yaml new file mode 100644 index 000000000..fa757b553 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/extensibility_enhancement_requirements.yaml @@ -0,0 +1,37 @@ +# Extensibility Enhancement Specific Requirements Configuration +# Defines ADDITIONAL requirements specific to extensibility-enhancement type issues +# NOTE: General requirements (title, description, meaningful justification) are defined in ea_config_general_requirements.yaml +# Version: 1.0 + +extensibility_enhancement: + enabled: true + description: "General extensibility improvements and enhancements - ADDITIONAL requirements beyond general ones" + detection_keywords: ["extensibility", "enhancement", "improvement", "access", "protected", "variable"] + max_iterations: 3 + + additional_requirements: + - id: proposed_enhancement + name: "Proposed Enhancement" + description: "Specific enhancement with exact code changes or design modifications" + mandatory: true + validation_hints: ["enhancement", "code", "change", "design", "modification"] + documentation_link: "https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-extensibility-patterns" + + - id: technical_justification + name: "Technical Justification" + description: "Technical reasoning why the enhancement is necessary and beneficial" + mandatory: true + validation_hints: ["technical", "reasoning", "necessary", "beneficial", "why"] + quality_standards: + reject_generic_statements: true + requires_specifics: + - "Technical limitation of current approach" + - "Performance or architectural benefits" + - "Specific scenarios where current design fails" + - "Impact on extension development patterns" + +# Validation Messages +validation_messages: + insufficient_detail: "The provided information is insufficient for processing. Please add:" + #requirement_reference: "For more details, see our Extensibility Guidelines: {link}" + resubmit_after_update: "Please update the issue with the missing details. We'll automatically re-analyze once updated." diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/general_requirements.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/general_requirements.yaml new file mode 100644 index 000000000..1a039f2aa --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/general_requirements.yaml @@ -0,0 +1,149 @@ +# General Requirements Configuration +# Defines common requirements that apply to ALL request types +# Version: 1.0 + +general_requirements: + description: "Common requirements that must be met by all extensibility requests" + + mandatory_requirements: + - id: clear_title + name: "Clear Title" + description: "Issue title must clearly describe the request type and target" + mandatory: true + validation_hints: ["title", "clear", "descriptive", "specific"] + quality_standards: + reject_generic_statements: true + unacceptable_patterns: + - "Need help" + - "Extension issue" + - "Problem with" + - "Question about" + - "Request" + - "Enhancement needed" + requires_specifics: + - "Include request type (Event, Public Access, Enum, etc.)" + - "Mention specific object/procedure name" + - "Be descriptive and actionable" + good_examples: + - "Add OnBeforePostSalesInvoice event to Sales Invoice posting" + - "Change ValidateCustomerData procedure from local to public in Codeunit 123" + - "Add Electronic Signature option to Document Approval Type enum" + bad_examples: + - "Need event" + - "Extension request" + - "Problem with sales" + + - id: clear_description + name: "Clear Description" + description: "Issue description must provide comprehensive context and details" + mandatory: true + validation_hints: ["description", "context", "details", "comprehensive"] + quality_standards: + minimum_elements: + - "What you're trying to accomplish" + - "Current limitation or problem" + - "Proposed solution" + - "Business/technical justification" + good_structure: | + **Problem:** [What can't be done today] + **Proposed Solution:** [Exact change requested] + **Justification:** [Why this is needed - see meaningful justification standards] + **Implementation:** [Specific code/design changes] + + - id: meaningful_justification + name: "Meaningful Justification" + description: "All requests must include meaningful business/technical justification with specific scenarios" + mandatory: true + validation_hints: ["justification", "business", "scenario", "specific", "meaningful"] + quality_standards: + reject_generic_statements: true + unacceptable_patterns: + - "We need this for our extension" + - "This is important for us" + - "It has been requested by our customers" + - "We need it for customization" + - "This would be useful" + - "Customers are asking for this" + - "We need better extensibility" + - "Required for our business" + - "Essential for our solution" + - "Standard requirement" + requires_specifics: + - "Technical intent or specific business scenario" + - "What functionality is needed (e.g., filter data, validate records, access information)" + - "Enough context to understand the extensibility gap being addressed" + validation_criteria: + - "Should include concrete business scenario OR technical intent" + - "Should explain current technical limitation OR desired functionality" + - "Accept statements showing clear technical need (e.g., 'set additional filters', 'validate before X', 'access Y data')" + - "Reject only completely generic statements with no technical context" + good_examples_by_type: + event_request: "We need to validate custom tax rules before invoice posting. Currently, there's no way to intercept the posting process before tax calculation occurs, which causes incorrect tax amounts when country-specific rules differ from standard BC logic." + request_for_external: "Our extension needs to call ValidateCustomerData() from external codeunit. This procedure is currently local but contains shared validation logic that multiple extensions require. Making it public would eliminate code duplication across 3 different customer-specific extensions." + request_for_external_simple: "I need this to avoid making duplicate code, as I need to check the same scenario." + enum_request: "We need to add 'Electronic Signature' option to Document Approval Type enum. Our compliance extension must track electronic signatures as a distinct approval method, but current enum values don't cover this regulatory requirement in our industry." + extensibility_enhancement: "The Purchase Header table's OnValidate trigger for Vendor No. field doesn't expose the vendor record to extensions. We need access to vendor data during validation to implement vendor-specific business rules without re-querying the database." + bug: "When posting a sales invoice with multiple lines containing items that have different VAT rates, the VAT calculation is incorrect. Expected: Each line should calculate VAT based on its item's VAT rate. Actual: All lines use the VAT rate from the first line." + + # Special case: Request-for-external with code duplication avoidance + request_for_external_simplified_acceptance: + description: "For request-for-external, simpler justification is acceptable when procedure is safe" + acceptable_simple_justifications: + - "I need this to avoid making duplicate code" + - "I need to reuse this logic in my extension" + - "I need to call the same check/validation" + - "To avoid copying this code to my extension" + condition: "Agent must verify procedure is NOT risky (no sensitive data, no security implications, no XPIA risk)" + rationale: "Code duplication avoidance is a valid technical reason - the intent is clear and the benefit is obvious" + + bad_examples: + - "We need this event to implement customization for customer" + - "This is important for our business requirements" + - "Customers have requested this functionality" + - "We need this to be public so we can call it" + - "This enum needs more options for our extension" + + - id: no_images_use_code_snippets + name: "No Images - Use Code Snippets" + description: "Images may be included as supplemental context, but the request must be understandable from text and all code must be provided as text using code snippets" + mandatory: true + validation_hints: ["image", "screenshot", "picture", "code snippet", "text"] + quality_standards: + reject_images_without_text_equivalent: true + rationale: | + - Images cannot be searched or indexed + - Code snippets allow for proper syntax highlighting and copying + - Text-based code is accessible and easier to work with + - Images may be kept as supplemental context when the request is already clear from the text submission + - Any attached images should be ignored for analysis when equivalent text/code is already provided + acceptance_criteria: + - "Accept when the request can be understood from the written description and any required AL code is provided as text snippets" + - "Accept when images are included only as supplemental context and are not required to interpret the request" + - "Reject when an image is the only source of code or key request details" + required_format: | + All required code must be provided as text using proper markdown code blocks with AL language syntax: + ```al + // Your AL code here + procedure Example() + begin + // Implementation + end; + ``` + polite_message_template: | + Thank you for your request! + + I noticed that your submission includes an image. We can only evaluate the request based on searchable text content, so any required code and key request details must be provided in text form. + + Please use the following format for AL code: + + ```al + // Your AL code here + procedure YourProcedureName() + begin + // Your implementation + end; + ``` + + If the request is already clear from the text and the code snippet is included as text, the image can remain as supplemental context and will be ignored during analysis. + + Thank you for your cooperation! diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/request_for_external_requirements.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/request_for_external_requirements.yaml new file mode 100644 index 000000000..dab414c08 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/input-requirements/request_for_external_requirements.yaml @@ -0,0 +1,80 @@ +# Request for External Specific Requirements Configuration +# Defines ADDITIONAL requirements specific to request-for-external type issues +# NOTE: General requirements (title, description, meaningful justification) are defined in ea_config_general_requirements.yaml +# Version: 1.0 + +request_for_external: + enabled: true + description: "Requests to change procedure/property access from local/internal to public/global, or to remove OnPrem Scope restrictions - ADDITIONAL requirements beyond general ones" + detection_keywords: ["local", "global", "public", "accessibility", "access level", "external", "OnPrem", "Scope", "remove OnPrem", "remove Scope"] + max_iterations: 3 + + # Simplified validation for safe procedures + simplified_validation: + enabled: true + description: "When author wants to avoid code duplication and procedure is safe, accept with minimal justification" + trigger_conditions: + - "Author mentions avoiding duplicate code" + - "Author mentions reusing existing logic" + - "Author mentions calling the same validation/check" + trigger_keywords: ["duplicate", "duplication", "reuse", "same logic", "same scenario", "same check", "same validation", "avoid copying"] + procedure_safety_check: + description: "Agent must verify the procedure is safe before applying simplified validation" + risky_indicators: + - "Accesses or modifies private/sensitive data (passwords, tokens, secrets)" + - "Performs security-related operations (authentication, authorization)" + - "Could be exploited for XPIA (Cross-Partner Integration Attack)" + - "Modifies critical system state that should remain internal" + - "Handles encryption/decryption operations" + - "Accesses user credentials or session data" + - "Could bypass permission checks if made public" + safe_indicators: + - "Simple validation/check procedure" + - "Read-only data access" + - "Formatting or calculation helpers" + - "UI-related utilities" + - "Standard business logic that extensions need to reuse" + when_safe_and_avoiding_duplication: + accept_minimal_justification: true + skip_detailed_usage_scenario: true + rationale: "Code duplication avoidance is a valid technical reason when the procedure poses no security or stability risk" + + additional_requirements: + - id: proposed_code_change + name: "Proposed Code Change" + description: "Exact access level change needed with current and proposed declarations" + mandatory: true + validation_hints: ["code", "procedure", "access", "level", "local", "public"] + flexible_validation: true + validation_notes: | + This requirement can be satisfied in two ways: + 1. Author explicitly provides current and proposed code structure, OR + 2. Agent can determine the target object/procedure/variable from the description and locate it in the codebase + + Only mark as missing-info if: + - Author's description is too vague to identify the target, AND + - Codebase search cannot locate the referenced object/procedure/variable + #documentation_link: "https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-al-procedures" + + - id: usage_scenario + name: "Usage Scenario" + description: "How the public procedure will be used and from which context" + mandatory: true + validation_hints: ["usage", "scenario", "context", "how", "where"] + flexible_validation: true + validation_notes: | + This requirement can be relaxed when: + 1. Author explicitly mentions avoiding code duplication, AND + 2. Agent verifies the procedure is safe (no risky indicators) + + In such cases, the justification "avoid duplicate code" or "reuse existing logic" + is sufficient - detailed usage scenario is NOT required. + + The intent is clear: author wants to call the same logic from their extension + instead of copying it. + +# Validation Messages +validation_messages: + insufficient_detail: "The provided information is insufficient for processing. Please add:" + #requirement_reference: "For more details, see our Extensibility Guidelines: {link}" + resubmit_after_update: "Please update the issue with the missing details. We'll automatically re-analyze once updated." diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step0-getting-started.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step0-getting-started.md new file mode 100644 index 000000000..ef247a2ad --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step0-getting-started.md @@ -0,0 +1,44 @@ +# Step 0: Startup Checks + +Verify the environment before processing begins. + +**Offline mode:** there is no live issue and no `gh`. The request is supplied as rendered +text in the prompt (`REQUEST_TEXT`), and the AL source is checked out locally at the +caller-supplied repository root (`CODE_ROOT`). The knowledge files are installed next to +these step files under `TRIAGE_ROOT = .github/instructions/extensibility-request-triage`. + +**Checks (both required, must pass):** +1. **Codebase**: `glob` for `/**/*.al` (or `**/SalesPost.Codeunit.al`) returns + at least one result — confirms the local AL source is present. +2. **Configs**: confirm the knowledge YAML files exist under `TRIAGE_ROOT` + (`team-configuration`, `comment-templates`, and at least some `input-requirements` / + `codebase-rules`). Do not open or read them yet. + +If either check fails, return `Success: false` with the reason and stop. + +## Format the input as `GH_REQUEST` + +Parse `REQUEST_TEXT` (title + body + any follow-up comments + current labels) into a +`GH_REQUEST` object. There is no live issue to fetch — everything comes from the prompt. + +```json +{ + "title": string, + "description": string, + "type": "Task", + "state": "open", + "labels": string[], + "comments": [ { "body": string } ] +} +``` + +Fields not present in `REQUEST_TEXT` take these defaults: `type = "Task"`, +`state = "open"`, `labels = []` (unless current labels are supplied), `comments = []`. + +**Return JSON:** +```json +{ + "Success": true, + "FailureReason": "" +} +``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step1-preprocess.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step1-preprocess.md new file mode 100644 index 000000000..47a9b7b99 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step1-preprocess.md @@ -0,0 +1,25 @@ +# Step 1: Preprocess Request Thread + +Extract the core extensibility request from the request thread. + +**Input:** `GH_REQUEST` — title, description, and all comments (from `REQUEST_TEXT`). + +Read the full thread and extract only what is needed for technical evaluation. If the author revised their request in comments, use the latest version. Also include answers the author gave in response to agent or reviewer questions — these often contain justification or clarification not present in the original post. Ignore greetings, bot replies, off-topic discussion, and repeated content. + +**Extract:** +- `goal` — what extensibility change the author wants (1–2 sentences) +- `latestRequest` — the most up-to-date version of the request +- `proposedCode` — any AL code, procedure names, or object names mentioned (empty string if none) +- `justification` — the business or technical reason given (empty string if none) +- `clarifications` — list of key points clarified during the thread; for each, briefly summarize what was asked and what the author explained (1 sentence each, empty array if none) + +**Return JSON:** +```json +{ + "goal": "...", + "latestRequest": "...", + "proposedCode": "...", + "justification": "...", + "clarifications": ["Q: ... — A: ...", "Q: ... — A: ..."] +} +``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step2-eligibility-check.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step2-eligibility-check.md new file mode 100644 index 000000000..9f44ac974 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step2-eligibility-check.md @@ -0,0 +1,34 @@ +# Step 2: Eligibility Check + +Decide whether this request should be processed. + +**Input:** `GH_REQUEST` (metadata: state, labels, type, comments) and `DISTILLED_REQUEST`. + +**Offline note:** there is no live issue, author identity, or activity history — timestamp- +and commenter-based checks do not apply. Evaluate eligibility from the request metadata and +the thread content only. + +**All checks below must pass:** +1. Request state is `open`. +2. Request type is `Task`. +3. Labels are empty OR only `missing-info` is set. +4. **Content Appropriateness**: body and comments must not contain inappropriate, offensive, or abusive content. +5. **Author Withdrawal**: the most recent comment must not indicate the request is being withdrawn or cancelled (e.g., phrases like "withdraw", "cancel", "no longer needed", "closing this", "nevermind"). Also treat as withdrawal when the author confirms that an alternative approach suggested by the agent satisfies their need and they explicitly state they no longer need the original request (e.g., "this alternative works for me", "I no longer need the original approach", "thank you for the change"). If withdrawn, set `FailureLabel: "close"` — no further processing needed. + +**Outcomes:** + +| Condition | IsEligible | FailureLabel | +|-----------|-----------|--------------| +| All checks pass | true | `""` | +| Author withdrawal detected | false | `"close"` | +| Check 4 fails (inappropriate content) | false | `"agent-not-processable"` | +| Any other failure | false | `"do-nothing"` | + +**Return JSON:** +```json +{ + "IsEligible": true, + "FailureLabel": "", + "FailureReason": "" +} +``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step3-request-types.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step3-request-types.md new file mode 100644 index 000000000..0cbdd1b77 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step3-request-types.md @@ -0,0 +1,32 @@ +# Step 3: Request Type Classification + +Classify the extensibility request into a type and subtype. + +**Input:** `DISTILLED_REQUEST` + +**Types and subtypes:** + +| Type | Keywords | Sub-Types / Logic | +|------|----------|-------------------| +| **`event-request`** | "event", "publisher", "subscriber", "OnBefore", "OnAfter" | • **`ishandled`**: When the request asks for an `IsHandled` (or similar) parameter to allow skipping code. Note: Simply adding other parameters to an *existing* event is NOT `ishandled`.
• **`regular`**: Default. | +| **`request-for-external`** | "local to global", "public", "protected", "accessibility", "remove OnPrem" | Change scope/visibility. | +| **`enum-request`** | "enum", "option" | • **`new_enum`**: Create brand new enum.
• **`extend_existing_enum`**: Add to existing. | +| **`extensibility-enhancement`** | "improve", "enhance", "add" | Catch-all for other enhancements. | + +**Classification rules:** +- `ishandled` subtype: the author wants to skip or bypass a portion of code that currently cannot be skipped. This is a semantic judgment — look at intent, not specific words. +- Multiple distinct types (e.g., event + enum) → `Success: false`, `FailureLabel: "missing-info"`, ask author to submit separate requests. +- Multiple requests of the same type (e.g., two events) → allowed, classify as that type. +- Pure bug with no extensibility request → `Success: false`, `FailureLabel: "agent-not-processable"`. +- Bug report combined with an extensibility request → classify as the extensibility type. + +**Return JSON:** +```json +{ + "Success": true, + "TYPE": "event-request", + "SUBTYPE": "regular", + "FailureLabel": "", + "FailureReason": "" +} +``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step4-requirements-check.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step4-requirements-check.md new file mode 100644 index 000000000..120a4a108 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step4-requirements-check.md @@ -0,0 +1,33 @@ +# Step 4: Requirements Check + +Validate that the request contains all required information for its type. + +**Input:** `DISTILLED_REQUEST`, `GH_REQUEST`, `TYPE`, `SUBTYPE` + +Read the requirement YAMLs from `TRIAGE_ROOT/input-requirements` +(`TRIAGE_ROOT = .github/instructions/extensibility-request-triage`). + +**Process:** +1. Read `TRIAGE_ROOT/input-requirements/general_requirements.yaml`. +2. Read `TRIAGE_ROOT/input-requirements/{TYPE}_requirements.yaml` (replace `-` with `_` in type name). +3. If subtype exists, read `TRIAGE_ROOT/input-requirements/{TYPE}_{SUBTYPE}_requirements.yaml` if present. +4. Evaluate every requirement against the distilled request. +5. Stop immediately if a requirement has `action: agent-not-processable` or `action: reject`. + - if requirement with `action: reject` is met, set `FailureLabel: "close"`. +6. Otherwise, collect all failures and report them together. + +**Log every requirement:** `{PASS|FAIL} | {requirement_id} - {one-line summary}` + +**Return JSON:** +```json +{ + "Success": true, + "FailureLabel": "", + "FailureReason": "" +} +``` + +If `Success` is `false`, set: +- `FailureLabel` to `"close"`, `"missing-info"`, or `"agent-not-processable"`. +- `FailureReason` to a consolidated explanation of all failures. +- For `FailureLabel: "close"`, include the rejection message from the matched YAML requirement. diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step5-codebase-analysis.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step5-codebase-analysis.md new file mode 100644 index 000000000..996257a53 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step5-codebase-analysis.md @@ -0,0 +1,81 @@ +# Step 5: Codebase Analysis + +Evaluate the feasibility of the extensibility request against the **local** AL source. + +**Input:** `DISTILLED_REQUEST`, `TYPE`, `SUBTYPE`, `CODE_ROOT` + +**Offline note:** the source is checked out locally under `CODE_ROOT`. Use local file tools +(`glob`, `grep`, `read`/`view`) — there is no remote GitHub API and no `github_*` tool. +Load the rule files yourself (below) from `TRIAGE_ROOT/codebase-rules` +(`TRIAGE_ROOT = .github/instructions/extensibility-request-triage`). + +## Load the rules first + +For each category (`blockers`, `alternative_suggestions`, `warnings`, `implementation`), +collect the following files in order, skipping any that do not exist, and assemble the +`RULES` batches: +- `TRIAGE_ROOT/codebase-rules/general_.yaml` +- `TRIAGE_ROOT/codebase-rules/_.yaml` +- `TRIAGE_ROOT/codebase-rules/_.yaml` + +`typePrefix`: `event-request→event_request`, `request-for-external→request_for_external`, +`enum-request→enum_request`, `extensibility-enhancement→extensibility_enhancement`. +`subtypeSuffix`: `ishandled→_ishandled`, `new_enum→_new_enum`, +`extend_existing_enum→_extend_existing_enum`, `regular→` (none). + +## Core Logic + +1. **Understand Intent:** Determine what the author wants, including all existing comments. If a prior author reply already addresses a suggestion or warning, do not raise it again. + +2. **Identify Targets:** Determine which objects require changes and generate `OBJECT_LIST` (only objects that need modification). + +3. **Locate Each Target File (local search):** For each object in `OBJECT_LIST`, find its source file path under `CODE_ROOT`. + + **Search order (keep searches scoped and cheap):** + 1. Derive `{ObjectNameCamelCase}.{ObjectType}.al` from the object name (remove spaces and special characters, apply CamelCase, append the object-type suffix — e.g. "Sales-Post" (Codeunit) → `SalesPost.Codeunit.al`, "Item Ledger Entry" (Table) → `ItemLedgerEntry.Table.al`). + 2. `glob` by filename first, e.g. `glob("/**/SalesPost.Codeunit.al")`. Prefer matches under the W1 layer. + 3. If glob fails, run a single targeted `grep` by object name (NOT numeric ID) for the object declaration, e.g. `grep("codeunit .* \"Sales-Post\"", "/**/*.al")`. + 4. Do **not** search by numeric ID and do **not** run broad unscoped `**/*.al` content scans — these are slow. If the target still cannot be identified after glob + one grep, return `agent-not-processable` and stop. + + - **Trigger missing?** Creating a new trigger is allowed. + - **Procedure missing?** Return `missing-info`. + +4. **Check Existing Implementation — quick pre-check before loading the full file.** `grep` the target file for the entity name to reveal its current declaration section (`var`, `protected var`, access modifiers, etc.). If the entity is **already in the exact form requested** (e.g., already `protected var`), set `FailureLabel: "already-implemented"` and stop — do not load the full file or evaluate rules. + +5. **Read the Full File — mandatory before any rule evaluation.** If not already implemented, `read` the complete target file. All rule checks must be based on this full content — do not substitute search snippets. The file may be large; process it entirely. + +6. **Expand Context — follow the call graph when it matters.** Use judgment to decide whether reading related files (callers/callees) would change feasibility or rule evaluation. Find callers with a scoped `grep` for the procedure name under `CODE_ROOT`; find callees from the calls in the already-read file, then `glob`/`read` those objects selectively. Limit depth to what is directly relevant — stop when you have enough context. + +7. **Progressive Rule Evaluation:** Apply the assembled `RULES` in order against the full file content. Short-circuit on the first blocker. + +8. **Evaluate Rules:** + - **`blockers`** — stop on first match; outcome: `auto-reject` + - **`alternative_suggestions`** — if any match; outcome: `missing-info` + - **`warnings`** — if any rule condition matches in code; outcome: `missing-info` + - **`implementation`** — only if nothing matched above; generate `SUGGESTED_IMPLEMENTATION` + + **Important:** For `warnings`, do not override the rule with personal judgment. If the condition matches, flag it unless the author already addressed it in comments. + + **`SUGGESTED_IMPLEMENTATION` format:** Produce an AL code snippet — not prose — showing exactly what to add or change. Include 3–5 lines of unchanged surrounding code before and after the insertion point so a developer can locate the exact position. Mark each newly added implementation line with `// NEW`. If a new publisher procedure is added, include its full definition at the end of the snippet, but do **not** mark the event signature line itself with `// NEW`. + +**Log every rule:** `{PASS|FAIL|SKIP} | {rule_id} - {reason}` + +**Multi-change:** If the request contains multiple changes and ANY is blocked, reject all. + +**Return JSON:** +```json +{ + "Success": true, + "OBJECT_LIST": [ + { + "objectType": "Codeunit", + "objectName": "Sales-Post", + "namespace": "Microsoft.Sales.Posting", + "filePath": "App/Layers/W1/BaseApp/Sales/Posting/SalesPost.Codeunit.al" + } + ], + "SUGGESTED_IMPLEMENTATION": "...", + "FailureLabel": "", + "FailureReason": "" +} +``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step6-team-assignment.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step6-team-assignment.md new file mode 100644 index 000000000..d9304d068 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step6-team-assignment.md @@ -0,0 +1,24 @@ +# Step 6: Team Assignment + +Assign the responsible team based on the objects in `OBJECT_LIST`. + +**Input:** `OBJECT_LIST` + +**Process:** +1. Read `TRIAGE_ROOT/team-configuration/team_namespace_mapping.yaml` + (`TRIAGE_ROOT = .github/instructions/extensibility-request-triage`). +2. For each object, extract its namespace and strip the `Microsoft.` prefix. +3. Match the namespace against the mapping using iterative fallback: try the full namespace, then remove the last segment and retry, until a match is found or no segments remain. +4. Count matches per team. Assign the team with the highest count. +5. Tie-breaker: alphabetical order (Finance before Integration before SCM). +6. No matches at all → `Success: false`, `FailureLabel: "agent-not-processable"`. + +**Return JSON:** +```json +{ + "Success": true, + "TEAM_LABEL": "Finance", + "FailureLabel": "", + "FailureReason": "" +} +``` diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step7-labels-comments.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step7-labels-comments.md new file mode 100644 index 000000000..b76cf4a46 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/step7-labels-comments.md @@ -0,0 +1,61 @@ +# Step 7: Finalize + +Produce the labels, advisory comment, and state, then **write the decision to disk**. + +**Input:** Full workflow state — `TYPE`, `TEAM_LABEL`, `FailureLabel`, `FailureReason`, +`OBJECT_LIST`, `SUGGESTED_IMPLEMENTATION`. + +**Important:** Work exclusively from the workflow state. Do not fetch files, search, or call +analysis tools here — the only tool used is the file write at the end. + +**Offline output:** there is no live issue. The decision is **advisory** and is recorded as +JSON only — nothing is posted, labelled, or closed on any server. A human reviews it later. + +## Decision table + +| Outcome | Labels | Comment template | State | +|---------|--------|-----------------|-------| +| Feasible (Success throughout, `FailureLabel` empty) | `[TEAM_LABEL, TYPE]` | `approved_{TYPE}` | open | +| Missing info | `["missing-info"]` | explain the missing items (e.g. `missing_info_requirements` / `missing_info_procedure_not_found`) | open | +| `FailureLabel: "agent-not-processable"` | `["agent-not-processable"]` | (none) | open | +| Auto-reject (blocker) | `[]` | `rejected_request` | closed | +| Already implemented | `[]` | `already_implemented` | closed | +| `FailureLabel: "close"` and reason is stale/inactive/withdrawn | `["missing-info"]` | `stale_issue_closure` | closed | +| `FailureLabel: "close"` and any other rejection reason | `[]` | `rejected_request` | closed | +| `FailureLabel: "do-nothing"` | (none) | (none) | (no change) | + +Fill the chosen template from `TRIAGE_ROOT/comment-templates/comment_templates.yaml` +(`TRIAGE_ROOT = .github/instructions/extensibility-request-triage`) using the workflow state +(team, type, missing items, suggested implementation, rejection reason). If no exact +`approved_{TYPE}` template exists, generate an equivalent approval comment. Never add a +footer that names or credits the triage agent. + +## Emit the result contract + +Produce **exactly** this object (the `Final_Output` the evaluation reads back): + +```json +{ + "Final_Output": { + "labels_to_set": ["Finance", "event-request"], + "comment_to_post": "Hi, ...", + "request_state": "open", + "failureStep": "", + "failureReason": "" + } +} +``` + +Field rules: +- **`labels_to_set`** — the complete set of *managed* labels the request should end with. May be empty (`[]`) for a rejected request. +- **`comment_to_post`** — the advisory comment body filled from the chosen template. Empty string = no comment. +- **`request_state`** — `"open"`, `"closed"`, or `""` (no recommendation, used for `do-nothing`). +- **`failureStep`** / **`failureReason`** — diagnostics for logging only; not part of the advisory comment. + +## Write it + +Write the object above as valid JSON to **`/triage_result.json`**. This is the +single artifact the evaluation reads — there is no issue to update, no label to reconcile, +and no comment to post. + +Report a one-line summary: outcome, labels, comment (y/n), state. diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/team-configuration/team_namespace_mapping.yaml b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/team-configuration/team_namespace_mapping.yaml new file mode 100644 index 000000000..dd6e0f3a3 --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/instructions/extensibility-request-triage/team-configuration/team_namespace_mapping.yaml @@ -0,0 +1,111 @@ +# Team Namespace Mapping Configuration +# Maps namespaces to team ownership for automated team assignment +# Version: 1.0 + +team_namespace_mapping: + + Finance: + namespaces: + - "AccountantPortal" + - "Bank" + - "Booking" + - "CashFlow" + - "Finance.CashFlow" + - "CostAccounting" + - "Finance" + - "FixedAssets" + - "Globalization" + - "GeneralLedger" + - "MoneyTransfer" + - "PaymentMethods" + - "Payroll" + - "PostingSetup" + - "Sales.Peppol" + - "SubscriptionBilling" + - "TaxEngine" + - "ValueAddedTax" + match_strategy: "iterative_fallback" + case_sensitive: false + description: "Handles financial/accounting related code" + + SCM: + namespaces: + - "Assembly" + - "CRM" + - "HumanResources" + - "Foundation" + - "Inventory" + - "Inventory.Posting" + - "Manufacturing" + - "Pricing" + - "Projects" + - "Purchases" + - "PurchaseDocuments" + - "Sales" + - "Sales.Document" + - "SalesInvoicing" + - "Service" + - "ServiceManagement" + - "Warehouse" + - "WarehouseManagement" + - "ProductionOrders" + - "ServiceOrders" + match_strategy: "iterative_fallback" + case_sensitive: false + description: "Handles supply chain, manufacturing, service, and related code" + + Integration: + namespaces: + - "AI" + - "API" + - "Automation" + - "Azure" + - "AzureML" + - "BusinessCentral" + - "DataAdministration" + - "DateTime" + - "Device" + - "Diagnostics" + - "EMail" + - "Email" + - "EServices" + - "ExternalServices" + - "Foundation" + - "Integration" + - "Intercompany" + - "JSON" + - "REST" + - "SOAP" + - "Security" + - "System" + - "SystemApplication" + - "Telemetry" + - "Utilities" + - "WebServices" + - "XML" + match_strategy: "iterative_fallback" + case_sensitive: false + description: "Handles system integration, API, automation, and utilities" + +# Assignment Algorithm +assignment_algorithm: + step1: "Extract namespaces from target objects in issue" + step2: "Search codebase to locate actual object and determine namespace" + step3: "Match each namespace against team namespace mappings (iterative fallback: remove Microsoft prefix, then try full, drop segment, repeat)" + step4: "Count namespace matches per team" + step5: "Assign team with highest match count" + step6: "If tie, sort alphabetically and select first team (Finance > Integration > SCM)" + step7: "If no matches found, mark as agent-not-processable" + +# Tie-Breaker Rules +tie_breaker: + enabled: true + strategy: "alphabetical" + order: ["Finance", "Integration", "SCM"] # Alphabetical order + description: "When multiple teams have equal match counts, select team appearing first alphabetically" + +# Fallback Handling +fallback: + no_objects_identified: "agent-not-processable" + no_namespace_match: "agent-not-processable" + single_object_multiple_namespaces: "count_all_matches" diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/skills/extensibility-request-implement/SKILL.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/skills/extensibility-request-implement/SKILL.md new file mode 100644 index 000000000..1b0689ffe --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/skills/extensibility-request-implement/SKILL.md @@ -0,0 +1,203 @@ +--- +name: extensibility-request-implement +description: > + Implement a single Business Central extensibility request as an AL code change. The request is + provided as plain text (a natural-language description of the extension point that is needed, + usually a new integration event / event publisher). The skill locates the affected AL code in the + current repository, adds the requested extension point following the repository extensibility + guidelines, and propagates the change across the W1 base layer and any same-named country/region + layer files. It edits only `.al` source files and leaves the changes unstaged in the working tree. + No build, no tests, no commit, and no pull request. TRIGGER when the user asks to implement or add + an extensibility request / extension point / integration event described in the provided text. + DO NOT TRIGGER for bug fixes, for building or testing, or for opening pull requests. +allowed-tools: ['view', 'create', 'edit', 'grep', 'glob', 'powershell', 'ask_user'] +--- + +# AI Extension Fix Skill + +This skill implements one Business Central extensibility request entirely offline, inside the +current repository. It reads the request text, adds the requested extension point (most often an +integration event) to the standard code following the repository guidelines, and applies the +change W1-first with propagation to same-named country/region layer files. It does **not** build, +test, commit, or open a pull request. The change is left as unstaged edits in the working tree; the +author (or the evaluation harness) reviews the resulting diff. + +--- + +## Critical Rules + +- **Text input only**: The extensibility request is provided as plain text in the current + conversation / prompt. Never fetch anything from an issue tracker or any external system, and never + depend on labels, tickets, or online metadata. +- **Single request**: This skill implements one request per run. If more than one distinct request is + provided, implement the first and warn. +- **No writeback, no PR**: Never commit, never push, never open or edit a pull request, and never run + any `gh` command. The only deliverable is the set of local `.al` edits. +- **No build, no tests**: Never build, publish, or run the app, and never create tests. Produce a + change that is aligned with the guidelines and, to the best of static reasoning, compilable. + Correctness is verified by the human reviewer on the resulting diff. +- **Surgical changes**: Make the smallest diff that satisfies the request. Follow the repository's + code surgery guidelines (see Step 2). Do not reformat, rename, reorder, or refactor unrelated code. +- **W1-first layering**: When the file to change exists in the W1 base layer (`App/Layers/W1/...`), + make the change in W1 first, then apply the same change to every same-named counterpart file that + exists in the other layers (Step 5). Only when the file exists solely in a specific country layer do + you edit that layer directly and skip the propagation step. +- **AL files only**: Only ever create or modify `.al` source files. Never edit, add, or delete any + other file type (for example `.json`, `.xml`, `.xlf`/translations, `.md`, `.txt`, project or + settings files, workflows, or build scripts). If a correct change would require touching a + non-`.al` file, STOP and report that instead of changing it. +- **No ticket-reference comments in code**: Do not add `// issue #`-style comments or any external + reference into the source. + +--- + +## Step 1: Parse the Request + +Review the current conversation and the most recent user message to extract the extensibility +request. Identify: + +- **What extension point is asked for** — typically a new integration event / event publisher, but it + may be an event added to an existing procedure or trigger. +- **Where it should live** — the object (codeunit/table/page/report), and the procedure or trigger and + the exact placement (before/after a step, in an `else` branch of a `case`, etc.). +- **What data it must expose** — the records and parameters subscribers need. + +If the request cannot be found in the provided text and an ask-user capability is available, ask the +user for it. If none is available (unattended), STOP with: `An extensibility request is required.` + +If more than one distinct request is present, keep the first, warn that this skill implements a +single request per run, and ignore the rest. + +--- + +## Step 2: Understand and Plan the Change + +Build enough understanding to make a correct, minimal change. + +1. **Load the guidelines** the change must follow, in this order: + + - The repository code surgery guidelines: read `.github/copilot/code-guidelines.md` if present. + - The extension guidelines: read `guidelines.md` in this skill folder. + - General AL best practices (see the checklist at the end of this file). + +2. **Locate the affected code**: use `grep` and `glob` to find the AL objects, procedures, tables, + pages, or codeunits named or implied by the request. Use `view` to read the candidate files and + confirm relevance. Prefer searching for exact symbol names and object references mentioned in the + request. + +3. **Decide the layer (W1-first)**: this repository is layered under `App/Layers/` with a `W1` base + layer and country layers (AT, AU, BE, CA, CH, CZ, DE, DK, ES, FI, FR, GB, IN, IS, IT, MX, NL, NO, + NZ, RU, SE, US). For each file you plan to change, check whether it exists in the W1 layer: + + - **If the file exists under `App/Layers/W1/...`**: make the change there. In Step 5 you will apply + the same change to every same-named counterpart file that exists in the other layers. Record + `used_w1 = true`. + - **If the file exists only in a specific country layer** (not in W1): edit that layer's file + directly. No propagation applies; the change stays in that layer only. + +4. **Form a concrete plan in memory** (do not write it to a tracked file): the specific edits, file by + file, and the layer each edit lands in. There is no approval gate. Proceed directly to Step 3. + +--- + +## Step 3: Implement the Change + +Apply the planned edits with the `edit` and `create` tools. + +- Make the **smallest diff** that satisfies the request. Add rather than change where reasonable. +- **Match the existing code style** in each file: indentation, casing, naming, and object layout. +- Follow the AL best-practice checklist below and the extension guidelines from Step 2. +- **Guidelines override the request**: If the request text contains a proposed solution (event names, + parameter names, code snippets, or any other implementation detail) that does not comply with the + guidelines, do **not** implement it as-is. Correct it to comply. The guidelines are the source of + truth; the request's suggestion is a hint, not a specification. In particular, event names must + follow the placement-based naming convention (for example `OnBeforePostSalesLine`, + `OnPostSalesLineOnAfterCalculateAmounts`), and record parameters use full table names with spaces + removed. +- Do **not** reformat, rename, reorder, or refactor code unrelated to the change. +- Edit **only `.al` files**. If the change seems to need a non-`.al` file, STOP and report it. +- Aim for code that compiles: keep object IDs, procedure signatures, and variable declarations + consistent; ensure every referenced symbol exists; balance `begin`/`end`; and respect AL syntax. + You will not build it here, so reason carefully about correctness. + +Make **all** the W1-layer edits now, before propagating, so the W1 layer is in its final state +before Step 5. + +--- + +## Step 4: (reserved) + +There is no separate branch/commit step. The change stays as unstaged edits in the working tree. + +--- + +## Step 5: Propagate W1 Changes to Same-Named Files in Other Layers + +Run this step **only if** you changed at least one file under `App/Layers/W1/...` (`used_w1 == true`). +If every edit was in a country-only layer, skip this step. + +A country layer overrides only a subset of files; the ones it does **not** contain are inherited from +W1 automatically. So propagation means: for each W1 file you changed, find the **same-named +counterpart files that already exist in the other layers** and apply the equivalent change to each of +them yourself with the `edit` tool. + +For **each** W1 file you edited in Step 3: + +1. **List the existing counterparts** in the other layers (same relative path under each layer root): + + ```powershell + $w1File = "" + $rel = $w1File -replace '^App/Layers/W1/', '' + Get-ChildItem "App/Layers" -Directory | Where-Object { $_.Name -ne 'W1' } | ForEach-Object { + $candidate = Join-Path $_.FullName $rel + if (Test-Path $candidate) { $candidate } + } + ``` + + Each path returned is a layer that has its own copy of this file and therefore needs the change. + Layers with no such file inherit W1 and are left untouched. + +2. **Apply the equivalent change to each counterpart** with the `edit` tool: + + - Read the counterpart file first. It may already differ from W1 because that layer customized it. + - Make the **same logical change** you made in W1 — locate the same object, procedure, or lines and + apply it there. + - **Adapt, do not overwrite**: preserve that layer's existing customizations, local naming, values, + and any country-specific behavior. If the layer's code differs from W1's, apply the intent of the + change to the layer's variant rather than pasting the W1 text verbatim. + - Keep the same surgical, style-matching discipline as Step 3. + +3. If a counterpart's code has genuinely diverged so the change does not map cleanly, apply the safest + equivalent change (preserve the layer's behavior) and continue. + +--- + +## Step 6: Summary + +Report to the user, concisely: + +- The extensibility request implemented, in one or two sentences. +- The extension point added (object, procedure/trigger, event name and signature). +- The exact list of `.al` files changed, including any propagated layer counterparts. + +If no file changes were produced, STOP and report that the request did not lead to any code change, +explaining why. + +--- + +## AL Best-Practice Checklist + +Apply these when editing AL code, unless the surrounding code clearly follows a different local +convention (match the file): + +- Follow standard event conventions: `[IntegrationEvent(false, false)]`, `local procedure`, empty + body, and placement-accurate `OnBefore`/`OnAfter`/`OnOn` naming. +- Use full table names with spaces removed for record parameters (e.g. `SalesHeader`, `Jobs Setup` + → `JobsSetup`); do not abbreviate. Use descriptive names for simple-type parameters. +- Append new parameters to the end of an existing event signature; do not insert in the middle. +- When adding `IsHandled`, initialize it to `false` before the event call. +- Use meaningful PascalCase names for objects, procedures, and variables; match existing naming. +- Respect existing object IDs and app structure; do not renumber or move objects. +- Preserve `begin`/`end`, `case`, and `if/then/else` structure and existing indentation. +- Avoid breaking public/extensible signatures; add optional parameters or overloads instead. +- Keep changes backward compatible for existing callers and subscribers. diff --git a/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/skills/extensibility-request-implement/guidelines.md b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/skills/extensibility-request-implement/guidelines.md new file mode 100644 index 000000000..29659c0bf --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoftInternal-NAV/skills/extensibility-request-implement/guidelines.md @@ -0,0 +1,374 @@ +# Extension fix guidelines + +These guidelines supplement the repository-wide rules in `.github/copilot/code-guidelines.md`. +When fixing AL extensibility issues, follow the event-specific rules in this file in addition to +general AL best practices. + +## Event syntax + +### Integration event declaration + +```al +[IntegrationEvent(IncludeSender: Boolean, GlobalVarAccess: Boolean)] +local procedure EventName(Parameters) +begin +end; +``` + +**Parameters:** + +- `IncludeSender`: Usually `false`. Set to `true` only when subscribers need the sender object. +- `GlobalVarAccess`: Usually `false`. Set to `true` only when subscribers must access globals. + +### Event publisher pattern + +```al +local procedure DoSomething(var SalesHeader: Record "Sales Header") +var + IsHandled: Boolean; +begin + IsHandled := false; + OnBeforeDoSomething(SalesHeader, IsHandled); + if IsHandled then + exit; + + // Standard logic here + + OnAfterDoSomething(SalesHeader); +end; +``` + +## Naming conventions + +### Event name format + +All events must follow the standard naming pattern based on where they are raised, regardless of +any suggested name. + +**Pattern components:** + +- Prefix: Always `On` +- Procedure or trigger name: The containing procedure or trigger +- Timing: `OnBefore` or `OnAfter` +- Action context: The specific action when the event is in the middle of the flow + +### Events at the beginning or end of a procedure or trigger + +Use `OnBefore[ProcedureOrTriggerName]` or `OnAfter[ProcedureOrTriggerName]` when the event fires +at the very start or very end. + +| Location | Event name | +|----------|------------| +| `PostSalesLine` procedure, beginning | `OnBeforePostSalesLine` | +| `PostSalesLine` procedure, end | `OnAfterPostSalesLine` | +| `OnInsert` trigger, beginning | `OnBeforeOnInsert` | +| `OnModify` trigger, end | `OnAfterOnModify` | +| `Sell-to Customer No.` `OnValidate`, beginning | `OnBeforeValidateSellToCustomerNo` | +| `Quantity` `OnValidate`, end | `OnAfterValidateQuantity` | + +```al +procedure PostSalesLine(var SalesLine: Record "Sales Line") +begin + OnBeforePostSalesLine(SalesLine); + + ValidateLine(SalesLine); + CalculateAmounts(SalesLine); + InsertEntries(SalesLine); + + OnAfterPostSalesLine(SalesLine); +end; +``` + +### Events in the middle of a procedure or trigger + +Use `On[ProcedureOrTriggerName]OnBefore[ActionContext]` or +`On[ProcedureOrTriggerName]OnAfter[ActionContext]` when the event is raised around a specific step +inside the flow. + +| Location | Event name | +|----------|------------| +| `PostSalesLine` before validation | `OnPostSalesLineOnBeforeValidateLine` | +| `PostSalesLine` after calculation | `OnPostSalesLineOnAfterCalculateAmounts` | +| `Code` procedure before check | `OnCodeOnBeforeCheck` | +| `OnInsert` after init defaults | `OnOnInsertOnAfterInitDefaults` | +| `OnModify` before location validation | `OnOnModifyOnBeforeValidateLocationCode` | + +```al +procedure PostSalesLine(var SalesLine: Record "Sales Line") +begin + OnPostSalesLineOnBeforeValidateLine(SalesLine); + ValidateLine(SalesLine); + + OnPostSalesLineOnAfterValidateLine(SalesLine); + + CalculateAmounts(SalesLine); + + OnPostSalesLineOnAfterCalculateAmounts(SalesLine); + + InsertEntries(SalesLine); +end; +``` + +### Naming examples + +Incorrect: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforePost(var SalesLine: Record "Sales Line") +begin +end; +``` + +Correct: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforePostSalesLine(var SalesLine: Record "Sales Line") +begin +end; + +[IntegrationEvent(false, false)] +local procedure OnPostSalesLineOnBeforeValidation(var SalesLine: Record "Sales Line") +begin +end; +``` + +### Quick reference + +- Beginning or end: `OnBefore` or `OnAfter` + procedure or trigger name +- Middle of flow: `On` + procedure or trigger name + `OnBefore` or `OnAfter` + action context + +## Event parameter naming + +### Record parameters + +Use the AL table name with spaces removed. Do not abbreviate record parameter names. + +| Table name | Correct parameter | Avoid | +|------------|-------------------|-------| +| `Sales Header` | `SalesHeader` | `SalesHdr`, `SH` | +| `Sales Line` | `SalesLine` | `SalesLn`, `SL` | +| `Item Ledger Entry` | `ItemLedgerEntry` | `ItemLedgEntry`, `ILE` | +| `G/L Entry` | `GLEntry` | `GLE`, `GenLedgEntry` | +| `Purchase Header` | `PurchaseHeader` | `PurchHdr`, `PH` | +| `Customer` | `Customer` | `Cust`, `C` | +| `Vendor` | `Vendor` | `Vend`, `V` | + +Incorrect: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforePost(var SalesHdr: Record "Sales Header"; var ItemLedgEntry: Record "Item Ledger Entry") +begin +end; +``` + +Correct: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforePost(var SalesHeader: Record "Sales Header"; var ItemLedgerEntry: Record "Item Ledger Entry") +begin +end; +``` + +### Simple type parameters + +Use descriptive names for simple types. Do not abbreviate them. + +| Avoid | Correct | +|-------|---------| +| `DocNo` | `DocumentNo` | +| `Amt` | `Amount` or `TotalAmount` | +| `Qty` | `Quantity` | +| `Desc` | `Description` | +| `Date` | `PostingDate`, `DocumentDate`, or another precise name | + +Incorrect: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforePost(DocNo: Code[20]; Amt: Decimal; Qty: Decimal) +begin +end; +``` + +Correct: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforePost(DocumentNo: Code[20]; TotalAmount: Decimal; Quantity: Decimal) +begin +end; +``` + +### Temporary record parameters + +All temporary record parameters in event signatures must use the `Temp` prefix. + +Rationale: + +- It makes the temporary nature explicit to subscribers. +- It prevents confusion about persistence. +- It follows standard BC naming conventions. + +Incorrect: + +```al +[IntegrationEvent(false, false)] +local procedure OnAfterProcess(var InvtOrderTracking: Record "Invt. Order Tracking" temporary) +begin +end; +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeCalculate(var Buffer: Record Item temporary) +begin +end; +``` + +Correct: + +```al +[IntegrationEvent(false, false)] +local procedure OnAfterProcess(var TempInvtOrderTracking: Record "Invt. Order Tracking" temporary) +begin +end; +``` + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeCalculate(var TempItem: Record Item temporary) +begin +end; +``` + +Common temporary prefixes: + +- `TempBuffer` +- `TempItem` +- `TempCustomer` +- `TempInteger` + +## Signature changes + +### New parameters go at the end + +When adding a parameter to an existing event, always append it at the end of the signature. + +Original event: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeProcess(var Customer: Record Customer; var IsHandled: Boolean) +begin +end; +``` + +Incorrect: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeProcess(var Customer: Record Customer; NewParam: Text; var IsHandled: Boolean) +begin +end; +``` + +Correct: + +```al +[IntegrationEvent(false, false)] +local procedure OnBeforeProcess(var Customer: Record Customer; var IsHandled: Boolean; NewParam: Text) +begin +end; +``` + +## IsHandled rules + +### Always initialize `IsHandled` + +When adding an event with `IsHandled`, or when adding `IsHandled` to an existing event, set it to +`false` before calling the event. + +Correct: + +```al +procedure PostDocument(var SalesHeader: Record "Sales Header") +var + IsHandled: Boolean; +begin + IsHandled := false; + OnBeforePostDocument(SalesHeader, IsHandled); + if IsHandled then + exit; + + // Standard logic +end; +``` + +Incorrect: + +```al +procedure PostDocument(var SalesHeader: Record "Sales Header") +var + IsHandled: Boolean; +begin + OnBeforePostDocument(SalesHeader, IsHandled); + if IsHandled then + exit; + + // Standard logic +end; +``` + +### Adding `IsHandled` to an existing event + +Before: + +```al +procedure Process(var Customer: Record Customer) +begin + OnBeforeProcess(Customer); + // Standard logic +end; + +[IntegrationEvent(false, false)] +local procedure OnBeforeProcess(var Customer: Record Customer) +begin +end; +``` + +After: + +```al +procedure Process(var Customer: Record Customer) +var + IsHandled: Boolean; +begin + IsHandled := false; + OnBeforeProcess(Customer, IsHandled); + if IsHandled then + exit; + + // Standard logic +end; + +[IntegrationEvent(false, false)] +local procedure OnBeforeProcess(var Customer: Record Customer; var IsHandled: Boolean) +begin +end; +``` + +## Review checklist + +Use this checklist when adding or reviewing AL extensibility events: + +- Event name matches its exact placement in the procedure or trigger. +- Record parameters use full table names with spaces removed. +- Temporary record parameters use the `Temp` prefix. +- Simple parameters use descriptive, non-abbreviated names. +- New parameters are appended at the end of existing signatures. +- `IsHandled` is explicitly initialized to `false` before the event call. +- `IncludeSender` and `GlobalVarAccess` stay `false` unless there is a concrete need. diff --git a/src/bcbench/commands/dataset.py b/src/bcbench/commands/dataset.py index 35d63f878..11a27e2b3 100644 --- a/src/bcbench/commands/dataset.py +++ b/src/bcbench/commands/dataset.py @@ -109,7 +109,7 @@ def view_entry( if show_patch and isinstance(entry, RepoGroundedEntry): console.print("\n[bold cyan]Patch:[/bold cyan]") - console.print(Panel(entry.patch, border_style="magenta")) + console.print(Panel(entry.patch or "[dim]Empty[/dim]", border_style="magenta")) # Display category-specific fields if isinstance(entry, _BugFixTestGenBase): diff --git a/src/bcbench/commands/evaluate.py b/src/bcbench/commands/evaluate.py index 8adcfc535..4b7940340 100644 --- a/src/bcbench/commands/evaluate.py +++ b/src/bcbench/commands/evaluate.py @@ -313,6 +313,10 @@ def evaluate(self, context: EvaluationContext[BaseDatasetEntry]) -> None: scenarios = ["invalid", "valid"] case EvaluationCategory.NL2AL: scenarios = ["raw", "empty"] + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + scenarios = ["raw", "empty"] + case EvaluationCategory.EXT_REQUEST_TRIAGE: + scenarios = ["raw", "empty"] case _: raise ValueError(f"Unsupported category for mock evaluation: {context.category}") diff --git a/src/bcbench/dataset/__init__.py b/src/bcbench/dataset/__init__.py index 5e8227969..f178a39b3 100644 --- a/src/bcbench/dataset/__init__.py +++ b/src/bcbench/dataset/__init__.py @@ -2,11 +2,15 @@ from bcbench.dataset.codereview import CodeReviewEntry, ReviewComment, Severity from bcbench.dataset.dataset_entry import BaseDatasetEntry, BugFixEntry, NL2ALEntry, RepoGroundedEntry, TestEntry, TestGenEntry +from bcbench.dataset.extensibility_request import ExtRequestImplementEntry, ExtRequestTriageEntry, ManagedLabel __all__ = [ "BaseDatasetEntry", "BugFixEntry", "CodeReviewEntry", + "ExtRequestImplementEntry", + "ExtRequestTriageEntry", + "ManagedLabel", "NL2ALEntry", "RepoGroundedEntry", "ReviewComment", diff --git a/src/bcbench/dataset/dataset_entry.py b/src/bcbench/dataset/dataset_entry.py index 637be599e..3c87b6fe5 100644 --- a/src/bcbench/dataset/dataset_entry.py +++ b/src/bcbench/dataset/dataset_entry.py @@ -127,14 +127,6 @@ class RepoGroundedEntry(BaseDatasetEntry): def customization_profile(self) -> str: return self.repo.replace("/", "-") - -class _BugFixTestGenBase(RepoGroundedEntry): - """Shared schema for bug-fix and test-generation entries (same JSONL, different semantics).""" - - fail_to_pass: Annotated[list[TestEntry], Field(alias="FAIL_TO_PASS", min_length=1)] - pass_to_pass: Annotated[list[TestEntry], Field(alias="PASS_TO_PASS")] = [] - test_patch: Annotated[str, Field(min_length=1, pattern=r"^[^\x00]*$")] - @property def problem_statement_dir(self) -> Path: return _config.paths.problem_statement_dir / self.instance_id @@ -143,6 +135,14 @@ def get_task(self) -> str: readme_path = self.problem_statement_dir / _config.file_patterns.problem_statement_readme return readme_path.read_text(encoding="utf-8") + +class _BugFixTestGenBase(RepoGroundedEntry): + """Shared schema for bug-fix and test-generation entries (same JSONL, different semantics).""" + + fail_to_pass: Annotated[list[TestEntry], Field(alias="FAIL_TO_PASS", min_length=1)] + pass_to_pass: Annotated[list[TestEntry], Field(alias="PASS_TO_PASS")] = [] + test_patch: Annotated[str, Field(min_length=1, pattern=r"^[^\x00]*$")] + @model_validator(mode="after") def validate_baseapp_patches_are_w1_only(self) -> Self: if self.extract_project_name() != "BaseApp": diff --git a/src/bcbench/dataset/extensibility_request.py b/src/bcbench/dataset/extensibility_request.py new file mode 100644 index 000000000..0b51659f9 --- /dev/null +++ b/src/bcbench/dataset/extensibility_request.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +from typing import Annotated, Literal + +from pydantic import Field + +from bcbench.dataset.dataset_entry import RepoGroundedEntry +from bcbench.types import Checklist, ChecklistAssertion + +__all__ = ["ExtRequestImplementEntry", "ExtRequestTriageEntry", "ManagedLabel"] + + +class ExtRequestImplementEntry(RepoGroundedEntry): + """Dataset entry for the extensibility-request-implement category — implement an approved extensibility request in AL. + + Judge-based (no build, no tests). The agent reads the extensibility request (provided as plain + text) and adds the requested extension point (typically an integration event) to the existing repo + checked out at `base_commit`. The agent's diff is graded by an LLM judge against `expected`, which + encodes both fidelity to the gold fix (`patch`) and correct propagation across the expected + W1 + country/region layer files. + """ + + # LLM-judge checklist: expected event/signature/placement and expected layer propagation. + expected: Annotated[list[ChecklistAssertion], Field(min_length=1)] + + def get_expected_output(self) -> Checklist: + return {"assertions": self.expected} + + +type ManagedLabel = Literal[ + "Finance", + "SCM", + "Integration", + "event-request", + "request-for-external", + "enum-request", + "extensibility-enhancement", + "missing-info", + "agent-not-processable", +] + + +class ExtRequestTriageEntry(RepoGroundedEntry): + """Dataset entry for the extensibility-request-triage category — triage a single extensibility request. + + Judge-based (no build, no tests), scored by LMChecklist like NL2AL and extensibility-request-implement. + The agent reads one extensibility-request thread (rendered from `title` + `description` + any follow-up + `comments`, plus `current_labels` already on the request) and analyses feasibility against the standard + AL source checked out at `base_commit`. It writes its `Final_Output` triage decision — the managed labels + to set, an advisory comment, and whether the request stays open or is closed. That raw decision is scored + downstream by an LLM judge against `expected`, a checklist encoding the correct labels, issue state and the + substance of the advisory comment. + """ + + # Triage has no gold code diff; the expected answer lives in the `expected` checklist below. + patch: str | None = None + + title: Annotated[str, Field(min_length=1)] + description: Annotated[str, Field(min_length=1)] + comments: str = "" + current_labels: Annotated[list[ManagedLabel], Field(default_factory=list)] + + # LLM-judge checklist: expected labels_to_set, issue_state and advisory-comment substance. + expected: Annotated[list[ChecklistAssertion], Field(min_length=1)] + + def get_task(self) -> str: + sections = [f"# {self.title}", "", self.description.rstrip()] + if self.current_labels: + sections += ["", f"Current labels: {', '.join(self.current_labels)}"] + if self.comments.strip(): + sections += ["", "## Follow-up conversation", "", self.comments.rstrip()] + return "\n".join(sections) + + def get_expected_output(self) -> Checklist: + return {"assertions": self.expected} diff --git a/src/bcbench/evaluate/__init__.py b/src/bcbench/evaluate/__init__.py index c96892d07..458f2cfe5 100644 --- a/src/bcbench/evaluate/__init__.py +++ b/src/bcbench/evaluate/__init__.py @@ -3,7 +3,9 @@ from bcbench.evaluate.base import EvaluationPipeline from bcbench.evaluate.bugfix import BugFixPipeline from bcbench.evaluate.codereview import CodeReviewPipeline +from bcbench.evaluate.ext_request_implement import ExtRequestImplementPipeline +from bcbench.evaluate.ext_request_triage import ExtRequestTriagePipeline from bcbench.evaluate.nl2al import NL2ALPipeline from bcbench.evaluate.testgeneration import TestGenerationPipeline -__all__ = ["BugFixPipeline", "CodeReviewPipeline", "EvaluationPipeline", "NL2ALPipeline", "TestGenerationPipeline"] +__all__ = ["BugFixPipeline", "CodeReviewPipeline", "EvaluationPipeline", "ExtRequestImplementPipeline", "ExtRequestTriagePipeline", "NL2ALPipeline", "TestGenerationPipeline"] diff --git a/src/bcbench/evaluate/ext_request_implement.py b/src/bcbench/evaluate/ext_request_implement.py new file mode 100644 index 000000000..de4a04d22 --- /dev/null +++ b/src/bcbench/evaluate/ext_request_implement.py @@ -0,0 +1,48 @@ +from collections.abc import Callable +from pathlib import Path + +from bcbench.dataset import ExtRequestImplementEntry +from bcbench.evaluate.base import EvaluationPipeline +from bcbench.exceptions import EmptyDiffError +from bcbench.github_actions import github_log_group +from bcbench.logger import get_logger +from bcbench.operations import copy_problem_statement_folder, set_runtime_version, setup_repo_prebuild, stage_and_get_diff +from bcbench.results.base import JudgeBasedEvaluationResult +from bcbench.types import EvaluationContext + +logger = get_logger(__name__) + +__all__ = ["ExtRequestImplementPipeline"] + + +class ExtRequestImplementPipeline(EvaluationPipeline[ExtRequestImplementEntry]): + """Pipeline for the extensibility-request-implement category — implement an extensibility request as an AL change. + + Judge-based: no BC container, no build, no tests. The repo is checked out at the entry's base + commit and the agent adds the requested extension point. The resulting diff is scored downstream + by an LLM judge against the entry checklist. + """ + + def setup_workspace(self, entry: ExtRequestImplementEntry, repo_path: Path) -> None: + setup_repo_prebuild(entry, repo_path) + copy_problem_statement_folder(entry, repo_path) + set_runtime_version(repo_path, entry.project_paths) + + def setup(self, context: EvaluationContext[ExtRequestImplementEntry]) -> None: + self.setup_workspace(context.entry, context.repo_path) + + def run_agent(self, context: EvaluationContext[ExtRequestImplementEntry], agent_runner: Callable) -> None: + with github_log_group(f"{context.agent_name} -- Entry: {context.entry.instance_id}"): + context.metrics, context.experiment = agent_runner(context) + + def evaluate(self, context: EvaluationContext[ExtRequestImplementEntry]) -> None: + try: + generated_patch = stage_and_get_diff(context.repo_path) + except EmptyDiffError: + result = JudgeBasedEvaluationResult.create_empty_output(context) + logger.warning(f"Agent produced no changes for {context.entry.instance_id}") + else: + result = JudgeBasedEvaluationResult.create_raw(context, output=generated_patch) + logger.info(f"Saved raw extensibility-request-implement result for {context.entry.instance_id} (scoring pending)") + + self.save_result(context, result) diff --git a/src/bcbench/evaluate/ext_request_triage.py b/src/bcbench/evaluate/ext_request_triage.py new file mode 100644 index 000000000..2a3f7b7cb --- /dev/null +++ b/src/bcbench/evaluate/ext_request_triage.py @@ -0,0 +1,54 @@ +"""Pipeline for the extensibility-request-triage category — triage a single extensibility request. + +The agent runs the `extensibility-request-triage` custom agent (instructions-based) in emit-only mode: it reads the request text from the prompt, +analyses the standard AL source checked out at the entry's base commit, and writes its `Final_Output` +decision (managed labels + advisory comment + open/closed state) to `triage_result.json` in the repo +root instead of applying it via `gh`. Like NL2AL and extensibility-request-implement, the pipeline only +persists that raw decision; scoring is performed downstream by the LMChecklist judge against the entry's +`expected` checklist. +""" + +from collections.abc import Callable +from pathlib import Path + +from bcbench.dataset import ExtRequestTriageEntry +from bcbench.evaluate.base import EvaluationPipeline +from bcbench.github_actions import github_log_group +from bcbench.logger import get_logger +from bcbench.operations import setup_repo_prebuild +from bcbench.results.base import JudgeBasedEvaluationResult +from bcbench.types import EvaluationContext + +logger = get_logger(__name__) + +__all__ = ["TRIAGE_RESULT_FILE", "ExtRequestTriagePipeline"] + +TRIAGE_RESULT_FILE = "triage_result.json" + + +class ExtRequestTriagePipeline(EvaluationPipeline[ExtRequestTriageEntry]): + """Pipeline for the extensibility-request-triage category — no BC container, no build, no tests.""" + + def setup_workspace(self, entry: ExtRequestTriageEntry, repo_path: Path) -> None: + setup_repo_prebuild(entry, repo_path) + (repo_path / TRIAGE_RESULT_FILE).unlink(missing_ok=True) + + def setup(self, context: EvaluationContext[ExtRequestTriageEntry]) -> None: + self.setup_workspace(context.entry, context.repo_path) + + def run_agent(self, context: EvaluationContext[ExtRequestTriageEntry], agent_runner: Callable) -> None: + with github_log_group(f"{context.agent_name} -- Entry: {context.entry.instance_id}"): + context.metrics, context.experiment = agent_runner(context) + + def evaluate(self, context: EvaluationContext[ExtRequestTriageEntry]) -> None: + result_path = context.repo_path / TRIAGE_RESULT_FILE + raw = result_path.read_text(encoding="utf-8").strip() if result_path.exists() else "" + + if not raw: + result = JudgeBasedEvaluationResult.create_empty_output(context) + logger.warning(f"Agent produced no {TRIAGE_RESULT_FILE} for {context.entry.instance_id}") + else: + result = JudgeBasedEvaluationResult.create_raw(context, output=raw) + logger.info(f"Saved raw extensibility-request-triage result for {context.entry.instance_id} (scoring pending)") + + self.save_result(context, result) diff --git a/src/bcbench/operations/instruction_operations.py b/src/bcbench/operations/instruction_operations.py index 7ee7c089f..b5899c2ac 100644 --- a/src/bcbench/operations/instruction_operations.py +++ b/src/bcbench/operations/instruction_operations.py @@ -3,7 +3,7 @@ from bcbench.config import get_config from bcbench.dataset import BaseDatasetEntry -from bcbench.dataset.dataset_entry import _BugFixTestGenBase +from bcbench.dataset.dataset_entry import RepoGroundedEntry from bcbench.logger import get_logger from bcbench.types import AgentType @@ -83,7 +83,7 @@ def _get_source_instructions_path(profile: str) -> Path: return instructions_path -def copy_problem_statement_folder(entry: _BugFixTestGenBase, repo_path: Path) -> None: +def copy_problem_statement_folder(entry: RepoGroundedEntry, repo_path: Path) -> None: """ Copy problem statement folder to the testbed repository root. diff --git a/src/bcbench/types.py b/src/bcbench/types.py index 32626541d..ae80c7e10 100644 --- a/src/bcbench/types.py +++ b/src/bcbench/types.py @@ -189,7 +189,11 @@ class EvaluationCategory(StrEnum): TEST_GENERATION = "test-generation" CODE_REVIEW = "code-review" NL2AL = "nl2al" - # EVENT_REQUEST = "event-request" + # Implement an approved extensibility request (add an event/extension point) as an AL code change. + # The sibling ext-advisor category is planned but not yet implemented. + EXT_REQUEST_IMPLEMENT = "extensibility-request-implement" + # Triage a single extensibility request: emit managed labels, an advisory comment, and open/closed state. + EXT_REQUEST_TRIAGE = "extensibility-request-triage" @property def dataset_path(self) -> Path: @@ -204,12 +208,16 @@ def dataset_path(self) -> Path: return get_config().paths.dataset_dir / "codereview.jsonl" case EvaluationCategory.NL2AL: return get_config().paths.dataset_dir / "nl2al.jsonl" + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + return get_config().paths.dataset_dir / "extensibility_request_implement.jsonl" + case EvaluationCategory.EXT_REQUEST_TRIAGE: + return get_config().paths.dataset_dir / "extensibility_request_triage.jsonl" raise ValueError(f"Unknown evaluation category: {self}") @property def entry_class(self) -> type[BaseDatasetEntry]: - from bcbench.dataset import BugFixEntry, CodeReviewEntry, NL2ALEntry, TestGenEntry + from bcbench.dataset import BugFixEntry, CodeReviewEntry, ExtRequestImplementEntry, ExtRequestTriageEntry, NL2ALEntry, TestGenEntry match self: case EvaluationCategory.BUG_FIX: @@ -220,6 +228,10 @@ def entry_class(self) -> type[BaseDatasetEntry]: return CodeReviewEntry case EvaluationCategory.NL2AL: return NL2ALEntry + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + return ExtRequestImplementEntry + case EvaluationCategory.EXT_REQUEST_TRIAGE: + return ExtRequestTriageEntry raise ValueError(f"Unknown evaluation category: {self}") @@ -239,6 +251,10 @@ def result_class(self) -> type[BaseEvaluationResult]: return CodeReviewResult case EvaluationCategory.NL2AL: return JudgeBasedEvaluationResult + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + return JudgeBasedEvaluationResult + case EvaluationCategory.EXT_REQUEST_TRIAGE: + return JudgeBasedEvaluationResult raise ValueError(f"Unknown evaluation category: {self}") @@ -257,6 +273,10 @@ def summary_class(self) -> type[EvaluationResultSummary]: return CodeReviewResultSummary case EvaluationCategory.NL2AL: return JudgeBasedEvaluationResultSummary + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + return JudgeBasedEvaluationResultSummary + case EvaluationCategory.EXT_REQUEST_TRIAGE: + return JudgeBasedEvaluationResultSummary raise ValueError(f"Unknown evaluation category: {self}") @@ -274,12 +294,16 @@ def aggregate_class(self) -> type[LeaderboardAggregate]: return CodeReviewLeaderboardAggregate case EvaluationCategory.NL2AL: return JudgeBasedLeaderboardAggregate + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + return JudgeBasedLeaderboardAggregate + case EvaluationCategory.EXT_REQUEST_TRIAGE: + return JudgeBasedLeaderboardAggregate raise ValueError(f"Unknown evaluation category: {self}") @property def pipeline(self) -> EvaluationPipeline: - from bcbench.evaluate import BugFixPipeline, CodeReviewPipeline, NL2ALPipeline, TestGenerationPipeline + from bcbench.evaluate import BugFixPipeline, CodeReviewPipeline, ExtRequestImplementPipeline, ExtRequestTriagePipeline, NL2ALPipeline, TestGenerationPipeline match self: case EvaluationCategory.BUG_FIX: @@ -290,6 +314,10 @@ def pipeline(self) -> EvaluationPipeline: return CodeReviewPipeline() case EvaluationCategory.NL2AL: return NL2ALPipeline() + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + return ExtRequestImplementPipeline() + case EvaluationCategory.EXT_REQUEST_TRIAGE: + return ExtRequestTriagePipeline() raise ValueError(f"Unknown evaluation category: {self}") @@ -309,6 +337,10 @@ def evaluators(self) -> list[str]: return ["precision_score", "recall_score", "f1_score", "valid_review_output"] case EvaluationCategory.NL2AL: return ["lm_checklist"] + case EvaluationCategory.EXT_REQUEST_IMPLEMENT: + return ["lm_checklist"] + case EvaluationCategory.EXT_REQUEST_TRIAGE: + return ["lm_checklist"] raise ValueError(f"Unknown evaluation category: {self}") @@ -320,7 +352,7 @@ def core_score(self) -> str: return "ResolutionRate" case EvaluationCategory.CODE_REVIEW: return "F1Score" - case EvaluationCategory.NL2AL: + case EvaluationCategory.NL2AL | EvaluationCategory.EXT_REQUEST_IMPLEMENT | EvaluationCategory.EXT_REQUEST_TRIAGE: return "test_passed" raise ValueError(f"Unknown evaluation category: {self}") @@ -331,7 +363,7 @@ def requires_container(self) -> bool: match self: case EvaluationCategory.BUG_FIX | EvaluationCategory.TEST_GENERATION: return True - case EvaluationCategory.CODE_REVIEW | EvaluationCategory.NL2AL: + case EvaluationCategory.CODE_REVIEW | EvaluationCategory.NL2AL | EvaluationCategory.EXT_REQUEST_IMPLEMENT | EvaluationCategory.EXT_REQUEST_TRIAGE: return False raise ValueError(f"Unknown evaluation category: {self}") @@ -352,7 +384,7 @@ def runner(self) -> str: match self: case EvaluationCategory.BUG_FIX | EvaluationCategory.TEST_GENERATION: return "GitHub-BCBench" - case EvaluationCategory.CODE_REVIEW: + case EvaluationCategory.CODE_REVIEW | EvaluationCategory.EXT_REQUEST_IMPLEMENT | EvaluationCategory.EXT_REQUEST_TRIAGE: return "ubuntu-latest" case EvaluationCategory.NL2AL: return "windows-latest" diff --git a/tests/conftest.py b/tests/conftest.py index c3b53567b..f979e24b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ import pytest -from bcbench.dataset import BaseDatasetEntry, BugFixEntry, NL2ALEntry, TestEntry +from bcbench.dataset import BaseDatasetEntry, BugFixEntry, ExtRequestImplementEntry, ExtRequestTriageEntry, ManagedLabel, NL2ALEntry, TestEntry from bcbench.dataset.codereview import CodeReviewEntry, ReviewComment, Severity from bcbench.dataset.dataset_entry import EntryMetadata, _BugFixTestGenBase from bcbench.evaluate.review_parsing import parse_review_output @@ -350,3 +350,79 @@ def create_nl2al_entry( @pytest.fixture def sample_nl2al_entry() -> NL2ALEntry: return create_nl2al_entry() + + +def create_ext_implement_entry( + instance_id: str = "microsoftInternal__NAV-Ext_Request_Impl-30361", + repo: str = "microsoftInternal/NAV", + base_commit: str = VALID_BASE_COMMIT, + environment_setup_version: str = VALID_ENVIRONMENT_VERSION, + project_paths: list[str] | None = None, + patch: str = VALID_PATCH, + created_at: str = VALID_CREATED_AT, + expected: list[ChecklistAssertion] | None = None, +) -> ExtRequestImplementEntry: + if project_paths is None: + project_paths = ["App/Layers/W1/BaseApp"] + + if expected is None: + expected = [ChecklistAssertion(text="A new integration event publisher is added to the standard codeunit.", level="critical")] + + return ExtRequestImplementEntry( + instance_id=instance_id, + repo=repo, + base_commit=base_commit, + environment_setup_version=environment_setup_version, + project_paths=project_paths, + patch=patch, + created_at=created_at, + expected=expected, + ) + + +@pytest.fixture +def sample_ext_implement_entry(tmp_path: Path) -> Generator[ExtRequestImplementEntry]: + problem_dir = create_problem_statement_dir(tmp_path) + entry = create_ext_implement_entry() + + with patch.object(ExtRequestImplementEntry, "problem_statement_dir", property(lambda self: problem_dir)): + yield entry + + +def create_ext_triage_entry( + instance_id: str = "microsoftInternal__NAV-Ext_Request_Triage-29447", + repo: str = "microsoftInternal/NAV", + base_commit: str = VALID_BASE_COMMIT, + environment_setup_version: str = VALID_ENVIRONMENT_VERSION, + project_paths: list[str] | None = None, + created_at: str = VALID_CREATED_AT, + title: str = '[Event Request] Codeunit 5880 "Phys. Invt. Order-Finish"', + description: str = "Please add an integration event in CreateOrderTrackingBufferLines.", + comments: str = "", + current_labels: list[ManagedLabel] | None = None, + expected: list[ChecklistAssertion] | None = None, +) -> ExtRequestTriageEntry: + return ExtRequestTriageEntry( + instance_id=instance_id, + repo=repo, + base_commit=base_commit, + environment_setup_version=environment_setup_version, + project_paths=project_paths if project_paths is not None else ["App/Layers/W1/BaseApp"], + created_at=created_at, + title=title, + description=description, + comments=comments, + current_labels=current_labels if current_labels is not None else [], + expected=expected + if expected is not None + else [ + {"text": "The labels_to_set is exactly: SCM, event-request.", "level": "critical"}, + {"text": "The issue_state is open.", "level": "critical"}, + {"text": "The advisory comment confirms the event request is valid and will be routed to the SCM team.", "level": "expected"}, + ], + ) + + +@pytest.fixture +def sample_ext_triage_entry() -> ExtRequestTriageEntry: + return create_ext_triage_entry() diff --git a/tests/test_agent_skills.py b/tests/test_agent_skills.py index f6fa2eca8..e12a6ada0 100644 --- a/tests/test_agent_skills.py +++ b/tests/test_agent_skills.py @@ -44,14 +44,14 @@ def test_setup_agent_skills(): # Verify file content matches if item.is_file(): - assert target_item.read_text() == item.read_text(), f"Content mismatch for {item.name}" + assert target_item.read_text(encoding="utf-8") == item.read_text(encoding="utf-8"), f"Content mismatch for {item.name}" elif item.is_dir(): # For directories, verify all files match recursively for source_file in item.rglob("*"): if source_file.is_file(): target_file = target_item / source_file.relative_to(item) assert target_file.exists(), f"{target_file} should exist" - assert target_file.read_text() == source_file.read_text(), f"Content mismatch for {target_file}" + assert target_file.read_text(encoding="utf-8") == source_file.read_text(encoding="utf-8"), f"Content mismatch for {target_file}" def test_nonexistent_skills(): diff --git a/tests/test_type_exhaustiveness.py b/tests/test_type_exhaustiveness.py index 06169ba29..d97ffa572 100644 --- a/tests/test_type_exhaustiveness.py +++ b/tests/test_type_exhaustiveness.py @@ -1,6 +1,6 @@ from pathlib import Path -from bcbench.dataset import BugFixEntry, CodeReviewEntry, NL2ALEntry +from bcbench.dataset import BugFixEntry, CodeReviewEntry, ExtRequestImplementEntry, ExtRequestTriageEntry, NL2ALEntry from bcbench.dataset.codereview import ReviewComment, Severity from bcbench.types import AgentType, EvaluationCategory @@ -40,7 +40,9 @@ def test_all_categories_have_aggregate_classes(): assert issubclass(aggregate_cls, LeaderboardAggregate) -def test_all_categories_handled_in_get_expected_output(sample_dataset_entry_with_problem_statement: BugFixEntry, sample_nl2al_entry: NL2ALEntry): +def test_all_categories_handled_in_get_expected_output( + sample_dataset_entry_with_problem_statement: BugFixEntry, sample_nl2al_entry: NL2ALEntry, sample_ext_implement_entry: "ExtRequestImplementEntry", sample_ext_triage_entry: "ExtRequestTriageEntry" +): for category in EvaluationCategory: entry_cls = category.entry_class if entry_cls == CodeReviewEntry: @@ -56,6 +58,10 @@ def test_all_categories_handled_in_get_expected_output(sample_dataset_entry_with ) elif entry_cls is NL2ALEntry: entry = sample_nl2al_entry + elif entry_cls is ExtRequestImplementEntry: + entry = sample_ext_implement_entry + elif entry_cls is ExtRequestTriageEntry: + entry = sample_ext_triage_entry else: # Reconstruct entry as the category-specific type so get_expected_output() works entry = entry_cls.model_validate(sample_dataset_entry_with_problem_statement.model_dump(by_alias=True))