[Bug]: E-Documents for Germany - FindEDocumentService always selects last matching service, ignoring which service triggered the export#9642
Conversation
…te FindEDocumentService The XRechnung and ZUGFeRD exports located the E-Document Service with a FindLast() lookup by document format. When more than one service shares a format the export always picked the last one by code instead of the service that actually triggered it, so settings such as "Embed PDF in export" were read from the wrong service. The triggering service is now handed to the export instead: * XRechnung: "XRechnung Format" passes it to "Export XRechnung Document" through SetEDocumentService before Run. * ZUGFeRD: the XML is built during report rendering, in a separate "Export ZUGFeRD Document" instance spawned by the report extension, which a setter called before Run cannot reach. The new "ZUGFeRD Export Context" codeunit (EventSubscriberInstance = Manual, bound per session rather than SingleInstance so parallel exports do not share state) carries the service from "ZUGFeRD Format" into the four posted-document report extensions, which push it onto their own instance. It is public so partners can do the same in customized report extensions. HasContext() reports true only once a non-blank service was pushed, so a bound-but-empty context cannot suppress the fallback. FindEDocumentService and OnAfterFindEDocumentService are obsoleted in both codeunits but deliberately kept working under #if not CLEAN29: the calls remain, so the event still fires for existing subscribers, and when no service was provided the original FindLast lookup still runs unchanged. Customized reports that do not use the context keep behaving exactly as before. As of CLEAN29 the functions, the events and the fallback are removed and callers must provide the service through SetEDocumentService. Tests cover the new and the legacy path for both formats: the triggering service wins over a later-sorting service, the FindLast fallback still applies when no service was provided, OnAfterFindEDocumentService still fires on both paths, and the ZUGFeRD context carries a service, refuses a blank one and clears on Stop. Also adds an "E-Document for Germany" workspace file, matching the per-app workspaces the other Microsoft apps already track (EDocumentCore, PEPPOL, PeppolBE, Subscription Billing and others), and removes the duplicated "features": [ "TranslationFile" ] property from the demo data app.json, where it was declared twice. Fixes microsoft#8414 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This PR adds fresh compatibility-routing logic inside methods it simultaneously marks obsolete: FindEDocumentService in both "Export XRechnung Document" and "Export ZUGFeRD Document" now contains new service-selection/event-routing behavior (the EDocumentServiceProvided branch) instead of only shrinking toward removal. New behavior belongs on the supported SetEDocumentService/context path; the obsolete methods should only forward to it during the warning window rather than growing new logic themselves. Knowledge: Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.13.4 |
| end; | ||
|
|
||
| #region Internal pub/sub bridge - the bound instance captures writes and answers reads | ||
| [IntegrationEvent(false, false)] |
There was a problem hiding this comment.
OnSetEDocumentService, OnHasContext, and OnGetEDocumentService are described as an internal pub/sub bridge, but they are declared as local [IntegrationEvent] publishers on a public codeunit. Local integration events remain subscribable by dependent extensions, so shipping them as IntegrationEvent turns these implementation-detail hooks into an external subscriber contract. If this bridge is intended to stay module-private, use [InternalEvent] before release.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
[InternalEvent(false, false)]
local procedure OnSetEDocumentService(NewEDocumentService: Record "E-Document Service")
begin
end;
[InternalEvent(false, false)]
local procedure OnHasContext(var Result: Boolean)
begin
end;
[InternalEvent(false, false)]
local procedure OnGetEDocumentService(var EDocumentService: Record "E-Document Service")
begin
end;Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.13.4
| EDocumentService.Modify(); | ||
| end; | ||
|
|
||
| local procedure CreateTrailingXRechnungService(EmbedPDFInExport: Boolean): Code[20] |
There was a problem hiding this comment.
CreateTrailingXRechnungService introduces an extra E-Document Service into a test codeunit whose Initialize() rebuilds the baseline service only once. The new tests remove the trailing service only after the export/assertion path (RemoveTrailingXRechnungServices called post-assert), so a failing export or assertion leaves the extra service behind and makes later tests in this codeunit order-dependent. Recreate the service fixture per test or move the trailing-service cleanup into teardown that still runs when the test body fails.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.13.4
| TempXMLBuffer.LoadFromStream(PdfAttachmentStream); | ||
| end; | ||
|
|
||
| local procedure CreateTrailingZUGFeRDService(): Code[20] |
There was a problem hiding this comment.
CreateTrailingZUGFeRDService has the same isolation problem: it adds a second E-Document Service, while Initialize() resets the fixture only on the first test in the codeunit. Because cleanup happens only after the export/assertion path, a failing test can leave the trailing service in place and contaminate later ZUGFeRD tests. Recreate the service fixture per test or move the trailing-service cleanup into teardown that still runs when the test body fails.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.13.4
What & why
The XRechnung and ZUGFeRD exports located the E-Document Service with a FindLast() lookup by document format. When more than one service shares a format the export always picked the last one by code instead of the service that actually triggered it, so settings such as "Embed PDF in export" were read from the wrong service.
The triggering service is now handed to the export instead:
FindEDocumentService and OnAfterFindEDocumentService are obsoleted in both codeunits but deliberately kept working under #if not CLEAN29: the calls remain, so the event still fires for existing subscribers, and when no service was provided the original FindLast lookup still runs unchanged. Customized reports that do not use the context keep behaving exactly as before. As of CLEAN29 the functions, the events and the fallback are removed and callers must provide the service through SetEDocumentService.
Tests cover the new and the legacy path for both formats: the triggering service wins over a later-sorting service, the FindLast fallback still applies when no service was provided, OnAfterFindEDocumentService still fires on both paths, and the ZUGFeRD context carries a service, refuses a blank one and clears on Stop.
Also adds an "E-Document for Germany" workspace file, matching the per-app workspaces the other Microsoft apps already track (EDocumentCore, PEPPOL, PeppolBE, Subscription Billing and others), and removes the duplicated "features": [ "TranslationFile" ] property from the demo data app.json, where it was declared twice.
Linked work
Fixes #8414
How I validated this
What I tested and the outcome (required — be specific: scenarios, commands, screenshots for UI changes)
I have created two E-document services for xRechnung, once with embedded PDF and once without and confirmed that functionality works as expected.
Risk & compatibility
Risk & compatibility
Behavior change (XRechnung): where several E-Document Services share the XRechnung format, the
export now uses the triggering service instead of the last one by code — service-dependent settings
like Embed PDF in export may resolve differently. This is the #8414 fix. ZUGFeRD output is
unchanged (its export reads no service field); the context added there is groundwork only.
Deprecation:
FindEDocumentService/OnAfterFindEDocumentServiceare obsoleted but stay fullyfunctional under
#if not CLEAN29— the calls remain, the event still fires, and the legacyFindLast()fallback still runs when no service is provided, so existing subscribers and customizedreports are unaffected today. At CLEAN29 the fallback disappears: callers that don't supply a
service via
SetEDocumentService(partners' customized reports reading"ZUGFeRD Export Context",as the standard report extensions do) would then export with a blank service. That's why the new
codeunit is public.
New public surface: codeunit
11039 "ZUGFeRD Export Context"andSetEDocumentServiceon bothexport codeunits. Session-scoped via
BindSubscription(notSingleInstance, so parallel UI and JobQueue exports don't share state).
No schema, upgrade, permission, telemetry or feature-flag impact.