Skip to content

[29.0] Backport Travel Request API lifecycle and employee/report alignment - #11652

Open
Claudiu Ciumedean (claudiuciumedean) wants to merge 3 commits into
releases/29.0from
backport/travel-request-api-29.0
Open

Claudiu Ciumedean (claudiuciumedean) wants to merge 3 commits into
releases/29.0from
backport/travel-request-api-29.0

Conversation

@claudiuciumedean

@claudiuciumedean Claudiu Ciumedean (claudiuciumedean) commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

What & why

Backports both Travel Request API changes together to releases/29.0, in their original order:

Linked work

Backport of #11007 and #11333. AB#626966.

Existing test-fixture follow-ups remain tracked by AB#650277 and AB#650245; this backport does not resolve them.

Extends the Expense Agent beta API so travel requests can be managed
through the same scoped OData model as expenses and expense reports.

- Advertises the `travelRequest` capability.
- Adds owner-scoped `travelRequests` navigation under `expenseUser`,
linked by SystemId through a GUID FlowFilter. The linked employee number
is resolved internally; `travelRequest.requestedBy` remains an Employee
No.
- Adds approver-scoped Released travel requests under `approverView`,
linked by SystemId through a GUID FlowFilter while preserving the
existing approval-assignment rules. Default submitter numbers are quoted
so filter operators inside identifiers remain literal.
- Exposes detail type, expense category, description, currency, and
expected amounts.
- Normalizes the configured local currency on header/detail API writes
and reads using the existing currency helper, while retaining
foreign-currency and status validation.
- Preserves the original Expense Management roles and D365 permission
extensions. Required operations use scoped Permissions on the Travel
Request codeunit and API/UI pages; the existing agent dependency set
supplies only indirect request/detail writes and ledger-link deletion,
without SUPER or wildcard grants.
- Adds bound submit, approve, and reject actions with owner/approver
authorization, status guards, audit fields, and rejection reasons.
- Prevents ordinary PATCH requests from changing status or transferring
ownership.
- Creates one linked expense report when a travel request is approved,
including automatic approval when the agent is disabled.
- Keeps nested details and travelers writable while preventing their
parent key from being changed.
- Automatically creates the requested-for traveler after insertion.
- Blocks deletion while any unposted or posted expense report header or
line references the Travel Request. Indexed lookups protect line-only
references and zero-net-spend history; ErrorInfo provides a Show it
action to the blocking record. Financial documents are never
cascade-deleted.
- Emits distinct submit, approve, automatic-approve, and reject usage
events with no user/request identifiers or rejection text.

- Preserves supplied expected dates on POST and validates PATCH against
the final effective date pair, retaining stored dates for omitted fields
and rejecting invalid ranges.

No Travel Request UI approval or rejection actions are added.

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

- [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.
- [ ] 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.

The current app and test projects compiled successfully in Default and
CLEAN25;CLEAN26;CLEAN27;CLEAN28;CLEAN29;CLEAN30 configurations with all
four repository analyzers and unchanged warning sets. The published
permission and filter-quoting corrections match those compiled sources.
Runtime tests were not executed locally and nothing was deployed to a BC
tenant. No passing end-to-end Travel Request run is claimed.

Coverage includes lifecycle authorization, linked-report creation and
deletion protection, permission boundaries, API payloads, owner
immutability, and the expected posting-preview path. Duplicate traveler
inserts were removed from the API and permission fixtures.

The eleven HTTP methods in codeunit 148347 `Travel Requests API Test`
and `TravelRequestCapabilityEnabledViaAPI` are excluded from BCApps CI
in `Expense_Agent_Tests.DisabledTest.json`, as [requested in
review](#11007 (comment)).
They are retained and compiled, not deleted.

The setup must be committed so the separate OData session can see it.
Execution also requires an authenticated OData endpoint, a dedicated
test company and compatible disabled test isolation. The dedicated
codeunit sets `RequiredTestIsolation = Disabled`.

**Re-enable criteria:** provision that HTTP execution environment in CI,
run the scenarios with committed fixtures and cleanup between runs, then
remove the twelve exclusions once they pass there. Run legacy endpoint
cases only in the non-CLEAN30 configuration. No alternative automated
HTTP runner has been established. These prerequisites and the removal
step are also documented beside the affected AL tests.

Business-logic, permission, and direct page-action tests remain enabled.
This follow-up adds twelve enabled in-process regressions for app-owned
role access, indirect detail/header updates, posted/line-only reference
protection, GUID owner scoping, and literal wildcard/OR characters in
approver-filter user numbers, and extends existing GUID approver and
HTTP scenarios. The direct page-action names explicitly distinguish them
from HTTP coverage.

- The API remains `beta`. Approve and reject require the acting approver
expense-user number; submit requires the acting submitter expense-user
number.
- Schema synchronization is required for the submission/approval audit
fields, GUID scoping filters, and new report-reference indexes. No
second persisted ownership key or ownership backfill is introduced.
- The beta contract intentionally requires `requestedBy` on POST and
rejects subsequent reassignment. Clients must supply the employee number
at creation rather than assigning it later. Resending the unchanged
owner on PATCH remains supported.
- Status changes require submit/approve/reject actions.
- Expense report creation is idempotent by Travel Request number and
requires an approved Travel Request.
- Automatic approval is allowed only while Expense Agent is disabled.
- The currency fix is included. The subsequent CR pass adds explicit
date/currency tooltips without changing the date-pair logic or currency
normalization.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit 96a6e99)
…lifecycle (#11333)

Aligns the Expense Agent travel request and expense report API flow used
by the web client.

- Adds a filterable `isExpenseUser` field to the Employees API.
- Keeps traveler writes employee-based while mapping Employee numbers to
Expense Users internally.
- Adds read-only `employees` navigation for a travel request's
travelers, selected by the request SystemId.
- Ensures travel request approval creates an Expense Report for the
`Requested For` Expense User when needed and fails the transaction if
required creation does not persist. Existing matching posted history
permits reapproval without creating another report.
- Adds owner-scoped report recreation for approved requests after draft
deletion. Existing drafts and posted header/line history for the same
request/user prevent recreation and provide actionable errors.
- Adds the indirect permissions and nonunique lookup keys required by
these operations.
- Adds object-level self-modify permission to BaseApp table 6840, Spend
Request, so its trusted total-update methods can use callers' existing
indirect rights. No direct user grants or entitlement changes are
introduced.

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

Tracks
[AB#650277](https://dev.azure.com/dynamicssmb2/Dynamics%20SMB/_workitems/edit/650277):
repair and re-enable eight posted-history tests. This PR quarantines
those tests; it does not resolve the fixture bug.

Also tracks
[AB#650245](https://dev.azure.com/dynamicssmb2/Dynamics%20SMB/_workitems/edit/650245):
the three unrelated, existing Expense Management role tests are
individually quarantined for later repair. The travel-request permission
tests remain enabled.

**Review follow-up (2026-09-16, commit 66aacb5)**

- Set Internal access on the two Employee helper fields (not the
tableextension object) and added a non-unique Expense User key on
Employee No.
- Expense Agent and its dependent test app both compiled against the
changed package. Local tenant2-1 publication failed with an unsatisfied
Base Application dependency; no runtime tests of this follow-up ran
locally. The owner explicitly approved pushing this follow-up and using
CI for runtime checks. No shared services, versions or metadata were
changed.
- Earlier head 81f5749 is verified in CI run 35046472281 (attempt 2):
15/15 active Expense Permissions tests and 63/63 active Spend Request
tests passed in actual W1 XML results. Its ES setup failure recovered on
a bot-triggered retry. These prior results do not validate the new
follow-up commit.

- [x] I read the full diff and it contains only changes I intended.
- [x] The full changed BaseApp built successfully locally; earlier
app/test builds are recorded below.
- [ ] Runtime validation of the latest BaseApp permission correction is
complete.
- [x] I added or updated tests for the new behavior, or explained below
why none are needed.

**Latest BaseApp correction (2026-09-16, commit 81f5749)**

- The complete BaseApp 30.0.0.0 package built successfully using
official System 30.0.54683.0 compiler symbols. Verified that it contains
the table's self-modify permission; compiler-generated report-layout
edits were excluded from the source commit.
- Publication to tenant2-1 was attempted with Synchronize but rejected
by NST with AL1024: its System 29.0.54137.0 symbols do not meet this
BaseApp package's minimum System 30.0.0.0 requirement. The new BaseApp
permission is therefore not runtime-validated locally.
- The user explicitly approved pushing the compiled correction with that
limitation documented. No application/manifest versions, shared
metadata, services, or other tenants were changed.
- Kept the related negative-approval assertion on its stable permission
error code and affected-table caption, with a transaction boundary
preserving its fixture. Quarantined only
ExpenseMgmtReadRetainsAppPermissions,
ExpenseMgmtEditRetainsAppPermissions, and
ExpenseMgmtAdminRetainsAppPermissions under bugId 650245; their
implementation was left unchanged.
- CI run https://github.com/microsoft/BCApps/actions/runs/35046472281
completed successfully on attempt 2; actual W1 results confirm 15/15
active permission tests and 63/63 active Spend Request tests passed.
Local BaseApp runtime validation remains blocked.

**Prior local runtime validation (2026-09-15)**

- After merging main `b6861a2327bddf153088a404df2e78ceec935b5e`, rebuilt
and published the merged application/test packages at version 30.0.0.0
to tenant2-1. Resolved the disabled-test-list conflict by retaining both
branches' entries; no environment reset or shared-service changes were
made.
- Ran codeunit 148339, Spend Request Test, with Codeunit isolation using
runner 130450: **63 active tests passed, zero failed**. Main adds
exclusions for three action tests; those three were also run separately
and **all passed**, giving 66 locally verified methods. Their upstream
exclusions remain intact.
- The eight posted-history methods are individually listed with `bugId:
650277`. Their fixture incorrectly assumes ordinary negative expense
entries offset spend-request spending; production posting deliberately
excludes negative entries unless they are corrections. These methods are
not counted as passing.
- Fixed the wildcard test's cross-test state leakage by clearing Expense
Approval Setup alongside the expense users in test initialization. The
wildcard and other-approver exclusion assertions remain unchanged and
enabled; no production permission/filter change was required.
- Corrected test enum assertions to compare formatted
WebServiceActionResultCode values, preserving expected result codes
while avoiding NST JIT boxing errors. Also corrected expected-error
transaction boundaries, the posted report's Last Posting No. lookup, and
the Requested For field-error assertion.
- An earlier green CI run masked the enum-related metadata/JIT failure
during test initialization through its tolerance handling. That run is
not evidence of passing Expense Agent runtime tests; this follow-up was
validated locally before being pushed.

**Earlier local compilation checks**

- Compiled the full Expense Agent app and test project in Default and
CLEAN25-CLEAN30 modes using CodeCop, AppSourceCop,
PerTenantExtensionCop, UICop, the CI base ruleset, and compatibility
comparisons.
- All four compilations completed with zero errors and zero new warnings
against the existing baselines (3 existing app warnings and 9 existing
test warnings per mode).
- Inspected compiled API symbols: `employeeNumber` exists in both modes;
the obsolete compatibility controls exist only in Default.
- Verified the six new in-process contract tests and existing
duplicate-error/approval/recreation action regressions are compiled in
both modes and not excluded.
- Added coverage for membership filtering, employee mapping/readback,
blank/unlinked rejection, duplicate/open-status validation, and
request-specific employee navigation. Strengthened duplicate-report
error fixtures and corrected posted-report setup to validate fields
before approval.
- `git diff --check` passes.

**Not completed:** Runtime validation of the eight quarantined
posted-history methods (tracked by
[AB#650277](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/650277)),
authenticated OData end-to-end tests, and a complete independent PR
audit. HTTP tests remain excluded until CI provisions an authenticated
endpoint and an isolated test company. Earlier Default/Clean compilation
checks are separate from the latest 66-test local runtime pass. Remote
CI is reported separately.

The beta Travelers API adds `employeeNumber`. Default builds retain
`expenseUserNo` and `expenseUserName` as Pending-obsolete controls
tagged 30.0; CLEAN30 omits them. Clients should migrate to the
employee-based contract. Expense User numbers remain stored internally,
with no data migration or new unique constraint.

Employee reads retain the existing root API contract and remain subject
to BC object/table permissions and security filters. The new navigation
narrows existing Employee reads to a request's travelers; it does not
introduce per-person read authorization or elevate Employee table
access. Report recreation has a separate owner-scoped write guard.
Changing the overall read authorization model is outside this PR's
contract.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Prangshuman Das <prdas@microsoft.com>
Copilot-Session: 988fb581-008c-4e0d-99c1-285cd45d49e7
(cherry picked from commit 1e87bfc)
Use CLEAN29 and 29.0 obsoletion tags on 29 release branches, and align the related API tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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 Team: Finance GitHub request for Finance area

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants