* Add trigger-default and declined-Confirm knowledge with review cues Three Microsoft-layer articles with good/bad samples: - error-handling/declined-confirm-must-abort-not-partially-apply - data-modeling/delete-master-data-with-trigger - data-modeling/master-data-must-be-inserted-with-trigger Wire targeted worklist cues into al-error-handling-review and al-data-modeling-review and register the samples in review-fixtures.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tighten declined-confirm, delete and insert trigger articles after review Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
5 KiB
| bc-version | domain | keywords | technologies | countries | application-area | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
error-handling |
|
|
|
|
A declined Confirm in OnValidate must abort or revert, not skip a required side effect
Description
By the time a field's OnValidate body runs, the field already holds its new value. When the trigger asks the user to confirm a side effect that the new value makes required for consistency — releasing or replacing state that is tied to the old value and that nothing will reference any more once the change commits — the shape if Confirm(...) then <side effect>; with nothing on the false branch lets the new value commit while silently skipping that effect. No error is raised, the user sees no indication that anything was declined, and the record is left inconsistent with its own dependents.
Not every confirmed follow-up is required. When the confirmed effect is a convenience the user may legitimately decline, skipping it is correct: "Sales Header"'s UpdateSalesLinesByFieldNo asks whether to update the lines after a header field changes and, on "no", simply exits — the header keeps its new value and the lines stay as they were, by design. A Confirm at the top of an action procedure, before anything has been written, may also just exit on "no" (for example the delete action in "Test Input Groups"). Neither shape is this anti-pattern. Nor is a declined update whose old state stays valid: Opportunity's "Campaign No." OnValidate asks before moving open tasks filtered on xRec."Campaign No." to the new campaign and does nothing on "no" — the tasks keep pointing at a campaign that still exists.
Best Practice
Make the declined branch match what "no" means:
- Cancel the whole change — raise an error before the side effect. The usual BCApps form inside
OnValidateis the silent abortif not Confirm(...) then Error('');(for example"Bank Account", field"Disable Bank Rec. Optimization", and"Interaction Template", field"Language Code (Default)", which endsif Confirm(...) then begin ... end else Error('');). The field trigger documentation states that in case of an error "the user entry is not written to the database." - Keep the old value but let the rest of the edit continue — assign the field back in code. In the
"To-do"table, field"Team Code", declining the reassignment runs"Team Code" := xRec."Team Code"; on a page,"Upload And Deploy Extension"resets its sync-mode value toAddwhen the user declinesForce Sync.
Ask before the side effect runs, and before taking locks the prompt would hold open (see avoid-user-prompts-inside-transactions). When the same validation can run without a UI, a required confirmation must not be silently skipped behind GuiAllowed; decide the non-interactive outcome explicitly (see job-queue-handlers-must-not-require-ui).
See sample: declined-confirm-must-abort-not-partially-apply.good.al.
Anti Pattern
Inside a field OnValidate (or a procedure it calls), a Confirm gates a side effect that releases, cancels, or replaces state belonging to the old value (xRec), the false branch neither errors nor restores the field, and code after it proceeds as if the change were accepted — for example overwriting the only field that tracks the old state. Flag it only when, after the change, nothing references the old state any more, so declining leaves it orphaned. Do not flag declined updates whose old state remains valid and referenced, optional follow-ups whose skipping leaves every record consistent, or exit on "no" in an action before any write.
See sample: declined-confirm-must-abort-not-partially-apply.bad.al.
References
- OnValidate (Field) trigger.
- BCApps
src/Layers/W1/BaseApp/Bank/BankAccount/BankAccount.Table.al, lines 980-988 (silent abort inOnValidate). - BCApps
src/Layers/W1/BaseApp/CRM/Interaction/InteractionTemplate.Table.al, lines 157-165 (else Error('')inOnValidate). - BCApps
src/Layers/W1/BaseApp/CRM/Task/Todo.Table.al, lines 80-90 (table-field revert toxRec). - BCApps
src/System Application/App/Extension Management/src/UploadAndDeployExtension.Page.al, lines 78-83 (explicit revert on a page). - BCApps
src/Layers/W1/BaseApp/CRM/Opportunity/Opportunity.Table.al, lines 142-157 (declined update; old campaign still valid). - BCApps
src/Layers/W1/BaseApp/Sales/Document/SalesHeader.Table.al,UpdateSalesLinesByFieldNo, lines 4997-5014 (optional follow-up;end else exit). - BCApps
src/Tools/Test Framework/Test Runner/src/DataDrivenTest/DataInputs/TestInputGroups.Page.al, lines 85-86 (exitbefore any write).