Skip to content

Repository files navigation

Account Territory Management — Salesforce DX

Automatically assigns Account owners based on configurable geographic territory rules, with a round-robin fallback for accounts that don't match any rule. Includes a Lightning Web Component admin UI for managing rules, round-robin rotations, and manual overrides — all with a full audit log.

Territory Management Console


Project Structure

force-app/
├── territory-rule/          # Territory Rule engine, LWC, objects

How It Works

Assignment Logic

When an Account is inserted or updated (segment, billing/shipping address changes), the trigger fires the assignment engine:

  1. Rule Match — All active Territory_Rule__c records are evaluated in Priority__c order (ascending). A rule matches when the Account's Account_Segment__c equals the rule's Business_Line__c and the billing or shipping address (controlled by Address_Type__c) matches the rule's Country__c and State__c. Blank rule fields act as wildcards.

  2. Round Robin Fallback — If no geographic rule matches, the engine looks up the Round_Robin_State__c record for the account's business line and cycles through the configured user list, advancing the index on each assignment.

  3. Audit Log — Every assignment attempt (matched or not, successful or failed) writes a Territory_Assignment_Log__c record with the method used, the matched rule name, and a human-readable notes field.

  4. Bypass — Any user holding the Territory_Management_Bypass custom permission skips trigger processing entirely.


Metadata Components

Custom Objects

Object Purpose
Territory_Rule__c Stores geographic assignment rules — one record per territory/business-line combination
Round_Robin_State__c Tracks the current rotation index and user list for each business line
Territory_Assignment_Log__c Immutable audit trail of every assignment event

Territory_Rule__c Fields

Field Type Description
Business_Line__c Picklist Must match Account.Account_Segment__c
Country__c Text Country to match (blank = any country)
State__c Text State/province to match (blank = any state)
Address_Type__c Picklist Billing, Shipping, or Either
Assigned_User_Id__c Text Salesforce User ID to assign as owner
Assigned_User_Name__c Text Auto-populated from the User record on save
Priority__c Number Lower number = evaluated first
Is_Active__c Checkbox Inactive rules are skipped entirely

Round_Robin_State__c Fields

Field Type Description
Business_Line__c Text Matches Account.Account_Segment__c
User_Ids__c Long Text Newline-separated list of Salesforce User IDs
Last_Index__c Number Index of the last assigned user; resets to -1 when user list changes

Territory_Assignment_Log__c Fields

Field Description
Account__c Lookup to the assigned Account
Assigned_To__c Lookup to the User who was assigned
Assignment_Method__c Rule Match, Round Robin, or Manual
Matched_Rule__c Name of the matched Territory_Rule__c (if applicable)
Business_Line__c Business line at time of assignment
Triggered_By__c Insert, Update, or Manual
Notes__c Human-readable explanation of what happened

Account Custom Fields

Field Description
Account_Segment__c Picklist — determines which business line rules and round-robin pool apply
ARR_Bookings__c Currency — Annual Recurring Revenue / bookings tracking

Apex Classes

TerritoryAssignmentService

Core assignment engine. Called by the trigger handler and by the manual reassignment flow.

  • assign(List<Account> accounts, String triggerType) — Bulk-safe entry point. Evaluates rules and round-robin state for each account, then performs DML in a single pass.
  • manualAssign(Id accountId, Id userId)@AuraEnabled method for explicit owner overrides from the UI.

TerritoryRuleController

@AuraEnabled controller backing the territoryRuleManager LWC.

Method Description
getRules(businessLine, activeOnly) Returns filtered, priority-sorted Territory_Rule__c records
saveRule(rule) Upserts a rule; auto-resolves Assigned_User_Name__c from the User record
deleteRule(ruleId) Deletes a single rule by ID
getRoundRobinStates() Returns all Round_Robin_State__c records
saveRoundRobinState(state) Upserts a round-robin state; resets Last_Index__c to -1 if the user list changes
manualAssign(accountId, userId) Delegates to TerritoryAssignmentService.manualAssign()
getAssignmentLog(accountId) Returns the 20 most recent log records for an account
searchAccounts(searchKey) LIKE-based account search (up to 10 results)
getUsersForBusinessLine(businessLine) Returns active users in a business line's round-robin pool
getActiveUsers() Returns all active users (up to 200), sorted by name

TerritoryTriggerHandler

Thin handler class between the trigger and the service.

  • Filters inserted accounts to only those with a populated Account_Segment__c.
  • On update, fires only when Account_Segment__c, BillingState, BillingCountry, ShippingState, or ShippingCountry has changed — avoids unnecessary processing.
  • Uses a static isFirstRun flag to prevent recursive trigger execution.
  • Checks Territory_Management_Bypass custom permission via FeatureManagement.checkPermission() before doing any work.

AccountTerritoryTrigger

Single after insert, after update trigger on Account. Delegates directly to TerritoryTriggerHandler.


LWC: territoryRuleManager

An admin UI surfaced as a Lightning App Page (also available on Record and Home pages).

Capabilities:

  • View, create, edit, and delete Territory_Rule__c records filtered by business line and active status
  • Manage Round_Robin_State__c user pools per business line
  • Manually reassign an Account's owner and log the action
  • View the assignment history log for any account

Targets: lightning__AppPage, lightning__RecordPage, lightning__HomePage


Permission Set: Territory_Manager

Grants the access needed to use the Territory Rule Manager UI.

Access Detail
Apex Classes TerritoryRuleController, TerritoryAssignmentService
Territory_Rule__c Create, Read, Edit, Delete
Round_Robin_State__c Create, Read, Edit, Delete
Territory_Assignment_Log__c Read only
Account.Account_Segment__c Read/Edit
All Territory_Rule__c fields Read/Edit
All Round_Robin_State__c fields Read/Edit
All Territory_Assignment_Log__c fields Read only
Tab Territory_Management (Available)
Custom Permission Territory_Management_Bypass (included so admins can also bypass if needed)
User Permission ApiEnabled

Deployment

# Deploy Territory Rule package only
sf project deploy start --source-dir force-app/territory-rule

# Deploy both packages
sf project deploy start

# Run all territory tests
sf apex run test --test-level RunLocalTests --wait 10

Test Coverage

Class Coverage
TerritoryRuleControllerTest TerritoryRuleController — all @AuraEnabled methods including error paths
TerritoryAssignmentServiceTest TerritoryAssignmentService — rule match, round-robin, manual assign, edge cases
AccountTerritoryTriggerTest End-to-end trigger → handler → service flow

Known Behaviors & Notes

  • Bulk safe — The assignment service processes all accounts in a single SOQL + DML pass; no per-record queries.
  • Round-robin index resets — Changing the User_Ids__c list on a Round_Robin_State__c record resets Last_Index__c to -1 so the rotation starts fresh from the first user.
  • No assignment if no segment — Accounts with a blank Account_Segment__c are silently skipped by both the trigger handler and the assignment service.
  • Owner unchanged if already correct — The service only adds an account to the update list if OwnerId is actually changing, avoiding unnecessary DML.
  • Bypass permission — Assign Territory_Management_Bypass to integration users or admins who manage account records but should not trigger reassignment.

About

Automatically assigns Account owners based on configurable geographic territory rules, with a round-robin fallback for accounts that don't match any rule. Includes a Lightning Web Component admin UI for managing rules, round-robin rotations, and manual overrides — all with a full audit log.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages