Class Name: BuyerGroupPricebookHelper¶
Last Updated: 2025-10-22 Source Code: https://github.com/AANP-IT/I2C.Salesforce.Metadata/blob/STAGING/force-app/main/default/classes/BuyerGroupPricebookHelper.cls
API Name: BuyerGroupPricebookHelper Type: Utility Helper Class Test Coverage: To be determined
Business Purpose¶
This utility class automates the synchronization of product visibility to buyer groups based on pricebook associations in Salesforce B2B Commerce. When BuyerGroupPricebook relationships are created or modified, this class ensures that products associated with specific pricebooks are automatically made visible to the corresponding buyer groups. This maintains proper access control for B2B commerce scenarios, ensuring customers only see products they are authorized to purchase based on their buyer group membership and associated pricing agreements.
Class Overview¶
Scope and Sharing¶
- Sharing Model: without sharing (bypasses all record-level security)
- Access Modifier: public
- Interfaces Implemented: None
Key Responsibilities¶
- Synchronize product visibility to buyer groups based on pricebook associations
- Maintain VisibleToBuyerGroups__c field on Product2 records with comma-separated buyer group IDs
- Process bulk BuyerGroupPricebook record changes efficiently
- Preserve existing buyer group visibility while adding new associations
- Map products to buyer groups through pricebook entry relationships
- Handle deduplication of buyer group IDs to prevent duplicates
Public Methods¶
updateVisibleBuyerGroups¶
Purpose: Processes a list of BuyerGroupPricebook records to update product visibility. Maps products from pricebooks to their associated buyer groups and updates the Product2.VisibleToBuyerGroups__c field with comma-separated buyer group IDs.
Parameters:
- bgpbList (List
Returns: - void (no return value)
Throws: - DMLException: If product update fails - QueryException: If SOQL queries fail - NullPointerException: If string splitting encounters null values (possible but handled)
Usage Example:
// Typically called from BuyerGroupPricebookTrigger
trigger BuyerGroupPricebookTrigger on BuyerGroupPricebook (after insert, after update) {
BuyerGroupPricebookHelper.updateVisibleBuyerGroups(Trigger.new);
}
// Manual invocation for testing
List<BuyerGroupPricebook> bgpbs = [SELECT Id, IsActive, Pricebook2Id, BuyerGroupId
FROM BuyerGroupPricebook
WHERE IsActive = true LIMIT 10];
BuyerGroupPricebookHelper.updateVisibleBuyerGroups(bgpbs);
Business Logic:
1. Filters bgpbList to only active records with both Pricebook2Id and BuyerGroupId populated
2. Builds map of Pricebook2Id → Set
Field Logic: - Only processes records where IsActive = true - Requires both Pricebook2Id and BuyerGroupId to be non-null - Preserves existing buyer group IDs and merges with new ones - Uses comma as delimiter for storing multiple buyer group IDs - Handles trimming of whitespace when parsing existing IDs
Performance Considerations: - Single SOQL query for all pricebook entries (efficient) - Single SOQL query for all affected products (efficient) - Single DML update for all products (bulkified) - String splitting and Set operations minimize duplicates
Private/Helper Methods¶
None - All logic contained in the single public method. Could benefit from refactoring into smaller helper methods for: - Filtering and mapping buyer group pricebooks - Querying pricebook entries - Parsing and merging buyer group ID strings - Building product update list
Dependencies¶
Apex Classes¶
- None - Standalone utility class
- Typically called by: BuyerGroupPricebookTrigger or BuyerGroupPricebookTriggerHandler
Salesforce Objects¶
- BuyerGroupPricebook (B2B Commerce Junction Object)
- Fields accessed: IsActive, Pricebook2Id, BuyerGroupId
-
Purpose: Junction between BuyerGroup and Pricebook2
-
Pricebook2 (Standard Object)
- Fields accessed: Id (via Pricebook2Id reference)
-
Purpose: Contains pricing for products
-
PricebookEntry (Standard Object)
- Fields accessed: Id, Product2Id, Pricebook2Id
-
Purpose: Links products to pricebooks
-
Product2 (Standard Object)
- Fields accessed: Id, VisibleToBuyerGroups__c (custom field)
- Fields modified: VisibleToBuyerGroups__c
-
Purpose: Store comma-separated list of buyer group IDs
-
BuyerGroup (B2B Commerce Object)
- Fields accessed: Id (via BuyerGroupId reference)
- Purpose: Group of buyers with common access/pricing
Custom Settings/Metadata¶
- None identified - Consider adding:
- B2B_Commerce_Settings__mdt: Configuration for delimiter, field mappings
- Trigger_Settings__c: Enable/disable helper execution
External Services¶
- None - Pure internal Salesforce processing
Design Patterns¶
- Helper Pattern: Static utility method for trigger processing
- Bulk Processing Pattern: Handles collections of records efficiently
- Data Aggregation Pattern: Maps and aggregates data from multiple objects
- String Concatenation Pattern: Uses comma-delimited strings for multi-value storage
Why These Patterns: - Static helper method allows reuse from multiple triggers or flows - Bulk processing ensures governor limit compliance - Aggregation pattern efficiently builds product-to-buyer group mappings - String storage chosen for custom field limitations (cannot store Set/List directly)
Pattern Considerations: - String-based storage has limitations (parsing overhead, query complexity) - Consider using junction object (Product_Buyer_Group__c) for better data model - Current approach requires string parsing which is less performant
Governor Limits Considerations¶
SOQL Queries: 2 (PricebookEntry query, Product2 query) DML Operations: 1 (Product2 update) CPU Time: Medium (string splitting and Set operations for each product) Heap Size: Depends on number of products and buyer groups
Bulkification: Yes - processes all records in collections Async Processing: No - synchronous execution only
Governor Limit Risks: - MEDIUM: String splitting for many buyer groups could consume CPU time - MEDIUM: Large pricebooks (>10,000 products) could hit SOQL row limits - LOW: PricebookEntry query could return many records if multiple large pricebooks - LOW: Product2 update limited to products in scope (not org-wide)
Query Limits: - PricebookEntry query: Returns all entries for specified pricebooks (could be thousands) - Product2 query: Returns all products from pricebook entries (could be thousands) - No explicit LIMIT clauses (relies on pricebook scope to limit results)
Recommendations: - Add LIMIT clauses for safety (e.g., LIMIT 10000) - Consider batch processing for large pricebook updates - Monitor CPU time with many buyer groups per product - Add logging for large data volume scenarios
Error Handling¶
Strategy: No explicit error handling - relies on system exceptions
Logging: - None - No logging of execution or errors - No tracking of which products were updated - No visibility into business logic execution - Silent failures possible
User Notifications: - None - No notifications on success or failure - Trigger context determines error visibility - Users see generic Salesforce error messages if DML fails
Rollback Behavior: - Full transaction rollback on any error - All product updates rolled back together - BuyerGroupPricebook records may be committed but products not updated - No partial success possible (uses standard update, not Database.update)
Recommended Improvements: - Add try-catch around DML operations - Log execution details to custom object - Use Database.update with allOrNone=false for partial success - Publish platform events for monitoring - Add validation before update (e.g., check buyer group still exists)
Security Considerations¶
Sharing Rules: ⚠️ BYPASSED - Uses 'without sharing' keyword - Updates any Product2 record regardless of sharing rules - Queries any BuyerGroup, Pricebook, Product data without restriction - Runs with system administrator equivalent privileges - RISK: Could expose pricing and product visibility to unauthorized users
Field-Level Security: ⚠️ NOT ENFORCED - Can update VisibleToBuyerGroups__c without FLS checks - Can query all fields without FLS enforcement - Appropriate for B2B Commerce automation but risky if misused
CRUD Permissions: ⚠️ NOT ENFORCED - Can update Product2 even if caller lacks edit permission - Can query all objects without object-level permission checks - Appropriate for system-level commerce operations
Input Validation: MINIMAL - Checks for IsActive = true - Checks for non-null Pricebook2Id and BuyerGroupId - No validation that buyer groups are still active - No validation that products should be visible
Security Risks: - MEDIUM: 'without sharing' could expose sensitive B2B pricing - MEDIUM: No validation that buyer groups are authorized - LOW: Could make products visible to inappropriate buyer groups if data is incorrect - LOW: No audit trail of who made changes
B2B Commerce Security Considerations: - Product visibility controls customer purchasing access - Incorrect visibility could allow unauthorized purchases - Pricing exposure through product visibility - Consider adding authorization checks before processing
Mitigation Recommendations: 1. Validate calling code has proper authorization 2. Add logging for audit trail 3. Validate buyer groups are active before adding to products 4. Consider 'inherited sharing' if appropriate for use case 5. Add custom permission check for commerce admin access 6. Implement field whitelisting for allowed updates
Test Class¶
Test Class: BuyerGroupPricebookHelperTest.cls (assumed name - verify in codebase) Coverage: To be determined
Test Scenarios That Should Be Covered: - ✓ Single BuyerGroupPricebook creates product visibility - ✓ Multiple BuyerGroupPricebooks for same pricebook - ✓ Multiple BuyerGroupPricebooks for different pricebooks - ✓ Bulk processing of 200+ BuyerGroupPricebook records - ✓ Existing VisibleToBuyerGroups__c values are preserved - ✓ Duplicate buyer group IDs are handled (deduplication) - ✓ Inactive BuyerGroupPricebook records are ignored (IsActive = false) - ✓ Null Pricebook2Id or BuyerGroupId are handled - ✓ Empty bgpbList parameter - ✓ Products with no existing VisibleToBuyerGroups__c value - ✓ Products with existing comma-separated values - ✓ Multiple products in single pricebook - ✓ Single product in multiple pricebooks - ✓ String parsing with extra spaces/commas - ✓ 'without sharing' behavior verification - ✓ Governor limits with large datasets
Testing Challenges: - Difficult to test 'without sharing' in test context (all run as system) - Need to create full B2B Commerce data setup (BuyerGroups, Pricebooks, Products) - String parsing edge cases need thorough coverage - Deduplication logic needs verification
Test Data Requirements: - Multiple BuyerGroup records - Multiple Pricebook2 records (including standard pricebook) - Multiple Product2 records - PricebookEntry records linking products to pricebooks - BuyerGroupPricebook records (both active and inactive) - Products with pre-existing VisibleToBuyerGroups__c values
Edge Cases to Test: - Very long comma-separated strings (many buyer groups) - Special characters in buyer group IDs (though Salesforce IDs are safe) - Empty strings in VisibleToBuyerGroups__c - Null vs empty string handling - Whitespace variations in existing data
Changes & History¶
- Initial creation date: Unknown (check git history)
- Created by: Unknown (check git annotations)
- Purpose: B2B Commerce product visibility automation
- Last modified: 2025-04-22 (from git log)
- Related to: B2B Commerce storefront implementation
Recommended: - Document B2B Commerce requirements that drove creation - Link to product visibility business rules - Reference B2B Commerce setup documentation
⚠️ Pre-Go-Live Concerns¶
CRITICAL - Fix Before Go-Live¶
- 'without sharing' Security Risk: Bypasses ALL sharing rules - could expose sensitive B2B pricing and product data to unauthorized users. Requires comprehensive security review.
- No Error Handling: Zero exception handling - any DML or SOQL failure will cause unhandled exception. Could break entire trigger transaction.
- String ID Conversion Risk: Direct string-to-ID casting
(Id)strId.trim()could fail with corrupted data in VisibleToBuyerGroups__c field. Needs validation. - No Null Safety on Split:
prod.VisibleToBuyerGroups__c.split(',')will fail if field is null (though checked with isBlank first, still risky).
HIGH - Address Soon After Go-Live¶
- No SOQL Query Limits: Queries lack LIMIT clauses - could hit 50,000 row limit with large pricebooks or fail with heap size limits.
- No Partial Success: Update uses standard DML - will fail entirely if any single product fails validation. Should use Database.update with allOrNone=false.
- No Audit Trail: No logging of what products were updated or which buyer groups were added. Makes troubleshooting impossible.
- Performance with Large Datasets: String splitting and Set operations for thousands of products could hit CPU time limits.
- No Validation: Doesn't validate that buyer groups are still active or that products should be visible.
MEDIUM - Future Enhancement¶
- Hardcoded Comma Delimiter: Delimiter is hardcoded - should be configurable via custom metadata if business rules change.
- Inefficient String Storage: Storing buyer group IDs as comma-separated strings is inefficient. Consider junction object (Product_Buyer_Group__c).
- No Deactivation Logic: When BuyerGroupPricebook is deactivated or deleted, product visibility is not removed. One-way sync only.
- Missing Business Validation: No validation that products are appropriate for buyer groups or that pricing exists.
- Code Organization: 70-line method should be refactored into smaller, testable methods.
LOW - Monitor¶
- Missing Documentation: Method lacks JavaDoc comments explaining parameters, behavior, and exceptions.
- Test Coverage: Verify adequate test coverage exists for all scenarios, especially edge cases.
- No Configuration: No custom metadata for enabling/disabling or configuring behavior.
- Monitoring: No integration with monitoring tools or error tracking systems.
Maintenance Notes¶
Complexity: Medium (complex data relationships and string manipulation) Recommended Review Schedule: Quarterly (B2B Commerce critical), after Salesforce releases
Key Maintainer Notes:
🛒 B2B COMMERCE CONSIDERATIONS: - This class is CRITICAL for B2B Commerce product visibility - Controls which products customers can see and purchase - Incorrect visibility could block legitimate purchases or allow unauthorized access - Changes could impact revenue and customer experience - Test thoroughly in B2B Commerce storefront before deploying
📋 Usage Patterns: - Typically called from BuyerGroupPricebookTrigger after insert/update - May be called from batch jobs or data loads - Runs synchronously in trigger context - keep performance in mind - One-way sync only (adding visibility, not removing)
🧪 Testing Requirements: - Test with production-like B2B Commerce data volumes - Verify storefront product visibility after updates - Test with multiple buyer groups per customer - Test with shared pricebooks across buyer groups - Load test with bulk pricebook updates - Verify no products become invisible unexpectedly
🔧 Data Model Considerations: - VisibleToBuyerGroups__c stores comma-separated IDs (not ideal but functional) - Alternative: Junction object Product_Buyer_Group__c for better queries and maintenance - Current approach works but doesn't scale well for reporting - String field has length limits (consider if many buyer groups per product) - String parsing overhead on every execution
⚠️ Gotchas and Warnings: - 'without sharing' means bypasses ALL security - be very careful who calls this - String splitting can fail with corrupted data - add validation - No removal of buyer groups when pricebook associations are deleted - Deduplication prevents duplicates but doesn't validate data quality - Large pricebooks could cause performance issues - Multiple products in multiple pricebooks creates cartesian product effect
📅 When to Review This Class: - Before B2B Commerce go-live (CRITICAL) - After Salesforce B2B Commerce upgrades - When adding new buyer groups or pricebooks - If product visibility issues reported by customers - During B2B Commerce performance optimization - When considering data model improvements - After any B2B Commerce security audit
🛑 Deactivation Strategy:
If this class needs to be temporarily disabled:
// Option 1: Add custom metadata check at start of method
public static void updateVisibleBuyerGroups(List<BuyerGroupPricebook> bgpbList) {
B2B_Commerce_Settings__mdt settings =
B2B_Commerce_Settings__mdt.getInstance('ProductVisibilitySync');
if (settings != null && settings.Disabled__c == true) {
System.debug('Product visibility sync disabled via metadata');
return;
}
// ... rest of method
}
// Option 2: Disable calling trigger via TriggerSettings__c
🔍 Debugging Tips: - Enable debug logs for Product2 and BuyerGroupPricebook objects - Use Developer Console to trace execution - Query VisibleToBuyerGroups__c before and after to see changes - Check PricebookEntry records to verify product-pricebook associations - Verify BuyerGroupPricebook records are Active - Check B2B Commerce storefront to verify product visibility
📊 Performance Monitoring: - Monitor CPU time with large pricebook updates - Track execution time vs number of products updated - Watch for SOQL query row limit warnings - Monitor heap size with large datasets - Alert on DML exceptions - Dashboard for daily/weekly product visibility updates
🔗 Related Components: - BuyerGroupPricebookTrigger: Calls this helper (primary entry point) - BuyerGroup object: Defines buyer group membership - Pricebook2: Contains product pricing - PricebookEntry: Links products to pricebooks - Product2: Stores visibility settings - B2B Commerce Storefront: Uses visibility settings for product access - Buyer Account assignment: Links accounts to buyer groups
Business Owner¶
Primary: B2B Commerce Team / Sales Operations Secondary: IT Operations / E-Commerce Team Stakeholders: Product Management, Pricing Team, Customer Service, Development Team