{"id":2736,"date":"2024-02-19T16:25:02","date_gmt":"2024-02-19T21:25:02","guid":{"rendered":"https:\/\/morecpq.com\/?p=2736"},"modified":"2024-02-19T16:35:33","modified_gmt":"2024-02-19T21:35:33","slug":"salesforce-cpq-sharing-quotes-based-on-opportunity-team-members","status":"publish","type":"post","link":"https:\/\/morecpq.com\/index.php\/2024\/02\/19\/salesforce-cpq-sharing-quotes-based-on-opportunity-team-members\/","title":{"rendered":"Salesforce CPQ: Sharing Quotes based on Opportunity Team Members"},"content":{"rendered":"\n<p>Let&#8217;s think about the concept of <a href=\"https:\/\/developer.salesforce.com\/docs\/atlas.en-us.apexcode.meta\/apexcode\/apex_bulk_sharing_creating_with_apex.htm\" target=\"_blank\" rel=\"noreferrer noopener\">using Apex to create bulk sharing operations<\/a>, which is a fundamental aspect of implementing Apex Managed Sharing in Salesforce. Apex Managed Sharing is designed to programmatically share records when declarative sharing rules do not suffice, especially for custom objects or complex sharing requirements that can&#8217;t be addressed through Salesforce&#8217;s standard sharing model.<\/p>\n\n\n\n<p>Here&#8217;s how you can apply the concepts from this concept to share Salesforce CPQ (<code>SBQQ__Quote__c<\/code>) quotes based on Opportunity Team Members:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1: Define a Custom Share Reason (If Needed)<\/h3>\n\n\n\n<p>First, if you want to track why a record was shared for reporting or auditing purposes, you may define a custom share reason on the <code>SBQQ__Quote__Share<\/code> object. This is optional but can be helpful for clarity.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Navigate to Setup.<\/li>\n\n\n\n<li>Enter &#8220;Sharing Settings&#8221; in the Quick Find box, then click <code>Sharing Settings<\/code>.<\/li>\n\n\n\n<li>Under <code>SBQQ__Quote__c<\/code>, click <code>New<\/code> in the <code>Custom Sharing Reasons<\/code> section.<\/li>\n\n\n\n<li>Define your custom reason (e.g., &#8220;SharedWithOpportunityTeam&#8221;).<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2: Implement Apex Managed Sharing<\/h3>\n\n\n\n<p>Using Apex to programmatically share <code>SBQQ__Quote__c<\/code> records involves creating <code>SBQQ__Quote__Share<\/code> records, which are used to define sharing access between <code>SBQQ__Quote__c<\/code> records and users or groups.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class CPQQuoteSharingService {\n\n    public class Request {\n        @InvocableVariable(required = true)\n        public Id opportunityId;\n    }\n\n    public class Result {\n        @InvocableVariable\n        public String message;\n    }\n\n    @InvocableMethod(label = 'Share Quotes With Opportunity Team' description = 'Shares CPQ quotes with the members of the opportunity team and returns operation results.')\n    public static List&lt;Result> shareQuotesWithOpportunityTeam(List&lt;Request> requests) {\n        List&lt;Result> results = new List&lt;Result>();\n        Set&lt;Id> opportunityIds = new Set&lt;Id>();\n        for (Request req : requests) {\n            opportunityIds.add(req.opportunityId);\n        }\n\n        try {\n            \/\/ Fetch all quotes related to the specified opportunities\n            List&lt;SBQQ__Quote__c> quotes = &#91;SELECT Id FROM SBQQ__Quote__c WHERE SBQQ__Opportunity2__c IN :opportunityIds];\n        \n            \/\/ Fetch all Opportunity Team Members for these opportunities\n            List&lt;OpportunityTeamMember> teamMembers = &#91;SELECT UserId, OpportunityId FROM OpportunityTeamMember WHERE OpportunityId IN :opportunityIds];\n        \n            \/\/ List to hold the new share records\n            List&lt;SBQQ__Quote__Share> sharesToInsert = new List&lt;SBQQ__Quote__Share>();\n\n            \/\/ Create share records for each quote and team member combination\n            for (SBQQ__Quote__c quote : quotes) {\n                for (OpportunityTeamMember member : teamMembers) {\n                    if (quote.SBQQ__Opportunity2__c == member.OpportunityId) {\n                        SBQQ__Quote__Share share = new SBQQ__Quote__Share();\n                        share.ParentId = quote.Id; \/\/ The quote to share\n                        share.UserOrGroupId = member.UserId; \/\/ The user to share with\n                        share.AccessLevel = 'Read'; \/\/ Set the access level as needed\n                        share.RowCause = Schema.SBQQ__Quote__Share.RowCause.Manual; \/\/ Use your custom share reason if defined\n                        \n                        sharesToInsert.add(share);\n                    }\n                }\n            }\n        \n            \/\/ Attempt to insert the share records in bulk\n            if (!sharesToInsert.isEmpty()) {\n                Database.SaveResult&#91;] saveResults = Database.insert(sharesToInsert, false);\n\n                \/\/ Check the results for errors and prepare feedback\n                for (Database.SaveResult sr : saveResults) {\n                    Result result = new Result();\n                    if (sr.isSuccess()) {\n                        result.message = 'Success';\n                    } else {\n                        \/\/ There were errors during the insert operation, gather them\n                        String errMsg = '';\n                        for(Database.Error err : sr.getErrors()) {\n                            errMsg += err.getMessage() + '\\n';\n                        }\n                        result.message = 'Error: ' + errMsg;\n                    }\n                    results.add(result);\n                }\n            }\n        } catch (Exception e) {\n            \/\/ Handle unexpected exceptions\n            Result result = new Result();\n            result.message = 'Exception: ' + e.getMessage();\n            results.add(result);\n        }\n\n        \/\/ Ensure we return a result for each request, even if there's no specific message to return\n        while (results.size() &lt; requests.size()) {\n            results.add(new Result());\n        }\n\n        return results;\n    }\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3: Call the Sharing Method from Triggers<\/h3>\n\n\n\n<p>You&#8217;ll need to call <code>shareQuotesWithOpportunityTeam<\/code> from triggers on both the <code>OpportunityTeamMember<\/code> and <code>SBQQ__Quote__c<\/code> objects to ensure quotes are shared according to changes in team composition or quote creation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Considerations:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Governor Limits<\/strong>: Be mindful of governor limits, especially SOQL queries and DML operations. Ensure your implementation is bulk-safe.<\/li>\n\n\n\n<li><strong>Access Level<\/strong>: Adjust the <code>AccessLevel<\/code> according to your specific sharing requirements (e.g., <code>Read<\/code>, <code>Edit<\/code>).<\/li>\n\n\n\n<li><strong>Testing<\/strong>: Rigorously test your sharing logic in a sandbox environment before deploying to production to ensure it behaves as expected under various scenarios.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Key Points<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>InvocableVariable<\/strong>: Each parameter that the Flow will pass to the Apex method needs to be wrapped in a class where each parameter is annotated with <code>@InvocableVariable<\/code>. This is because <code>@InvocableMethod<\/code> expects its parameters to be lists, as it&#8217;s designed to handle bulk operations.<\/li>\n\n\n\n<li><strong>Bulk Operations<\/strong>: The method processes a list of requests to handle bulk operations efficiently, allowing multiple opportunity IDs to be processed in a single call from the Flow.<\/li>\n\n\n\n<li><strong>Error Handling<\/strong>: The method should include error handling logic (not shown in the example) to gracefully deal with any issues during the insertion of share records. This could involve returning information about any failures back to the Flow for further action.<\/li>\n<\/ul>\n\n\n\n<p>By following these steps, you can create a dynamic sharing model for Salesforce CPQ quotes that respects the composition of Opportunity Teams, aligning with the guidance provided in the Salesforce documentation on bulk sharing creation with Apex.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s think about the concept of using Apex to create bulk sharing operations, which is a fundamental aspect of implementing Apex Managed Sharing in Salesforce. Apex Managed Sharing is designed to programmatically share records when declarative sharing rules do not suffice, especially for custom objects or complex sharing requirements that can&#8217;t be addressed through Salesforce&#8217;s &hellip;<br \/><a href=\"https:\/\/morecpq.com\/index.php\/2024\/02\/19\/salesforce-cpq-sharing-quotes-based-on-opportunity-team-members\/\" class=\"more-link pen_button pen_element_default pen_icon_arrow_double\">Continue reading <span class=\"screen-reader-text\">Salesforce CPQ: Sharing Quotes based on Opportunity Team Members<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2736","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_featured_media_url":"","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/2736","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/comments?post=2736"}],"version-history":[{"count":11,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/2736\/revisions"}],"predecessor-version":[{"id":2748,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/2736\/revisions\/2748"}],"wp:attachment":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/media?parent=2736"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/categories?post=2736"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/tags?post=2736"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}