Developer Resources
Create Quote & Create Order Gl...
Discounts: Percentage, Amount, and Propagation
19 min
the https //docs nue io/create quotes and orders supports discretionary discounts at three layers — header , bundle (parent) , and line — plus system discounts from https //docs nue io/price and discount tags each layer exists so that sales users and integrations can express discounts at the level that matches their intent, without having to manually calculate the discount for every individual line item why three layers? in real world quoting, discounts rarely apply uniformly to every line a sales rep might want to give a blanket 10% off the entire quote — without touching each line individually that's a header level discount offer 20% off the primary product bundle , while giving other products a different discount that's a bundle level discount protect a professional services line from discounting — by explicitly setting it to 0% — while the rest of the bundle inherits the bundle discount that's a line level override set a total dollar discount for the deal — such as "$10,000 off" — and let the engine distribute it proportionally across eligible lines, automatically skipping any line that already has its own explicit discount that's a header discount amount the three layer model means you describe your intent at the right level, and the pricing engine handles the per line math for you discount layers true 165,165,165,167 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type how they interact header discount percentage ( ruby discount c ) is a blanket mechanism — it applies the same percentage to every eligible line it cannot be combined with product level or bundle level discount overrides if you need different discounts on different products, use bundle level or line level discounts instead of a header percentage header discount amount ( ruby discountamount c ) is a total target mechanism — it distributes a fixed dollar amount across eligible lines and can coexist with product level overrides (explicit discounts count toward the target; the remainder is distributed to other lines) bundle discount propagates to all children that do not have their own explicit line level discount explicitly setting discount = 0 on a line means "no discount intended" — it is not overridden by bundle discounts this is different from omitting the discount field entirely, which allows the line to inherit from the bundle percentage takes priority over amount at any level if both discount (%) and discountamount ($) are set on the same line or header, the percentage is used and the amount is ignored (with a product discount applied or header discount applied warning) header discount amount distribution when ruby discountamount c is set on the quote header, the engine treats it as a total quote discount target and distributes it across eligible lines lines with explicit line level or bundle discounts are applied first — their discount amounts count toward the target the remaining budget is distributed proportionally by ruby listtotalprice c to lines without explicit discounts bundled items at $0 are skipped if explicit discounts already exceed the target, remaining lines receive a negative discount amount to bring the total back to the target a rounding correction is applied to the last eligible line to ensure the sum equals exactly the header target bundle discount amount conversion when discountamount (a flat dollar amount) is set on a bundle parent, the engine applies the amount to the parent line and converts it to an equivalent percentage that percentage is then propagated to children without their own discount, ensuring all items in the bundle are discounted proportionally system discounts are separate discounts from https //docs nue io/price and discount tags are applied as system discounts in a separate calculation step before discretionary discounts they appear in ruby systemdiscount c / ruby systemdiscountamount c and do not interact with the precedence rules above when both are present, system discounts reduce the list total to a subtotal, then the discretionary discount is applied to the subtotal https //docs nue io/understanding price calculations list price x quantity x subscription term = list total price \ system discount (from tags) = subtotal \ discretionary discount (header/bundle/line) = total price \+ tax = total amount system discounts are applied first, then discretionary discounts are applied to the resulting subtotal examples each example below maps to a real world scenario all examples use preview mode ( iscommit = false ) so nothing is persisted 1\ "10% off everything" scenario a sales rep wants to offer a blanket discount across the entire quote without configuring each line individually set ruby discount c on the quote header the percentage propagates to all lines with a price bundled items at $0 are unaffected opportunity opp = \[select id from opportunity where accountid != null limit 1]; ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request iscommit = false; request quote = new quote( opportunityid = opp id, name = 'blanket 10% off', ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month', ruby discount c = 10 // 10% applied to all eligible lines ); ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 1; request products = new list\<ruby globalapitypes productinput>{ bundle }; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // every priced line receives 10% — no per line configuration needed // bundled items (totalprice = $0) are unaffected for (ruby globalapitypes quotelineitemdto li response data lineitems) { quotelineitem qli = li quotelineitem; if (qli ruby totalprice c > 0) { system assertequals(10, qli ruby discount c); } } 2\ "20% off the gem edition bundle, 10% off everything else" scenario the primary bundle was negotiated at a deeper discount, while other products get a standard rate since different products need different discounts, set each product's discount directly at the bundle or line level opportunity opp = \[select id from opportunity where accountid != null limit 1]; ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request iscommit = false; request quote = new quote( opportunityid = opp id, name = 'bundle 20% + standalone 10%', ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month' ); // bundle negotiated at 20% — propagates to its children ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 1; bundle discount = 20; // 20% for the bundle and its children // standalone product at 10% ruby globalapitypes productinput standalone = new ruby globalapitypes productinput(); standalone productsku = 'nue platform'; standalone uom = 'user/month'; standalone quantity = 1; standalone discount = 10; // 10% for this product request products = new list\<ruby globalapitypes productinput>{ bundle, standalone }; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); map\<string, quotelineitem> skumap = new map\<string, quotelineitem>(); for (ruby globalapitypes quotelineitemdto li response data lineitems) { skumap put(li quotelineitem product2 stockkeepingunit, li quotelineitem); } // gem bundle and its children 20% system assertequals(20, skumap get('nue gem edition') ruby discount c); // standalone nue platform 10% for (ruby globalapitypes quotelineitemdto li response data lineitems) { quotelineitem qli = li quotelineitem; if (qli product2 stockkeepingunit == 'nue platform' && li parentid == null) { system assertequals(10, qli ruby discount c); } } 3\ "15% off the bundle, but the security key add on is at full price" scenario a 15% discount is negotiated for the gem edition bundle, but one add on is a fixed price item that should not be discounted the bundle discount propagates to all children, except the add on that explicitly sets discount = 0 this is the distinction between discount = 0 (explicitly no discount) and omitting the discount field (inherit from the bundle) opportunity opp = \[select id from opportunity where accountid != null limit 1]; ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request iscommit = false; request quote = new quote( opportunityid = opp id, name = 'bundle 15% + protected add on', ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month' ); ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 1; bundle discount = 15; // 15% propagates to all children // usb security key fixed price, not discountable ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'usb security key'; addon discount = 0; // explicit zero — bundle 15% does not apply bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); map\<string, quotelineitem> skumap = new map\<string, quotelineitem>(); for (ruby globalapitypes quotelineitemdto li response data lineitems) { skumap put(li quotelineitem product2 stockkeepingunit, li quotelineitem); } // gem 15% (own discount) system assertequals(15, skumap get('nue gem edition') ruby discount c); // nue platform 15% (inherited from parent — no explicit discount set) system assertequals(15, skumap get('nue platform') ruby discount c); // usb 0% (explicit — not overridden by parent's 15%) system assertequals(0, skumap get('usb security key') ruby discount c); quotelineitem usb = skumap get('usb security key'); system assertequals(usb ruby listtotalprice c, usb ruby totalprice c, 'full price — no discount'); 4\ "$50 total discount for the deal" scenario the deal desk approves a fixed dollar discount for the entire quote instead of figuring out how much to take off each line, the rep sets the total amount and the engine distributes it proportionally based on each line's list price set ruby discountamount c on the quote header the engine distributes the amount across all eligible non bundled lines proportionally by ruby listtotalprice c opportunity opp = \[select id from opportunity where accountid != null limit 1]; ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request iscommit = false; request quote = new quote( opportunityid = opp id, name = 'total dollar discount', ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month', ruby discountamount c = 50 // $50 total — distributed proportionally ); ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 1; ruby globalapitypes productinput standalone = new ruby globalapitypes productinput(); standalone productsku = 'nue platform'; standalone uom = 'user/month'; standalone quantity = 1; request products = new list\<ruby globalapitypes productinput>{ bundle, standalone }; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // engine distributes $50 proportionally — bundled items at $0 are skipped // sum of all line discount amounts equals exactly $50 00 decimal totaldiscapplied = 0; for (ruby globalapitypes quotelineitemdto li response data lineitems) { quotelineitem qli = li quotelineitem; decimal discamt = qli ruby discountamount c != null ? qli ruby discountamount c 0; totaldiscapplied += discamt; } system assert(math abs(totaldiscapplied 50) < 0 02, 'total discount equals header target $50'); 5\ "$20 total discount, but the bundle already has a negotiated 10%" scenario the deal desk approves a $20 total discount for the quote, but the gem edition bundle already has its own negotiated 10% the header discount amount is a total quote discount target — the engine first honors the bundle's 10%, then distributes whatever remains to lines without their own discount if the bundle's discount already exceeds the $20 target, the remaining lines receive a negative adjustment to bring the total back to $20 opportunity opp = \[select id from opportunity where accountid != null limit 1]; ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request iscommit = false; request quote = new quote( opportunityid = opp id, name = 'dollar discount + negotiated bundle', ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month', ruby discountamount c = 20 // $20 total target for the entire quote ); // bundle has its own negotiated 10% discount ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 1; bundle discount = 10; // explicit 10% — honored first, counts toward the $20 target // standalone product — no explicit discount, receives the remainder ruby globalapitypes productinput standalone = new ruby globalapitypes productinput(); standalone productsku = 'nue platform'; standalone uom = 'user/month'; standalone quantity = 1; request products = new list\<ruby globalapitypes productinput>{ bundle, standalone }; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // total discount across all lines equals the header target of $20 decimal totaldisc = 0; for (ruby globalapitypes quotelineitemdto li response data lineitems) { decimal discamt = li quotelineitem ruby discountamount c != null ? li quotelineitem ruby discountamount c 0; totaldisc += discamt; } system assert(math abs(totaldisc 20) < 0 02, 'total discount equals header target $20'); 6\ "$50 total discount, but professional services is not discountable" scenario the deal desk approves a $50 total discount, but a specific product should pay full price setting discount = 0 on the non discountable product means the $50 is distributed across all other lines, completely skipping it opportunity opp = \[select id from opportunity where accountid != null limit 1]; ruby globalapitypes createquoterequest request = new ruby globalapitypes createquoterequest(); request iscommit = false; request quote = new quote( opportunityid = opp id, name = 'dollar discount + non discountable', ruby subscriptionstartdate c = date today(), ruby subscriptionterm c = 12, ruby subscriptiontermdimension c = 'month', ruby discountamount c = 50 // $50 total target ); // bundle — no explicit discount, receives proportional share ruby globalapitypes productinput bundle = new ruby globalapitypes productinput(); bundle productsku = 'nue gem edition'; bundle uom = 'user/month'; bundle quantity = 1; // usb security key add on explicitly not discountable ruby globalapitypes productinput addon = new ruby globalapitypes productinput(); addon productsku = 'usb security key'; addon discount = 0; // explicit zero — no share of the $50 bundle addons = new list\<ruby globalapitypes productinput>{ addon }; request products = new list\<ruby globalapitypes productinput>{ bundle }; ruby globalapitypes createquoteresponse response = ruby globalquoteserviceapi createquote(request); system assertequals('succeed', response status); // usb receives no discount — full price for (ruby globalapitypes quotelineitemdto li response data lineitems) { quotelineitem qli = li quotelineitem; if (qli product2 stockkeepingunit == 'usb security key') { system assertequals(0, qli ruby discount c); system assertequals(qli ruby listtotalprice c, qli ruby totalprice c, 'usb full price'); } } // total discount across all lines still equals $50 decimal totaldisc = 0; for (ruby globalapitypes quotelineitemdto li response data lineitems) { decimal discamt = li quotelineitem ruby discountamount c != null ? li quotelineitem ruby discountamount c 0; totaldisc += discamt; } system assert(math abs(totaldisc 50) < 0 02, 'total discount equals header target $50'); warning messages the api returns structured warnings (not errors) when discount precedence rules are triggered true 220,220,222 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type warnings appear in response warnings\[] even when response status == 'succeed' always inspect warnings to understand how discounts were resolved discount fields on response line items true 331,331 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type summary true 331,331 left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type left unhandled content type
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.