Skip to content

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
  • 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 logic
  • AccountUpdateUserQueueable: Async User update processing
  • I2C_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:

if (TriggerSettings__mdt.getInstance('AccountTrigger')?.Disabled__c == true) {
    return;
}