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).
2.7 KiB
| bc-version | domain | keywords | technologies | countries | application-area | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
data-modeling |
|
|
|
|
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.