Skip to content

[Bug]: E-Documents for Germany - FindEDocumentService always selects last matching service, ignoring which service triggered the export (backport to releases/29.x) - #12155

Open
Miljan Milosavljević (miljance) wants to merge 1 commit into
microsoft:releases/29.xfrom
miljance:EDocDEFindEDocumentServiceFor29.x

Conversation

@miljance

Copy link
Copy Markdown
Contributor

What & why

  • This is a backport of PR for 30: to 29.x [Bug]: E-Documents for Germany - FindEDocumentService always selects last matching service, ignoring which service triggered the export #9642
  • 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 CLEAN30: 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 CLEAN30 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.

Linked work

Fixes #8414
AB#651618

How I validated this

  • I read the full diff and it contains only changes I intended.
  • I built the affected app(s) locally with no new analyzer warnings.
  • I ran the change in Business Central and confirmed it behaves as expected.
  • I added or updated tests for the new behavior, or explained below why none are needed.

What I tested and the outcome (required — be specific: scenarios, commands, screenshots for UI changes)

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 therefore 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 / OnAfterFindEDocumentService are obsoleted but stay fully functional under #if not CLEAN30: the calls remain, the event still fires, and the legacy FindLast() fallback still runs when no service is provided, so existing subscribers and customized reports are unaffected. At CLEAN30 the fallback disappears: callers that don't supply a service via SetEDocumentService would then export with a blank service. Partners' customized reports can supply it by reading "ZUGFeRD Export Context" the way the standard report extensions do, which is why the new codeunit is public.

Obsolete tag on this branch: the members keep ObsoleteTag = '30.0' and the CLEAN30 guard exactly as merged to main. That matches the existing convention for obsoletions backported to releases/29.x (e.g. Expense Event Subscriber AT: '30.0' under #if not CLEAN30). The build scripts allow 30.0 as an obsolete-tag version on this branch, and the Clean build mode here (CLEAN25–CLEAN29) keeps the legacy path compiled.

New public surface: codeunit 11042 "ZUGFeRD Export Context" and SetEDocumentService on both export codeunits. Session-scoped via BindSubscription (not SingleInstance, so parallel UI and Job Queue exports don't share state).

No schema, upgrade, permission, telemetry or feature-flag impact. No conflict resolution was needed on releases/29.x.

🤖 Generated with Claude Code

…last matching service, ignoring which service triggered the export (microsoft#9642)

<!--
Thanks for contributing to BCApps!

A few things before you hit "Create pull request":
- Your PR must link to an approved issue. New here? See CONTRIBUTING.md.
- You must have built and run your change yourself. CI is a safety net,
not a substitute.
- If you used AI or an agent to write this PR, you are still the author.
Read the diff,
  build it, and try it before requesting review.

Contributing guide:
https://github.com/microsoft/BCApps/blob/main/CONTRIBUTING.md
Local dev environment:
https://github.com/microsoft/BCApps/blob/main/LOCAL_DEV_ENV.md
-->

## What & why

<!-- A few sentences: what does this change do, and what problem does it
solve? -->
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.
## Linked work

<!-- Required: link an approved GitHub issue using "Fixes #<number>".
Microsoft contributors: also link the ADO work item with "AB#<number>"
if you have one. -->

Fixes microsoft#8414

[AB#651618](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/651618)

## How I validated this

- [x] I read the full diff and it contains only changes I intended.
- [x] I built the affected app(s) locally with no new analyzer warnings.
- [x] I ran the change in Business Central and confirmed it behaves as
expected.
- [x] I added or updated tests for the new behavior, or explained below
why none are needed.

**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.

<!-- Example:
- Ran the new "Post and Send" action on a sales invoice in a fresh
container; document posted and email queued (see screenshot).
- New unit tests in MyFeatureTest.Codeunit.al pass locally; full module
test suite green.
- No tests added because change is comment-only / refactor with existing
coverage. -->

## Risk & compatibility

<!-- Anything reviewers should watch for: breaking changes, upgrade/data
impact, permissions,
telemetry, feature flags, follow-up work. Write "None" if there's
nothing to call out. -->
## 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 microsoft#8414
fix. **ZUGFeRD output is
unchanged** (its export reads no service field); the context added there
is groundwork only.

**Deprecation:** `FindEDocumentService` / `OnAfterFindEDocumentService`
are obsoleted but stay fully
functional under `#if not CLEAN29` — the calls remain, the event still
fires, and the legacy
`FindLast()` fallback still runs when no service is provided, so
existing subscribers and customized
reports 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"` and
`SetEDocumentService` on both
export codeunits. Session-scoped via `BindSubscription` (not
`SingleInstance`, so parallel UI and Job
Queue exports don't share state).

No schema, upgrade, permission, telemetry or feature-flag impact.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 0b91a89)
@github-actions github-actions Bot added the From Fork Pull request is coming from a fork label Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Issue #8414 is not valid. Please make sure you link an issue that exists, is open and is approved.

@github-actions github-actions Bot added the Team: Finance GitHub request for Finance area label Sep 30, 2026
@github-actions github-actions Bot added needs-approval Workflow runs require maintainer approval to start and removed needs-approval Workflow runs require maintainer approval to start labels Sep 30, 2026
@alexei-dobriansky

Copy link
Copy Markdown
Contributor

Good Sense Reviewer - Round 1

Recommendation: Accept with Suggestions

What this PR does

This backport passes the triggering E-Document Service into XRechnung and carries it through a session-bound ZUGFeRD context, while preserving the legacy lookup and event behavior until CLEAN30.

The change addresses the root cause: export settings now come from the service that started the export, not the last service with the same format. The report extensions transfer the ZUGFeRD context into their local exporter instance, and the compatibility paths, event behavior, instance reset, and multi-service cases have focused tests. After normalizing the release guard and object ID, the patch matches the integrated implementation.

Problem-solution fit

Fit: Strong

The reported multi-service scenario is fully addressed at the service-selection point. The tests cover both XRechnung PDF settings, the legacy fallback, event compatibility, reused instances, and the ZUGFeRD report-extension path.

Suggestions

S1 (🟠 Moderate): Run the target-branch build and tests
These changes use release-specific CLEAN30 guards and a different object ID, but this head has not been compiled or tested on releases/29.x. Run the affected app build and the added XRechnung and ZUGFeRD tests before merge.

Risk assessment and necessity

Risk: The runtime change affects export service selection and adds public setter/context surfaces. Compatibility is preserved until CLEAN30, and the normalized patch matches the integrated implementation, but current automation has not run an app build or the tests on this target branch.

Necessity: The change is needed because multiple services with the same format can otherwise apply settings from the wrong service. The backport scope is focused and includes compatibility fallbacks and regression coverage.


[AI-PR-REVIEW] version=1 promptVersion=4 system=github pr=12155 round=1 by=alexei-dobriansky at=2026-09-30T19:55:08Z lastSha=44dc022a75788dd98dc700afeedc881fe0635ebc reviewKey=b38eae4bc00188253d199bdffe8968557dcaf310b0a9efda50067239d026094b suggestions=S1@c0ef3f0c

@github-actions github-actions Bot added needs-approval Workflow runs require maintainer approval to start and removed needs-approval Workflow runs require maintainer approval to start labels Sep 30, 2026
@github-actions github-actions Bot added needs-approval Workflow runs require maintainer approval to start and removed needs-approval Workflow runs require maintainer approval to start labels Oct 1, 2026
@github-actions github-actions Bot added the needs-approval Workflow runs require maintainer approval to start label Oct 2, 2026

This branch was successfully deployed

1 active deployment
triage — 44dc022a Deployed Sep 30, 2026 by miljance via Classify team ownership #6351
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

From Fork Pull request is coming from a fork needs-approval Workflow runs require maintainer approval to start Team: Finance GitHub request for Finance area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants