bcquality/microsoft/knowledge/data-modeling/code-must-not-change-workdate.md
Michael Dieringer 67962727f6 Fix ten focused correctness items plus sample links from Jesper's 2026-09-15 re-review
Six carried-over threads:
- api-page-least-privilege-write-access fixtures: added the mandatory
  EntityName/EntitySetName properties (AL0485).
- pages-must-not-contain-business-logic fixtures: Sales Line has no
  "Total Amount" field; replaced with the real "Line Amount" (field 103).
- test-feature-scenario-tags.good.al and test-one-when-per-test.good.al:
  CreatePriceHeader leaves a price list in Draft status, which price
  calculation ignores. Added Validate(Status, Active) + Modify before the
  sales line that depends on it. Verified Status field/enum against
  PriceListHeader.Table.al and PriceStatus.Enum.al in the BCApps clone.
- exposed-objects-must-be-in-a-permission-set.md: a published codeunit is
  a SOAP endpoint (SOAP is deprecated), not OData - Page/Query are the
  OData object types. Corrected and pointed new integrations at API
  pages/queries instead.
- al-error-handling-review.md: the log-writes-must-survive-rollback cue
  selected on Session.StartSession, which only appears in the compliant
  fix, never in the anti-pattern - the bad fixture could never be
  worklisted. Recued on the actual risk shape (log insert around a
  failed TryFunction/GetLastError* path, then raise/propagate), with
  StartSession as an explicit compliant discriminator instead.
- page-design-must-match-bc-page-type-conventions.md: the enum value is
  NavigatePage, not Navigate; noted the type list is a selected subset,
  not an exhaustive PageType catalogue (PromptDialog, ConfigurationDialog,
  UserControlHost, XmlPort also exist, out of this article's scope).

Four new correctness gaps:
- release-must-update-app-version.md: "the version is the only identity"
  was backwards - id is the app's stable identity, version identifies a
  release/code-state of it.
- defensive-vs-offensive-code-must-match-blast-radius.good.al: the "low
  blast radius" example had no else branch, so a failed Customer.Get()
  left the field at its prior/default value instead of the explicit
  chosen fallback the article claims to demonstrate. Added the else.
- bcpt-scenarios-must-be-app-specific.good.al: InitTest and both measured
  StartScenario/EndScenario sections were empty/comment-only, so the
  "app-specific" fixture measured no actual work. Filled in a real,
  self-contained header+line creation path.
- upgrade-tag-logic-must-not-nest-deeply.good.al: the flattened version
  dropped both safety conditions the bad fixture had (Discount % = 0,
  nonblank posting group), silently changing behavior instead of just
  removing nesting. Extracted the guarded update into a helper with both
  conditions preserved as early exits.

Also converted this PR's remaining plain-backtick "See sample:" sample
references (16 articles) to the READ-convention markdown-link form,
matching the fix already made on #156/#158.

Rebased onto upstream/main (conflicts in al-ui-review.md, al-style-review.md,
al-upgrade-review.md against merged upstream PRs - all additive, both
sides' worklist cues retained).
2026-09-21 22:44:17 +02:00

2.7 KiB

bc-version domain keywords technologies countries application-area
all
data-modeling
workdate
session-setting
user-control
side-effect
al
w1
all

Application code must not change the WorkDate

Description

The work date is a per-user session setting the user controls from the client (the date shown in the top-right corner, used to default posting dates and date filters). Business logic unrelated to that setting must not call WorkDate(NewDate) as a side effect of doing something else — that silently changes what the user sees and defaults to for the rest of their session, a surprising, hard-to-trace behavior change the user never asked for and has no visibility into. This is not a blanket ban on the setter itself: BCApps' own demo-data generators legitimately save the current work date, set a specific one to backdate the data they create, and restore it afterward (see CreateDemoEDocsBE.Codeunit.al's WorkDate(SampleInvoiceDate) / WorkDate(SavedWorkDate) pair), and test codeunits routinely set WorkDate deliberately to control the date context a test runs under (hundreds of calls across BCApps' test suite, for example SustainabilityPostingTest.Codeunit.al). Both are the code's actual purpose, not a side effect of something unrelated.

This is a call-direction distinction for the read side: reading the current work date via WorkDate (or WorkDate() with no argument) is always fine.

Best Practice

Read the work date to default a value. Only write to it when changing it is the operation being performed — implementing the user's own work-date/settings action, or a test or demo-data routine that deliberately establishes a date context (saving and restoring the prior value if the routine must leave the session as it found it). Business logic that exists to do something else must never write WorkDate as an incidental side effect; if a calculation needs a specific date, pass or compute that date as a local variable instead.

See sample: code-must-not-change-workdate.good.al.

Anti Pattern

Setting the work date from within a codeunit, report, or page action whose purpose is unrelated to the user's date preference — for example, a posting or calculation routine that calls WorkDate(SomeDate) to make its own logic simpler. This changes session state the user owns for the duration of a call that was never about the work date, and never restores it. This is a different case from a test or demo-data routine explicitly declaring a date context: the anti-pattern is unrelated logic silently mutating state it does not own, not the setter form itself.

See sample: code-must-not-change-workdate.bad.al.