{"id":1117,"date":"2021-08-12T20:04:56","date_gmt":"2021-08-12T20:04:56","guid":{"rendered":"https:\/\/morecpq.com\/?p=1117"},"modified":"2022-10-21T13:48:08","modified_gmt":"2022-10-21T13:48:08","slug":"subscription-conversion-one-per-unit","status":"publish","type":"post","link":"https:\/\/morecpq.com\/index.php\/2021\/08\/12\/subscription-conversion-one-per-unit\/","title":{"rendered":"Subscription Conversion &#8211; One per unit"},"content":{"rendered":"\n<p>You know how on the Product record, you can set up a Product to convert to an Asset?  And you can either make it convert to an Asset One per Quote Line or One per unit?  One per unit means a Quote Line with a Quantity of 5 gets 5 separate Asset records.<\/p>\n\n\n\n<p>How great!  But.  What about Subscriptions?  You can&#8217;t do this with Subscriptions!  Gah.  Not good.  What if I want to convert a Subscription Product to a Subscription record, One per unit, just like Assets can be?<\/p>\n\n\n\n<p>Well!  you&#8217;ve come to the right place.  \ud83d\ude03  Below is a solution to this requirement.  It&#8217;s a couple Custom Fields, a small Apex Class, and a Process Builder.  This solution works with all of the out of the box CPQ functionality.  Contracts, Amendments, Renewals, MDQ, you name it.  The flow is as follows.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Create Quote with a line of a Subscription Product that has Subscription Conversion set to One per unit.<\/li><li>Contract Opportunity.<ul><li>This solution will separate the Subscriptions into one record per unit just like the Asset Conversion field does.<\/li><\/ul><\/li><li>Amend this Contract.<ul><li>CPQ will combine all Subscriptions into one Quote Line on the Amendment Quote.<\/li><\/ul><\/li><li>Contract this Amendment Opportunity.<ul><li><span style=\"color: initial; font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, Oxygen-Sans, Ubuntu, Cantarell, &quot;Helvetica Neue&quot;, sans-serif;\">Updated Quantities on the Amendment Quote will produce similar separated out Subscription records<\/span>.<\/li><\/ul><\/li><li>Check the Renewal Forecast\/Quoted checkbox(es).<ul><li>CPQ will combine all Subscriptions into one Quote Line on the Renewal Quote.<\/li><\/ul><\/li><\/ul>\n\n\n\n<p><strong>Custom Fields<br><\/strong>There are two Custom Fields that are involved in this solution.  <\/p>\n\n\n\n<p>The first field is on the Product object.  It&#8217;s a Picklist called Subscription Conversion (just like Asset Conversion) with the same values as Asset Conversion.  The &#8220;One per unit&#8221; value will separate the Subscriptions into different records with 1 Quantity.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"891\" height=\"787\" src=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-2.png?resize=891%2C787&#038;ssl=1\" alt=\"\" class=\"wp-image-1122\" srcset=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-2.png?w=891&amp;ssl=1 891w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-2.png?resize=300%2C265&amp;ssl=1 300w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-2.png?resize=768%2C678&amp;ssl=1 768w\" sizes=\"(max-width: 891px) 100vw, 891px\" \/><\/figure>\n\n\n\n<p>The second field is a Formula on the Subscription that points to the above field.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"883\" height=\"458\" src=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-4.png?resize=883%2C458&#038;ssl=1\" alt=\"\" class=\"wp-image-1124\" srcset=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-4.png?w=883&amp;ssl=1 883w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-4.png?resize=300%2C156&amp;ssl=1 300w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/08\/image-4.png?resize=768%2C398&amp;ssl=1 768w\" sizes=\"(max-width: 883px) 100vw, 883px\" \/><\/figure>\n\n\n\n<p><strong>Apex Class<br><\/strong>There is one Apex Class that provides all of the magic for this.  It is set up as a batch class in case you have a giant Quantity on your Subscriptions.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:10px\"><code>public class SubscriptionHelper implements Database.Batchable&lt;sObject> {\n    public static boolean ranSplitSubs = false;\n    public List&lt;SBQQ__Subscription__c> subsToInsert;\n    \n    \/\/ function to split subscriptions by quantity if the subscription's product's subscription conversion field is set to One per unit.\n    @InvocableMethod\n    public static void createNewSplitSubscriptionsByQuantity(List&lt;SBQQ__Subscription__c> subs) {\n        if (!SubscriptionHelper.ranSplitSubs) {\n            SubscriptionHelper.ranSplitSubs = true;\n            \n            \/\/ create subscriptions to insert list.\n            List&lt;SBQQ__Subscription__c> subsToInsert = new List&lt;SBQQ__Subscription__c>();\n            \n            \/\/ create subscriptions to update list.\n            Map&lt;Id, SBQQ__Subscription__c> subsToUpdateMap = new Map&lt;Id, SBQQ__Subscription__c>();\n            \n            \/\/ loop through subscriptions.\n            for (SBQQ__Subscription__c s : subs) {\n                \/\/ if this subscription's product has the subscription conversion field set to One per unit,\n                if (s.Subscription_Conversion__c == 'One per unit' &amp;&amp; s.SBQQ__Quantity__c > 1 || s.SBQQ__Quantity__c &lt; 1) {\n                    \/\/ add original subscription to subscriptions to update list.\n                    subsToUpdateMap.put(s.id, s);\n                    \n                    \/\/ loop subscription . quantity times.\n                    for (decimal i = 1; i &lt; Math.abs(s.SBQQ__Quantity__c); i++) {\n                        \/\/ clone subscription.\n                        SBQQ__Subscription__c cs = s.clone();\n                        \n                        \/\/ set pertinent values on duplicate subscriptions.\n                        cs.id = null;\n                        cs.SBQQ__Quantity__c = s.SBQQ__Quantity__c > 1 ? 1 : -1;\n                        cs.SBQQ__RenewalQuantity__c = s.SBQQ__Quantity__c > 1 ? 1 : -1;\n                        if (cs.SBQQ__RevisedSubscription__c == null) {\n                            cs.SBQQ__RevisedSubscription__c = s.id;\n                        }\n                        \n                        \/\/ add cloned subscription to subscriptions to insert list.\n                        subsToInsert.add(cs);\n                    }\n                }\n            }\n            \n            \/\/ if we have subscriptions to update,\n            if (subsToUpdateMap.size() > 0) {\n                \/\/ query subscriptions to update.\n                subsToUpdateMap = new Map&lt;id, SBQQ__Subscription__c>(&#91;select Id, SBQQ__Quantity__c, SBQQ__RootId__c from SBQQ__Subscription__c where Id in :subsToUpdateMap.keySet()]);\n                \n                \/\/ loop through subscriptions.\n                for (SBQQ__Subscription__c s : subsToUpdateMap.values()) {\n                    \/\/ make updates.\n                    s.SBQQ__Quantity__c = s.SBQQ__Quantity__c > 1 ? 1 : -1;\n                    s.SBQQ__RenewalQuantity__c = s.SBQQ__Quantity__c;\n                    s.SBQQ__RootId__c = s.id;\n                }\n                System.debug('subsToUpdateMap: '+subsToUpdateMap);\n                \/\/ update subscriptions to update.\n                update subsToUpdateMap.values();\n            }\n            \n            \/\/ if we have subscriptions to insert,\n            if (subsToInsert.size() > 0) {\n                System.debug('subsToInsert: '+subsToInsert);\n                \/\/ insert subscriptions to insert.\n                Id batchId = Database.executeBatch(new SubscriptionHelper(subsToInsert), 100);\n            }\n        }\n    }\n    \n    public SubscriptionHelper(List&lt;SBQQ__Subscription__c> subs) { subsToInsert = subs; }\n    public Iterable&lt;sObject> start(Database.BatchableContext bc) { return subsToInsert; }\n    public void execute(Database.BatchableContext bc, List&lt;SBQQ__Subscription__c> records) { insert records; }\n    public void finish(Database.BatchableContext bc) {}\n}<\/code><\/pre>\n\n\n\n<p><strong><strong>Test<\/strong><\/strong> <strong>Apex Class<br><\/strong>Here&#8217;s the Test Apex Class for the above Apex Class.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:10px\"><code>@isTest\npublic class SubscriptionHelperTester {\n    @isTest static void test() {\n        Product2 p = new Product2(Name='Test', IsActive=True, Subscription_Conversion__c='One per unit');\n        insert p;\n        Account a = new Account(Name='Test');\n        insert a;\n        Contract c = new Contract(AccountId=a.id);\n        insert c;\n        \n        Test.startTest();\n        SBQQ__Subscription__c s = new SBQQ__Subscription__c(SBQQ__Product__c = p.id, SBQQ__Quantity__c=5);\n        insert s;\n        s = &#91;select Id, SBQQ__Quantity__c, SBQQ__RootId__c, Subscription_Conversion__c, SBQQ__RevisedSubscription__c from SBQQ__Subscription__c where Id = :s.id];\n        SubscriptionHelper.createNewSplitSubscriptionsByQuantity(new List&lt;SBQQ__Subscription__c&gt;{ s });\n        Test.stopTest();\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>Flow<br><\/strong>This Flow executes on create only and calls the Apex Class above in a Scheduled Action.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"907\" height=\"541\" src=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2022\/10\/Snip-Subscription-On-After-Create-V1.png?resize=907%2C541&#038;ssl=1\" alt=\"\" class=\"wp-image-2139\" srcset=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2022\/10\/Snip-Subscription-On-After-Create-V1.png?w=907&amp;ssl=1 907w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2022\/10\/Snip-Subscription-On-After-Create-V1.png?resize=300%2C179&amp;ssl=1 300w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2022\/10\/Snip-Subscription-On-After-Create-V1.png?resize=768%2C458&amp;ssl=1 768w\" sizes=\"(max-width: 907px) 100vw, 907px\" \/><\/figure>\n\n\n\n<p><strong>Install Links<\/strong><br>If you are here at the end, you deserve a great reward!  Below are some install links for all of the above configuration!  Install and modify as needed.<strong><br><\/strong><a href=\"https:\/\/login.salesforce.com\/packaging\/installPackage.apexp?p0=04t4x000000adf5&amp;isdtp=p1\" target=\"_blank\" rel=\"noreferrer noopener\">Production<\/a> | <a href=\"https:\/\/test.salesforce.com\/packaging\/installPackage.apexp?p0=04t4x000000adf5&amp;isdtp=p1\" target=\"_blank\" rel=\"noreferrer noopener\">Sandbox<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>You know how on the Product record, you can set up a Product to convert to an Asset? And you can either make it convert to an Asset One per Quote Line or One per unit? One per unit means a Quote Line with a Quantity of 5 gets 5 separate Asset records. How great! &hellip;<br \/><a href=\"https:\/\/morecpq.com\/index.php\/2021\/08\/12\/subscription-conversion-one-per-unit\/\" class=\"more-link pen_button pen_element_default pen_icon_arrow_double\">Continue reading <span class=\"screen-reader-text\">Subscription Conversion &#8211; One per unit<\/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-1117","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\/1117","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=1117"}],"version-history":[{"count":17,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/1117\/revisions"}],"predecessor-version":[{"id":2143,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/1117\/revisions\/2143"}],"wp:attachment":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/media?parent=1117"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/categories?post=1117"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/tags?post=1117"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}