{"id":1071,"date":"2021-06-03T13:29:42","date_gmt":"2021-06-03T13:29:42","guid":{"rendered":"http:\/\/morecpq.com\/?p=1071"},"modified":"2021-06-03T15:48:32","modified_gmt":"2021-06-03T15:48:32","slug":"quote-template-custom-lines-section","status":"publish","type":"post","link":"https:\/\/morecpq.com\/index.php\/2021\/06\/03\/quote-template-custom-lines-section\/","title":{"rendered":"Quote Template &#8211; Custom Lines Section"},"content":{"rendered":"\n<p>Today, I realized that I&#8217;ve not done a custom lines section article!  A travesty of justice!  Or just an oversight.  Either way, here it is!  This includes a controller APEX class, a VisualForce page and a Template Content that is of type Custom.  Have at it below!<\/p>\n\n\n\n<p>This example is a lines section that groups lines by Product Family.  It also rolls up lines by Product!  So there will be a section of Hardware, and a rollup of hardware by product within that section (combining quantities of same products).<\/p>\n\n\n\n<p><strong>APEX Class (Controller)<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:12px\"><code>\/\/ make sure to put \/apex\/c__CustomLinesSection in the Custom Source of the Template Content\npublic class CustomLinesSectionController {\n    public Map&lt;String, Map&lt;String, SBQQ__QuoteLine__c>> linesByByProductCodeByProductFamily {get;set;}\n    public Map&lt;String, Decimal> netTotalByProductCode {get;set;}\n    public Map&lt;String, Decimal> netTotalByFamily {get;set;}\n    public CustomLinesSectionController() {\n        Id quoteId = (Id)ApexPages.currentPage().getParameters().get('qid');\n        linesByByProductCodeByProductFamily = new Map&lt;String, Map&lt;String, SBQQ__QuoteLine__c>>();\n        netTotalByProductCode = new Map&lt;String, Decimal>();\n        netTotalByFamily = new Map&lt;String, Decimal>();\n        for (SBQQ__QuoteLine__c ql : &#91;SELECT Id, SBQQ__Number__c, SBQQ__ListPrice__c, SBQQ__Quantity__c, SBQQ__ProductName__c, SBQQ__ProductCode__c, SBQQ__NetTotal__c, SBQQ__ProductFamily__c FROM SBQQ__QuoteLine__c WHERE SBQQ__Quote__c = :quoteId AND SBQQ__Hidden__c = false ORDER BY SBQQ__Number__c]) {\n            \/\/ by product group.\n            String family = ql.SBQQ__ProductFamily__c == null ? 'General' : ql.SBQQ__ProductFamily__c;\n            if (!linesByByProductCodeByProductFamily.containsKey(family)) {\n                linesByByProductCodeByProductFamily.put(family, new Map&lt;String, SBQQ__QuoteLine__c>());\n            }\n            \n            \/\/ by product code.\n            String key = (ql.SBQQ__ProductCode__c == null || ql.SBQQ__ProductCode__c == '' ? 'null' : ql.SBQQ__ProductCode__c) + ql.SBQQ__ProductName__c;\n            if (!linesByByProductCodeByProductFamily.get(family).containsKey(key)) {\n                linesByByProductCodeByProductFamily.get(family).put(key, ql);\n            } else {\n                linesByByProductCodeByProductFamily.get(family).get(key).SBQQ__Quantity__c += ql.SBQQ__Quantity__c;\n            }\n            \n            \/\/ net total.\n            if (!netTotalByProductCode.containsKey(key)) {\n                netTotalByProductCode.put(key, 0);\n            }\n            if (ql.SBQQ__NetTotal__c > 0) {\n                netTotalByProductCode.put(key, netTotalByProductCode.get(key) + ql.SBQQ__NetTotal__c);\n            }\n            \n            \/\/ net total by group.\n            if (!netTotalByFamily.containsKey(family)) {\n                netTotalByFamily.put(family, ql.SBQQ__NetTotal__c);\n            } else {\n                netTotalByFamily.put(family, netTotalByFamily.get(family) + ql.SBQQ__NetTotal__c);\n            }\n        }\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>APEX Unit Test Class<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:12px\"><code>@isTest\npublic class CustomLinesSectionControllerTester {\n    static testMethod void QuoteByLocation_PostInstall() {\n        Product2 p = new Product2(Name='test', IsActive=True);\n        insert p;\n        \n        Id pbid = Test.getStandardPricebookId();\n        PricebookEntry pbe = new PricebookEntry(Pricebook2Id=pbid, Product2Id=p.id, UnitPrice=1, IsActive=True);\n        insert pbe;\n        \n        SBQQ__Quote__c q = new SBQQ__Quote__c(SBQQ__Pricebook__c=pbid);\n        insert q;\n        \n        SBQQ__QuoteLine__c ql1 = new SBQQ__QuoteLine__c(SBQQ__Quote__c=q.id, SBQQ__Product__c=p.id, SBQQ__Quantity__c=1);\n        SBQQ__QuoteLine__c ql2 = new SBQQ__QuoteLine__c(SBQQ__Quote__c=q.id, SBQQ__Product__c=p.id, SBQQ__Quantity__c=1);\n        insert new List&lt;SBQQ__QuoteLine__c&gt; {ql1, ql2};\n        \n        PageReference pageRef = Page.CustomLinesSection;\n        Test.setCurrentPage(pageRef);\n        ApexPages.currentPage().getParameters().put('qid', q.id);\n        \n        CustomLinesSectionController clsc = new CustomLinesSectionController();\n    }\n}<\/code><\/pre>\n\n\n\n<p><strong>VisualForce Page<\/strong><br>This is the VisualForce Page that renders the custom lines section.  Notice!  It doesn&#8217;t look like HTML!  That is correct.  The CPQ document generator requires custom content to be in a form of XML called XSL:FO.  <a href=\"https:\/\/w3schools.sinsixx.com\/xslfo\/xslfo_documents.asp.htm\" target=\"_blank\" rel=\"noreferrer noopener\">Here&#8217;s<\/a> some documentation on it!<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:12px\"><code>&lt;apex:page showHeader=\"false\" sidebar=\"false\" cache=\"false\" contentType=\"text\/xml\" controller=\"CustomLinesSectionController\" >\n    &lt;!-- make sure to put \/apex\/c__CustomLinesSection in the Custom Source of the Template Content -->\n    &lt;apex:repeat var=\"family\" value=\"{!linesByByProductCodeByProductFamily}\">\n        &lt;table table-layout=\"fixed\" width=\"100%\">\n            &lt;table-column column-width=\"100%\" \/>\n            &lt;table-body>\n                &lt;table-row>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#2B3263\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block color=\"#FFFFFF\" font-weight=\"bold\" font-size=\"9pt\" padding=\"2pt\">{!HTMLENCODE(family)}&lt;\/block>\n                    &lt;\/table-cell>\n                &lt;\/table-row>\n            &lt;\/table-body>\n        &lt;\/table>\n        &lt;table table-layout=\"fixed\" width=\"100%\">\n            &lt;table-column column-width=\"50%\" \/>\n            &lt;table-column column-width=\"5%\" \/>\n            &lt;table-column column-width=\"20%\" \/>\n            &lt;table-column column-width=\"25%\" \/>\n            &lt;table-body>\n                &lt;table-row>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block color=\"#000000\" font-weight=\"bold\">\n                            Product\n                        &lt;\/block>\n                    &lt;\/table-cell>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block color=\"#000000\" font-weight=\"bold\">\n                            Qty \n                        &lt;\/block>\n                    &lt;\/table-cell>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block color=\"#000000\" font-weight=\"bold\">\n                            Item Price \n                        &lt;\/block>\n                    &lt;\/table-cell>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block color=\"#000000\" font-weight=\"bold\">\n                            Extended Price \n                        &lt;\/block>\n                    &lt;\/table-cell>\n                &lt;\/table-row>\n                &lt;apex:repeat var=\"code\" value=\"{!linesByByProductCodeByProductFamily&#91;family]}\">\n                    &lt;table-row>\n                        &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                            &lt;block >\n                                {!HTMLENCODE(linesByByProductCodeByProductFamily&#91;family]&#91;code].SBQQ__ProductName__c)}\n                            &lt;\/block>\n                        &lt;\/table-cell>\n                        &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                            &lt;block >\n                                &lt;apex:outputText value=\"{0, number, ###,###,###,###}\">\n                                    &lt;apex:param value=\"{!linesByByProductCodeByProductFamily&#91;family]&#91;code].SBQQ__Quantity__c}\"\/>\n                                &lt;\/apex:outputText>\n                            &lt;\/block>\n                        &lt;\/table-cell>\n                        &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                            &lt;block text-align=\"right\">\n                                &lt;apex:outputText value=\"{0, number, $###,###,###,##0.00}\">\n                                    &lt;apex:param value=\"{!linesByByProductCodeByProductFamily&#91;family]&#91;code].SBQQ__ListPrice__c}\"\/>\n                                &lt;\/apex:outputText>\n                            &lt;\/block>\n                        &lt;\/table-cell>\n                        &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                            &lt;block text-align=\"right\">\n                                &lt;apex:outputText value=\"{0, number, $###,###,###,##0.00}\">\n                                    &lt;apex:param value=\"{!netTotalByProductCode&#91;code]}\"\/>\n                                &lt;\/apex:outputText>\n                            &lt;\/block>\n                        &lt;\/table-cell>\n                    &lt;\/table-row>\n                &lt;\/apex:repeat>\n                &lt;table-row>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"0px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block >\n                            &amp;nbsp;\n                        &lt;\/block>\n                    &lt;\/table-cell>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"0px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block >\n                            &amp;nbsp;\n                        &lt;\/block>\n                    &lt;\/table-cell>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block text-align=\"right\" font-weight=\"bold\">\n                            Total\n                        &lt;\/block>\n                    &lt;\/table-cell>\n                    &lt;table-cell padding=\"2pt\" background-color=\"#FFFFFF\" font-size=\"9pt\" border-width=\"1px\" border-color=\"#000000\" border-style=\"solid\">\n                        &lt;block text-align=\"right\">\n                            &lt;apex:outputText value=\"{0, number, $###,###,###,##0.00}\">\n                                &lt;apex:param value=\"{!netTotalByFamily&#91;family]}\"\/>\n                            &lt;\/apex:outputText>\n                        &lt;\/block>\n                    &lt;\/table-cell>\n                &lt;\/table-row>\n            &lt;\/table-body>\n        &lt;\/table>\n        &lt;block>&amp;nbsp;&lt;\/block>\n    &lt;\/apex:repeat>\n&lt;\/apex:page><\/code><\/pre>\n\n\n\n<p><strong>Template Content (Custom)<\/strong><\/p>\n\n\n\n<p class=\"has-small-font-size\">Content Name: Lines (Custom) Content<br>Custom Source: \/apex\/c__CustomLinesSection<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/06\/image.png?resize=713%2C220\" alt=\"\" class=\"wp-image-1078\" width=\"713\" height=\"220\" srcset=\"https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/06\/image.png?w=771&amp;ssl=1 771w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/06\/image.png?resize=300%2C93&amp;ssl=1 300w, https:\/\/i0.wp.com\/morecpq.com\/wp-content\/uploads\/2021\/06\/image.png?resize=768%2C237&amp;ssl=1 768w\" sizes=\"(max-width: 713px) 100vw, 713px\" \/><\/figure>\n\n\n\n<p><strong>Install Links<\/strong><br>Here&#8217;s some links for you to install the above things!<br><a href=\"https:\/\/test.salesforce.com\/packaging\/installPackage.apexp?p0=04t4x000000lxMa\" target=\"_blank\" rel=\"noreferrer noopener\">Sandbox<\/a> | <a href=\"https:\/\/login.salesforce.com\/packaging\/installPackage.apexp?p0=04t4x000000lxMa\" target=\"_blank\" rel=\"noreferrer noopener\">Production<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today, I realized that I&#8217;ve not done a custom lines section article! A travesty of justice! Or just an oversight. Either way, here it is! This includes a controller APEX class, a VisualForce page and a Template Content that is of type Custom. Have at it below! This example is a lines section that groups &hellip;<br \/><a href=\"https:\/\/morecpq.com\/index.php\/2021\/06\/03\/quote-template-custom-lines-section\/\" class=\"more-link pen_button pen_element_default pen_icon_arrow_double\">Continue reading <span class=\"screen-reader-text\">Quote Template &#8211; Custom Lines Section<\/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-1071","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\/1071","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=1071"}],"version-history":[{"count":15,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/1071\/revisions"}],"predecessor-version":[{"id":1090,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/posts\/1071\/revisions\/1090"}],"wp:attachment":[{"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/media?parent=1071"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/categories?post=1071"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/morecpq.com\/index.php\/wp-json\/wp\/v2\/tags?post=1071"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}