Trigger Name: AccountTrigger¶
Last Updated: 2025-10-22 Source Code: https://github.com/AANP-IT/I2C.Salesforce.Metadata/blob/STAGING/force-app/main/default/triggers/AccountTrigger.trigger
API Name: AccountTrigger Object: Account Pattern: Handler Pattern
Business Purpose¶
This trigger manages Account record lifecycle events including auto-generating unique PersonId and BusinessId values for person and business accounts, synchronizing account membership data to related User records, and monitoring changes to auto-renewal settings. It ensures data consistency between Account and User objects while maintaining proper ID sequencing for tracking purposes.
Trigger Events¶
This trigger fires on the following events: - ☑️ Before Insert - ☐ Before Update - ☐ Before Delete - ☑️ After Insert (Currently no logic implemented) - ☑️ After Update - ☐ After Delete - ☐ After Undelete
Trigger Handler¶
Handler Class: AccountTriggerHandler.cls Pattern: Hybrid - Uses switch statement with handler class methods Entry Point: Direct method calls from trigger switch statement
trigger AccountTrigger on Account (
after insert,
after update,
before insert
) {
Static Boolean afterUpdateHasRun = false;
switch on Trigger.operationType{
when AFTER_INSERT{
// Logic after insert (currently empty)
}
when BEFORE_INSERT {
AccountTriggerHandler.accountBeforeInsert(Trigger.new);
}
when AFTER_UPDATE{
if(!afterUpdateHasRun){
afterUpdateHasRun = true;
AccountTriggerHandler.accounAfterUpdate(Trigger.oldMap, Trigger.newMap);
}
}
}
}
Process Flow by Event¶
Before Insert¶
Purpose: Auto-generate unique PersonId and BusinessId values for accounts and set default phone values
Business Logic: 1. Query existing accounts to determine last PersonId (for person accounts) starting from 3,000,000 2. Query existing accounts to determine last BusinessId (for business accounts) starting from 6,000,000 3. Increment and assign sequential IDs based on account type 4. Set default phone number (+10000000000) and phone type (Home) if phone is blank
Methods Called:
- AccountTriggerHandler.accountBeforeInsert(List<Account> newAccounts)
Field Updates:
- PersonId__c: Sequential ID starting at 3,000,000+ for person accounts
- BusinessId__c: Sequential ID starting at 6,000,000+ for business accounts
- Phone: Set to '+10000000000' if blank
- Phone_Type__c: Set to 'Home' if phone was blank
After Insert¶
Purpose: Placeholder for future logic
Business Logic: - Currently empty - reserved for future after insert operations
After Update¶
Purpose: Synchronize account membership data to related User records and monitor auto-renewal changes
Conditional Logic:
- Fires When: Any field changes on Account records
- Recursion Prevention: Static boolean afterUpdateHasRun prevents multiple executions
Business Logic: 1. Check if Auto_Renew__c changed to true and enqueue auto-renewal processing 2. Query all User records related to updated accounts 3. Compare User field values with Account field values for membership data 4. Identify Users needing updates based on changed values 5. Enqueue AccountUpdateUserQueueable job to update Users asynchronously
Methods Called:
- AccountTriggerHandler.accounAfterUpdate(Map<Id, Account> oldMap, Map<Id, Account> newMap)
- AccountTriggerHandler.autoRenewalCheck(Map<Id, Account> oldMap, Map<Id, Account> newMap)
Field Synchronization (Account to User):
- Membership_End_Date__c -> Dues_Paid_Thru__c
- Is_Member__c -> Is_Member__c (uppercase string)
- Member_Type__c -> MemberType__c (or 'None' if not a member)
- Is_a_Fellow__c -> Fellow__c (uppercase string)
- PersonId__c -> PersonAccountId__c
- Is_Company_Admin__c -> CompanyAdmin__c (uppercase string)
- Is_BoardMember__c -> BoardMember__c (uppercase string)
- Is_Staterep__c -> Staterep__c (uppercase string)
- Member_Type__c == 'Staff' -> Staff__c (uppercase string)
Related Records Updated: - User records related to the Account via AccountId
Key Business Rules¶
Validation Rules¶
- PersonId and BusinessId must be unique sequential numbers
- PersonId starts at 3,000,000 for person accounts
- BusinessId starts at 6,000,000 for business accounts
Field Updates¶
- PersonId__c / BusinessId__c: Auto-generated on insert based on account type
- Phone / Phone_Type__c: Default values set if phone is blank
- User Fields: Synchronized from Account membership data on update
Related Record Operations¶
- User records updated asynchronously via Queueable when Account membership data changes
- I2C_QueueableAutoRenewChanges enqueued when Auto_Renew__c changes to true
Bulkification & Governor Limits¶
Bulkified: Partial Max Records Handled: 200 (with governor limit considerations) SOQL Queries: 3-4 per transaction (2 in before insert, 1 in after update) DML Operations: 0 in trigger (DML performed in Queueable/Future contexts)
Bulkification Strategy¶
- Collects all accounts into lists for processing
- Performs SOQL queries outside loops
- Uses Queueable pattern to defer User DML operations
- No DML operations in trigger itself
Governor Limit Considerations¶
- SOQL: 3-4 queries per transaction (before insert: 2, after update: 1)
- DML: 0 in trigger (handled asynchronously)
- Queueable Jobs: 1 per execution in after update
- CRITICAL ISSUE: Before insert queries last PersonId/BusinessId with LIMIT 1 - could have issues if multiple accounts need sequential IDs and concurrent transactions occur
- RISK: Auto-increment logic not truly transactional - potential for ID conflicts in high-volume scenarios
Recursion Prevention¶
Strategy: Static boolean flag in trigger context
Implementation:
Static Boolean afterUpdateHasRun = false;
// ...
when AFTER_UPDATE{
if(!afterUpdateHasRun){
afterUpdateHasRun = true;
AccountTriggerHandler.accounAfterUpdate(Trigger.oldMap, Trigger.newMap);
}
}
Scenarios: - Prevents re-execution if Account update triggers itself during same transaction - Does not prevent cross-object recursion (e.g., User update -> Account update) - Note: Queueable execution happens in separate transaction, avoiding recursion
Execution Order & Dependencies¶
Order of Execution Impact¶
- Before Insert: Runs before validation rules, workflow rules, and process builders
- After Update: Runs after all validation rules and before async processes
Dependent Triggers¶
- UserTrigger: May update Accounts, creating potential circular dependency
- Risk: Possible infinite loop if User updates trigger Account updates
Dependent Processes¶
- AccountUpdateUserQueueable: Queueable job that updates User records
- I2C_QueueableAutoRenewChanges: Queueable job for auto-renewal processing
- Any workflows/flows on Account that might cause updates
Error Handling¶
Strategy: Try-catch blocks with System.debug logging
User Experience: - Success: Records save normally, async jobs enqueued - Validation Error: Standard Salesforce error messages - System Error: Logged to debug logs, record save may fail
Logging: - Errors logged via System.debug(LoggingLevel.ERROR) - Includes error message and stack trace - Logs only visible in debug logs (not persisted)
Rollback Behavior: - Before Insert: Full transaction rollback on exception - After Update: Full transaction rollback on exception - Async operations fail independently without rolling back main transaction
Dependencies¶
Apex Classes¶
AccountTriggerHandler: Main handler with business logicAccountUpdateUserQueueable: Async User update processingI2C_QueueableAutoRenewChanges: Auto-renewal change processing
Salesforce Objects¶
Account: Primary object (Person and Business accounts)User: Related portal users that need sync
Custom Settings/Metadata¶
- None currently implemented
External Systems¶
- None
Testing¶
Test Class: AccountTriggerHandlerTest.cls Coverage: Unknown
Test Scenarios: - Single person account insert with PersonId generation - Single business account insert with BusinessId generation - Bulk account insert (ID sequencing) - Account update with membership changes - User field synchronization - Auto-renewal flag changes - Recursion prevention - Error handling
Performance Considerations¶
Average Execution Time: Unknown Max Records Processed: 200 per transaction Async Processing: Queueable jobs for User updates and auto-renewal
Optimization Opportunities: - Replace sequential ID generation with external ID or custom numbering service - Consider Platform Events for User updates to fully decouple - Add Custom Metadata to enable/disable trigger functionality - Implement proper distributed counter pattern for ID generation - Add selective field checking before enqueuing jobs
Changes & History¶
- Author: Original implementation (team maintained)
- Pattern: Hybrid approach with handler class and switch statement
Pre-Go-Live Concerns¶
CRITICAL - Fix Before Go-Live¶
- ID Generation Race Condition: The LIMIT 1 query for last ID + increment pattern is not transaction-safe. Multiple concurrent inserts could generate duplicate IDs
- Infinite Loop Risk: No protection against User->Account->User circular updates
- Missing Queueable Limit Check: Code checks Limits.getQueueableJobs() but continues if limit reached
HIGH - Address Soon After Go-Live¶
- No Persisted Error Logging: System.debug logs are lost after 24 hours
- Hard-coded Phone Values: Default phone '+10000000000' and type 'Home' should be in Custom Metadata
- Test Coverage Unknown: Verify test coverage meets 75% minimum requirement
- User Query Scalability: The after update queries all users for updated accounts
MEDIUM - Future Enhancement¶
- Implement Trigger Settings: Add Custom Metadata to enable/disable trigger
- Move to Full Framework: Currently hybrid pattern - consider full TriggerHandler framework
- Add Field Change Detection: Only enqueue jobs if specific fields actually changed
- Better Async Error Handling: Queueable jobs fail silently
LOW - Monitor¶
- CPU Time: Sequential ID calculation with multiple queries could impact CPU
- Static Variable Reset: Static boolean only resets at transaction end
- Phone Number Validation: No validation that phone matches expected format
Maintenance Notes¶
Complexity: High Recommended Review Schedule: Quarterly
Key Maintainer Notes: - This trigger implements manual ID generation which is fragile - The trigger registers for AFTER_INSERT but has no logic - likely placeholder - User synchronization happens asynchronously - debug timing issues carefully - Static recursion flag only prevents same-transaction recursion - Always test with bulk operations (200 records) - Method name "accounAfterUpdate" has a typo
Deactivation Instructions: Add custom metadata-based bypass at start of trigger: