quote → cash
EN/FR Contact

Product·Advanced·11 min·Updated 2026-10-05

A genuinely complex offer: tiered setup, adders, and twenty-one hardware variants

What it takes to model a managed-services offer where the setup fee steps at a headcount threshold, adders scale on a quantity that is not the seat count, and one bundle carries twenty-one appliance variants in three roles.

Salesforce docs documented by SalesforceField experience observed on a real orgRecommended pattern what we recommendTo verify unverified

The offer

A managed-services provider sells several offers that look simple in a brochure and are not simple in a catalog. One has a setup fee that depends on a headcount threshold, plus per-unit adders. Another is built on hardware: twenty-one appliance variants, each sellable in three roles, inside a single bundle.

Nothing here is exotic. It is simply more real than the examples in the documentation, and every mechanism that breaks does so for a reason worth knowing.

The quantity that drives the price is not the one you expect

The setup fee steps at a headcount threshold: one price below it, another above. That is a genuine condition, so it is a genuine decision table. Fine.

The trap is in what drives the other elements. The recurring management components — service delivery, review meetings, monitoring — do not scale with the number of workstations. They scale with the number of supported users, which is a different number carried on a different field.

The two are close enough that the wrong one looks right on every test quote where they happen to match, and wrong on every real one where they do not.

The practical defence is documentation at the point of use: name the driving quantity explicitly on the product, and write down which quantity drives which pricing element. This is precisely the detail that has to be re-derived, painfully, a year later by someone who was not there.

Tiered setup, and where to put the flat parts

The threshold goes in a decision table. The adders do not — they are flat prices, and a flat price belongs on the price book entry, where it consumes no procedure capacity.

This matters more than it sounds, because a pricing procedure has a ceiling and decision tables added by reflex are what fill it. A catalog of this size can exhaust a procedure on tables that answer no question at all.

Included allowances, and why only one of them worked

Several bundles include an allowance — so many hours of training, so many months of a service — with anything beyond charged at a real rate. The native way to express that is a price adjustment schedule with tiers: the included quantity at zero, the overage at the real price.

Five allowances were configured this way. Exactly one applied.

The tiers were correct. The cause was somewhere else entirely: the default pricing procedure applies adjustment schedules through hardcoded constants, not dynamically. One constant pointed at one schedule, so only that schedule was ever read. The other four were configured, valid, and never consulted.

The fix is a single global schedule holding every allowance, with the constant pointed at it. The per-offer schedules can stay in place, inert, or be deactivated so the next person is not misled by configuration that does nothing.

One further detail that costs an afternoon when an offer reaches a new region: tiers carry a currency. Tiers written in euros do not match a transaction in dollars, and the allowance silently fails to apply. There is no mismatch warning — the line simply prices as if no allowance existed.

Twenty-one appliances in three roles

The hardware offer is a bundle of twenty-one appliance models, each sellable as a primary unit, a secondary unit or part of a cluster. Modelled naively that is sixty-three products. Modelled as a bundle with three component groups it is twenty-one products and three groups.

Two mechanical traps appear immediately at this size:

  • A null sequence on a bundle component throws an index error at configuration time. With three components it is obvious. With sixty-three component relationships it is not, and the error names nothing useful. Check every one before shipping.
  • A duplicate price book entry where one is inactive blocks the active one, with the message “price book entry is inactive” — which points at the wrong record. Delete the duplicate rather than trying to reactivate it.

And one that is permanent rather than merely annoying: ProductSellingModelId on the price book entry is required and immutable after insert. Get it wrong and the only route back is delete and recreate. Set it in the insert.

Built by API, broken by API

The hardware bundle was created in bulk by API — twenty-four products, the bundle, three groups, prices and selling models. Everything looked right. At configuration time it failed with a null-success error from the configuration response, which says nothing about its cause.

What the elimination proved, in order:

  1. Not the pricing. Rolling back the procedure plan settings changed nothing, which ruled out the entire pricing path in one move.
  2. Not the obvious field. Correcting the configure-during-sale flag on the components had no effect.
  3. Not a null sequence — the known cause of index errors on bundle components — because there were none.

What remained: an equivalent bundle built by hand in the UI worked. The working hypothesis is that the UI populates fields the API call does not, and the diagnostic that follows is mechanical — build one product by hand, then diff it field by field against one created by API.

This is reported as an open investigation rather than a solved problem, because that is what it is.

The org's own required fields

A last category, easy to miss and specific to every org: local required fields. On this one, products carry two mandatory custom fields, the second a dependent picklist whose available values are controlled by the product family. A bulk load that sets the family and the dependent value independently produces combinations that are individually valid and jointly impossible.

One thing that is not needed, and is often built anyway: a formula field on the line to expose a product attribute to pricing. The context can traverse to the product directly, so the attribute is reachable without duplicating it onto the line.

What we would do differently

Build one product by hand in the UI, end to end, before loading anything — through configuration, pricing and a repriced quote. Then diff an API-created equivalent against it. That single comparison would have caught the configurator failure, the selling model immutability and the dependent picklist in one pass, before twenty-four products existed to fix.

Salesforce components

Select one to open its detail.

What this is based on

  • The appliance bundle failure is an open investigation, not a solved problem. It is published because the elimination is useful even though the cause is not yet proved.
  • All behaviours described were observed on real orgs while building these catalogs.

Salesforce resources