Developer Resources
Create Quote & Create Order Gl...
Advanced Bundle Scenarios
22 min
this article covers advanced https //docs nue io/product bundle configurations beyond the basics in 04 bundles md https //docs nue io/dynamic product options , per addon overrides, multi level nesting, mixed bundle/standalone products, large quantity stress scenarios, and negative validation cases 1\ dynamic product options bundles can include https //docs nue io/dynamic product options — options where the specific product is not hardcoded but instead matched at runtime using a filter expression a ruby productoption c with ruby isdynamic c = true uses the ruby optionproductfilter c json field to define a product filter (e g , name like '%uds%' ) key behaviors static (bundled) items are auto included when the bundle is added dynamic items are not auto included — they must be explicitly specified as add ons an explicit add on matching a dynamic option's filter is accepted an add on not matching any option (static or dynamic) is rejected with invalid addon product multiple dynamic add ons can be specified in the same request a product matching both a static option and a dynamic option appears only once dynamic add on accepted // upgrade downgrade and swap bundle has a dynamic option with filter // name like '%uds%' // product 'uds migration tool' matches this filter ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request quote = new quote( opportunityid = opp id, name = 'dynamic option quote', pricebook2id = stdpricebookid, ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month' ); ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'upgrade downgrade and swap'; bundle uom = 'user/month'; bundle quantity = 5; // explicitly add a product matching the dynamic filter ruby globalapitypes productinput dynamicaddon = new ruby globalapitypes productinput(); dynamicaddon productsku = 'uds migration tool'; bundle addons = new list\<ruby globalapitypes productinput>{ dynamicaddon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // line items include bundle parent + static bundled items + uds migration tool invalid add on rejected // product 'unrelated product' does not match any option (static or dynamic) ruby globalapitypes productinput invalidaddon = new ruby globalapitypes productinput(); invalidaddon productsku = 'unrelated product'; bundle addons = new list\<ruby globalapitypes productinput>{ invalidaddon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('failure', response status); // error code invalid addon product multiple dynamic add ons // two products matching the dynamic filter ruby globalapitypes productinput addon1 = new ruby globalapitypes productinput(); addon1 productsku = 'uds migration tool'; ruby globalapitypes productinput addon2 = new ruby globalapitypes productinput(); addon2 productsku = 'uds sync engine'; bundle addons = new list\<ruby globalapitypes productinput>{ addon1, addon2 }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // both dynamic add ons are included alongside static bundled items product matching both static and dynamic option when a product matches both a static (bundled) option and a dynamic option filter, it appears only once in the line items — the static option takes precedence // bundled product matches both a static option and the dynamic filter ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'bundled product'; bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // product appears once (not duplicated), resolved via the static option 2\ per add on overrides in bundles add on products within a bundle can have their own https //docs nue io/understanding subscription term basis and proration and billing overrides, independent of the bundle parent and quote header settings subscription term override on add on ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request quote = new quote( opportunityid = opp id, name = 'per addon override quote', pricebook2id = stdpricebookid, ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, // quote level 12 months ruby subscriptiontermdimension c = 'month' ); ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 10; // add on with 6 month term override (vs quote level 12 months) ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'impl service'; addon subscriptionterm = 6; addon subscriptiontermdimension = 'month'; bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // find the impl service line item for (ruby globalapitypes quotelineitemdto li response data lineitems) { quotelineitem qli = li quotelineitem; if (qli product2 stockkeepingunit == 'impl service') { system assertequals(6, qli ruby subscriptionterm c); // overridden to 6 months } } billing period and timing override on add on ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'impl service'; addon billingperiod = 'monthly'; // stored as 'month' addon billingtiming = 'in advance'; bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); for (ruby globalapitypes quotelineitemdto li response data lineitems) { quotelineitem qli = li quotelineitem; if (qli product2 stockkeepingunit == 'impl service') { system assertequals('month', qli ruby billingperiod c); system assertequals('in advance', qli ruby billingtiming c); } } auto renew override on nested bundle add on overrides apply even to nested bundles within the outer bundle // nue ultimate edition contains tax manager (itself a bundle) ruby globalapitypes productinput outerbundle = new ruby globalapitypes productinput(); outerbundle productsku = 'nue ultimate edition'; outerbundle uom = 'user/month'; outerbundle quantity = 10; // override autorenew on the nested tax manager bundle ruby globalapitypes productinput nestedbundle = new ruby globalapitypes productinput(); nestedbundle productsku = 'tax manager'; nestedbundle autorenew = false; outerbundle addons = new list\<ruby globalapitypes productinput>{ nestedbundle }; request products = new list\<ruby globalapitypes productinput>{ outerbundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); for (ruby globalapitypes quotelineitemdto li response data lineitems) { quotelineitem qli = li quotelineitem; if (qli product2 stockkeepingunit == 'tax manager') { system assertequals(false, qli ruby autorenew c); } } 3\ multi level explicit add on configuration three levels of nesting a root bundle containing an explicit add on that is itself a bundle with its own bundled children ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request quote = new quote( opportunityid = opp id, name = 'multi level bundle quote', pricebook2id = stdpricebookid, ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month' ); // level 1 root bundle ruby globalapitypes productinput rootbundle = new ruby globalapitypes productinput(); rootbundle productsku = 'pricing rule'; rootbundle uom = 'user/month'; rootbundle quantity = 10; // level 2 explicit add on that is itself a bundle ruby globalapitypes productinput childbundle = new ruby globalapitypes productinput(); childbundle productsku = 'pot management'; // pot management is a bundle with its own bundled children (level 3) rootbundle addons = new list\<ruby globalapitypes productinput>{ childbundle }; request products = new list\<ruby globalapitypes productinput>{ rootbundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // 3 level nesting produces 6 line items (example) // 1 pricing rule (root bundle parent) // 2 pricing rule bundled child // 3 pot management (level 2 bundle parent) // 4 pot management bundled child a // 5 pot management bundled child b // 6 pot management bundled child c system debug('total line items ' + response data lineitems size()); // each child has a parentid linking it to its bundle parent for (ruby globalapitypes quotelineitemdto li response data lineitems) { system debug(li quotelineitem product2 stockkeepingunit + ' | parentid=' + li parentid); } 4\ bundle + standalone mixed the same product can appear both inside a bundle and as a standalone line item on the same quote they are treated as separate line items with different parentid values same product in bundle and standalone ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request quote = new quote( opportunityid = opp id, name = 'bundle + standalone quote', pricebook2id = stdpricebookid, ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month' ); // product as part of a bundle ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 10; // same product as standalone ruby globalapitypes productinput standalone = new ruby globalapitypes productinput(); standalone productsku = 'nue on salesforce'; // also appears inside nue gem edition standalone uom = 'user/month'; standalone quantity = 5; request products = new list\<ruby globalapitypes productinput>{ bundle, standalone }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // nue on salesforce appears twice // 1 as a bundle child (parentid = bundle parent's id) // 2 as a standalone (parentid = null) integer nueonsfcount = 0; for (ruby globalapitypes quotelineitemdto li response data lineitems) { if (li quotelineitem product2 stockkeepingunit == 'nue on salesforce') { nueonsfcount++; } } system assertequals(2, nueonsfcount); two bundles + standalone // two different bundles plus a standalone product ruby globalapitypes productinput bundle1 = new ruby globalapitypes productinput(); bundle1 productsku = 'nue gem edition'; bundle1 uom = 'user/month'; bundle1 quantity = 10; ruby globalapitypes productinput bundle2 = new ruby globalapitypes productinput(); bundle2 productsku = 'nue ultimate edition'; bundle2 uom = 'user/month'; bundle2 quantity = 5; ruby globalapitypes productinput standalone = new ruby globalapitypes productinput(); standalone productsku = 'revenue lifecycle platform'; standalone uom = 'user/month'; standalone quantity = 3; request products = new list\<ruby globalapitypes productinput>{ bundle1, bundle2, standalone }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // line items are flattened depth first // bundle1 parent → bundle1 children → bundle2 parent → bundle2 children → standalone 5\ large bundle quantity (stress test) verify that the api handles bundles with large quantities and extended terms, including correct pricing calculations and database persistence ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request quote = new quote( opportunityid = opp id, name = 'large bundle stress test', pricebook2id = stdpricebookid, ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 24, ruby subscriptiontermdimension c = 'month' ); ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue ultimate edition'; bundle uom = 'user/month'; bundle quantity = 10; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = true; // persist to database ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // quote persisted with id system assertnotequals(null, response data quote id); // verify large pricing total (e g , $11,976 00 for 10 qty x 24 months) system assertnotequals(null, response data quote ruby totalprice c); system debug('total price ' + response data quote ruby totalprice c); // verify all line items were persisted integer linecount = \[select count() from quotelineitem where quoteid = \ response data quote id]; system assertequals(response data lineitems size(), linecount); 6\ negative validation the api validates billing and subscription settings on add on products and returns structured error codes invalid billing period ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 10; ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'impl service'; addon billingperiod = 'biweekly'; // invalid value bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('failure', response status); system assertequals('product billingperiod invalid', response errors\[0] errortype); zero subscription term ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 10; ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'impl service'; addon subscriptionterm = 0; // invalid must be > 0 bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('failure', response status); system assertequals('product subscriptionterm invalid', response errors\[0] errortype); invalid billing timing ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 10; ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'impl service'; addon billingtiming = 'on delivery'; // invalid value bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; request iscommit = false; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('failure', response status); system assertequals('product billingtiming invalid', response errors\[0] errortype); summary of error codes for bundle validation scenario error code add on not matching any bundle option invalid addon product invalid billing period value product billingperiod invalid subscription term <= 0 product subscriptionterm invalid invalid billing timing value product billingtiming invalid renewal term <= 0 product renewalterm invalid evergreen + subscriptionterm conflict invalid input
Have a question?
Get answers fast with Nue’s intelligent AI, expert support team, and a growing community of users - all here to help you succeed.
To ask a question or participate in discussions, you'll need to authenticate first.