Skip to content

E-Document: process all records in OnAfterSend and OnAfterSendToEMail#7785

Open
jeffreybulanadi wants to merge 5 commits into
microsoft:mainfrom
jeffreybulanadi:fix/5650-edocument-onaftersend-loop-all-records
Open

E-Document: process all records in OnAfterSend and OnAfterSendToEMail#7785
jeffreybulanadi wants to merge 5 commits into
microsoft:mainfrom
jeffreybulanadi:fix/5650-edocument-onaftersend-loop-all-records

Conversation

@jeffreybulanadi
Copy link
Copy Markdown
Contributor

@jeffreybulanadi jeffreybulanadi commented Apr 21, 2026

Summary

When multiple posted documents are selected and sent using a Document Sending Profile configured for e-documents, only the first document was created as an e-document.

Root Cause

In EDocumentSubscribers.Codeunit.al, both OnAfterSendEDocument and OnAfterSendToEMailEDocument processed the incoming RecordVariant as a single record without iterating through all records in the set. The check and creation calls operated on the same fixed first record on every call.

Fix

Use RecordRef.GetTable + FindSet/Next to iterate over every record in the variant. In each iteration, the current RecordRef position is passed to IsEDocumentCreatedForRecord and CreateEDocumentFromPostedDocument so each document is independently checked and created.

For OnAfterSendToEMailEDocument, only the e-document creation loop is changed. The subsequent ProcessEDocumentAsEmail call continues to receive the original RecordVariant as it handles the email attachment step for the full selection.

Files Changed

  • src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al

Fixes #5650

Fixes AB#632028

…il subscribers

When multiple posted documents are selected and sent using a Document
Sending Profile configured for e-documents, only the first document
was being created as an e-document. The root cause was that
OnAfterSendEDocument and OnAfterSendToEMailEDocument each processed
the incoming RecordVariant as a single record instead of iterating
through all records.

Use RecordRef.GetTable + FindSet/Next to iterate over every record
in the variant. Pass the RecordRef for each iteration to both
IsEDocumentCreatedForRecord and CreateEDocumentFromPostedDocument so
each record is independently checked and created.

Fixes microsoft#5650
@jeffreybulanadi jeffreybulanadi requested a review from a team as a code owner April 21, 2026 20:09
@github-actions github-actions Bot added AL: Apps (W1) Add-on apps for W1 From Fork Pull request is coming from a fork labels Apr 21, 2026
@Groenbech96
Copy link
Copy Markdown
Contributor

This change needs to have tests that cover the scenario.
Also the negative case that a single document, not multi selected, still creates a document.

@Groenbech96 Groenbech96 self-assigned this Apr 22, 2026
@Groenbech96 Groenbech96 added Linked Issue is linked to a Azure Boards work item Approved The issue is approved labels Apr 22, 2026
@github-actions github-actions Bot added this to the Version 29.0 milestone Apr 22, 2026
Add EDocSendSubscriberTest codeunit (ID 139897) with two test procedures
that exercise the RecordRef.FindSet/Next loop added in the fix:

- SendMultiplePostedInvoicesCreatesEDocumentForEach: posts two invoices
  without auto E-Document creation, then sends both together via
  DocumentSendingProfile.Send() and asserts EDocument.Count() = 2.
- SendSinglePostedInvoiceCreatesOneEDocument: regression guard ensuring
  a single-invoice send still produces exactly one E-Document.

Pattern follows EDocEmailTests: disable DSP before posting, re-enable
before sending, bind EDocImplState subscriber so the mock format produces
non-empty TempBlob content through the processing pipeline.

Closes microsoft#5650
@jeffreybulanadi
Copy link
Copy Markdown
Contributor Author

Thank you for the feedback! Both test scenarios are already included in the second commit of this PR (2514b3a).

New test file: src/Apps/W1/EDocument/Test/src/Processing/EDocSendSubscriberTest.Codeunit.al (codeunit 139897 "E-Doc. Send Subscriber Test")

Test 1 - Multi-document scenario (the bug case):
SendMultiplePostedInvoicesCreatesEDocumentForEach

  • Posts two invoices without auto E-Document creation (DSP disabled during post)
  • Re-enables the extended E-Document service flow
  • Calls DocumentSendingProfile.Send() with both invoices in the filtered RecordRef
  • Asserts EDocument.Count() = 2 -- one E-Document per invoice

Test 2 - Single-document regression guard:
SendSinglePostedInvoiceCreatesOneEDocument

  • Same setup pattern but with a single invoice
  • Asserts EDocument.Count() = 1

Both tests follow the pattern from EDocEmailTests (bind EDocImplState, use LibraryEDoc.SetupStandardSalesScenario, disable DSP before posting, re-enable before sending). Local verification was also done on a BC 28 W1 Docker instance: Invoke-NAVCodeunit on the equivalent test codeunit completed with no failures in approximately 5 seconds.

Groenbech96
Groenbech96 previously approved these changes Apr 22, 2026
darjoo
darjoo previously approved these changes Apr 22, 2026
…irective

AL0792: The using directive was flagged as unused by the BC29 compiler.
Assert is resolved without it in this compilation context.
Copilot AI review requested due to automatic review settings April 22, 2026 23:27
@jeffreybulanadi jeffreybulanadi dismissed stale reviews from darjoo and Groenbech96 via 707bb4a April 22, 2026 23:27
@jeffreybulanadi jeffreybulanadi force-pushed the fix/5650-edocument-onaftersend-loop-all-records branch from 707bb4a to 88ceae2 Compare April 22, 2026 23:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes E-Document creation when sending multiple posted documents via a Document Sending Profile by iterating over all records in the RecordVariant set, and adds regression tests to validate multi-record vs single-record sending.

Changes:

  • Iterate through the full RecordVariant set in OnAfterSend and OnAfterSendToEMail subscribers to create an E-Document per selected posted document.
  • Add integration tests that verify E-Documents are created for both multi-invoice and single-invoice send scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al Updates event subscribers to loop over all records in the passed RecordVariant before checking/creating E-Documents.
src/Apps/W1/EDocument/Test/src/Processing/EDocSendSubscriberTest.Codeunit.al Adds new integration tests covering multi-record and single-record send behavior for posted sales invoices.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al Outdated
Comment thread src/Apps/W1/EDocument/App/src/Processing/EDocumentSubscribers.Codeunit.al Outdated
… RecordRef variants

Replace RecordRef.GetTable(RecordVariant) with the TypeHelper pattern already
used in EDocumentProcessing.Codeunit.al. GetTable only accepts a Record variant
and throws a runtime error when the caller passes a RecordRef variant.

TypeHelper.CopyRecVariantToRecRef handles both IsRecord() and IsRecordRef() cases,
preventing regressions when downstream callers pass a RecordRef. Add a guard
clause that exits early if the variant is neither type, consistent with the
existing pattern in EDocumentProcessing.GetTypeFromSourceDocument.
@JesperSchulz JesperSchulz added the Integration GitHub request for Integration area label Apr 24, 2026
@JesperSchulz
Copy link
Copy Markdown
Contributor

Re-running CI.

@JesperSchulz JesperSchulz reopened this Apr 28, 2026
@jeffreybulanadi jeffreybulanadi changed the title fix(E-Document): loop all records in OnAfterSend and OnAfterSendToEMail subscribers E-Document: process all records in OnAfterSend and OnAfterSendToEMail Apr 30, 2026
@Groenbech96
Copy link
Copy Markdown
Contributor

@copilot pull main into branch

@Groenbech96 Groenbech96 closed this Jun 1, 2026
@Groenbech96 Groenbech96 reopened this Jun 1, 2026
@Groenbech96 Groenbech96 requested a review from a team as a code owner June 1, 2026 11:54
@github-actions github-actions Bot added the needs-approval Workflow runs require maintainer approval to start label Jun 5, 2026

// [THEN] One E-Document is created for each of the two posted invoices
Assert.AreEqual(2, EDocument.Count(), 'Expected one E-Document per sent invoice.');
UnbindSubscription(EDocImplState);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

UnbindSubscription missing from try/finally

Both test procedures call UnbindSubscription(EDocImplState) at the end of the method body, but not inside a try/finally block. If any assertion or runtime error is thrown before reaching line 81 (or 131), the subscription remains bound for the remainder of the test session, potentially causing cascading failures in unrelated test codeunits.

Recommendation:

  • Wrap the test body (after BindSubscription) in a try/finally block and call UnbindSubscription(EDocImplState) in the finally branch to guarantee cleanup regardless of test outcome.
Suggested change
UnbindSubscription(EDocImplState);
BindSubscription(EDocImplState);
try
// ... test body ...
finally
UnbindSubscription(EDocImplState);
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why


// [THEN] Exactly one E-Document is created for the invoice
Assert.AreEqual(1, EDocument.Count(), 'Expected exactly one E-Document for a single sent invoice.');
UnbindSubscription(EDocImplState);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟠\ High\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

UnbindSubscription missing from try/finally

Same subscription-leak risk as the first test: UnbindSubscription(EDocImplState) is called at the end of SendSinglePostedInvoiceCreatesOneEDocument but not inside a finally block, leaving the subscription active if the test errors out mid-run.

Recommendation:

  • Wrap the test body in a try/finally block and place UnbindSubscription in the finally branch.
Suggested change
UnbindSubscription(EDocImplState);
BindSubscription(EDocImplState);
try
// ... test body ...
finally
UnbindSubscription(EDocImplState);
end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

EDocument.DeleteAll();
EDocumentServiceStatus.DeleteAll();
EDocumentService.DeleteAll();
DocumentSendingProfile.DeleteAll();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

DeleteAll on DocumentSendingProfile pollutes shared state

DocumentSendingProfile.DeleteAll() removes every document sending profile in the database. If another test codeunit in the same session runs after this and expects a default sending profile to already exist, it will fail with unexpected results. The cleanup at the start of each test covers the E-Document tables, but DocumentSendingProfile is shared system data.

Recommendation:

  • Instead of DeleteAll(), isolate the test by creating a new profile with a distinct code, use it explicitly, and delete only that record in cleanup (or inside a finally block).
Suggested change
DocumentSendingProfile.DeleteAll();
// Create a dedicated test profile instead of DeleteAll
DocumentSendingProfile.Init();
DocumentSendingProfile.Code := 'TEST-EDOC';
DocumentSendingProfile.Insert();

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

EDocument.DeleteAll();
EDocumentServiceStatus.DeleteAll();
EDocumentService.DeleteAll();
DocumentSendingProfile.DeleteAll();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Performance} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

DeleteAll on DocumentSendingProfile pollutes shared state

Same broad DocumentSendingProfile.DeleteAll() in SendSinglePostedInvoiceCreatesOneEDocument carries the same cross-test contamination risk described in the first test.

Recommendation:

  • Scope the document sending profile to a test-specific code and clean up only that record.
Suggested change
DocumentSendingProfile.DeleteAll();
// Create a dedicated test profile instead of DeleteAll
DocumentSendingProfile.Init();
DocumentSendingProfile.Code := 'TEST-EDOC';
DocumentSendingProfile.Insert();

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

@github-actions github-actions Bot removed the needs-approval Workflow runs require maintainer approval to start label Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AL: Apps (W1) Add-on apps for W1 Approved The issue is approved From Fork Pull request is coming from a fork Integration GitHub request for Integration area Linked Issue is linked to a Azure Boards work item

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: e-Documents: EventSubscriber OnAfterSend does not loop the RecordVariant variable

5 participants