Skip to content

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

public static void updateVisibleBuyerGroups(List<BuyerGroupPricebook> bgpbList)

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): Collection of BuyerGroupPricebook records to process. Should contain IsActive, Pricebook2Id, and BuyerGroupId fields populated.

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 for all active associations 3. Queries PricebookEntry to find all products in those pricebooks 4. Builds map of Product2Id → Set by combining pricebook and buyer group associations 5. Queries existing Product2 records to get current VisibleToBuyerGroups__c values 6. For each product: - Parses existing comma-separated buyer group IDs from VisibleToBuyerGroups__c - Adds new buyer group IDs from the bgpbList - Deduplicates using Set - Converts back to comma-separated string 7. Updates all modified Product2 records with new visibility settings

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