How a business rule actually fires, what users see, and where decisions still need to be made
Every rule follows the same lifecycle. Here's the sequence:
Key insight: rules are pure evaluators — they don't directly affect the system. The caller (trigger or service class) decides what to do based on returned results. This keeps the engine testable and lets us evolve action handling separately.
This is the part the BDD specs gloss over. Each abstract phrase maps to a concrete user-visible outcome — but several have options we need to decide on.
| Action | BDD phrase | What actually happens |
|---|---|---|
| block_sync (BR-001, BR-003, BR-022, BR-029, BR-040, BR-041, BR-051, BR-053, BR-056) |
"the sync is blocked" |
Three patterns to choose from: A) Pre-save validation rule — record never saves; user sees standard SF validation error toast inline. B) Save proceeds, sync attempt blocked separately; record gets Xero_Sync_Error__c populated; user sees no immediate error but sees red flag on next view.C) Apex throws DmlException from trigger; user sees blocking error toast at save time but with our custom message.
Recommendation: Pattern C. User-facing immediate feedback with our wording, doesn't pollute SF's validation rule UI.
|
| flag_record (BR-003, BR-021) |
"the record is flagged" |
A picklist or text field on the record is set to a specific value. Save proceeds normally. User sees the flag on next view.
Example: BR-003 sets
Opportunity.Invoice_Status__c = "No Invoice Required". User sees this in the standard field on the record page. |
| alert (BR-002, BR-012, BR-022, BR-032) |
"an alert is raised" |
Three patterns to choose from: A) Salesforce Task created and assigned to a designated user (e.g. Ilkay, Sara, Jo per BR-032). Appears in their My Tasks list and email digest.B) Email via Messaging.SingleEmailMessage — direct email to recipients.C) Chatter post on the affected record — visible to everyone following. Recommendation: Pattern A (Task). Already used by
XeroTokenRefreshJob for token expiry alerts. Persistent, assignable, dismissable, doesn't spam inboxes. |
| map_field (BR-013, BR-014, BR-018, BR-023, BR-026, BR-030, BR-044) |
"field mapping is applied" |
Invisible to the user during normal sales work — happens at sync time. The Xero invoice is built with mapped values. User sees the result only on the synced Xero invoice, or in the audit log.
Example: BR-018 sets
Tax_Rate__c = 0 for non-UK invoices. The Salesforce-side invoice record gets the value, and so does the synced Xero invoice. |
| execute_apex (BR-005, BR-006, BR-015, BR-019, BR-027, BR-028, BR-032, BR-033, BR-039, BR-049, BR-050, BR-052, BR-055) |
"the configured logic runs" |
Engine delegates to a named Apex class (e.g. OpportunityTypeDeriver). The class does whatever the rule needs — multi-step logic, complex calculations, multi-record updates. Outcome depends entirely on the handler. Each handler should have its own clear UX expectation.
Note: Workflow rules are where the highest variance lives. Each one needs its own UX spec at design time.
|
| log (BR-036, BR-038, BR-042, BR-043, BR-047) |
"an audit log entry is recorded" |
A Xero_Webhook_Log__c (or new audit object) record is created. Invisible to most users; visible to Finance/Ops in the Webhook Logs tab and the monthly governance review.
|
| require_approval (BR-048) |
"an approval request is raised" |
Standard Salesforce Approval Process triggered. Approver receives email + in-app notification. User sees record locked pending approval. After approval (or auto-approval after timeout), record progresses normally.
Example: BR-048 — discount >50% requires CRO sign-off, auto-approves after 1 day if no response.
|
Same rule, different audiences experience it differently.
| User | What they experience when a rule fires |
|---|---|
| Sales / RM (creates the deal) |
• block_sync: red error toast at save with the rule's message • flag_record: a field value updates after save • require_approval: record becomes locked, sees "Pending CRO approval" • Most other rule types: no visible signal during normal flow |
| Sales Ops / Finance (reviews invoices) |
• Sees the consequences in dashboards and reports — flagged opportunities, approval queue, audit log • Receives Tasks for alerts and exceptions • Reviews Webhook Logs for sync failures • Monthly governance review (BR-037) surfaces aggregate issues |
| System admin (maintains the integration) |
• Monitors the Xero_Webhook_Log__c object for failures• Sees CMT records for active rules in Setup • Can deactivate individual rules without code deploy (set Is_Active__c = false)• Receives critical alerts via Task or email |
| Finance leadership (governance) |
• Doesn't see rules firing day-to-day • Sees outcomes via reports — invoice consistency, ARR/TCV correctness, intercompany separation • Reviews business-rule register quarterly via the rules viewer |
The BDD spec is intentionally agnostic about these — we should land them before Phase 2.
map_field rules, should the source record reflect the mapped value, or only the synced Xero record? (e.g. BR-018 setting Tax_Rate to 0 — does that go on Salesforce too?)
Rules are evaluated at events — defined in each rule's when.event. Here's the full picture:
A single rule can fire at multiple events if needed (rare). Most rules fire once at a defined point in the lifecycle. Adding new event hooks is cheap (a few lines in the relevant trigger or service).
Business_Rule__mdt), not code. Active rules are queried at runtime.