Skip to main content

Bundles

A bundle is a SKU sold as a single unit but composed of multiple component SKUs. Quantaprice prices bundles deterministically and integrates them into the standard pricing pipeline — a bundle price is read exactly like any other price, with no special handling by the caller.

A bundle is defined at the article/catalog level (its component list), while its pricing rule is defined per pricelist and follows the normal inheritance hierarchy.


Defining a bundle

A bundle article carries a list of components, each a (sku, quantity):

{
"sku": "BUNDLE-START-KIT",
"components": [
{ "sku": "COMP-A", "quantity": 2 },
{ "sku": "COMP-B", "quantity": 1 }
]
}

Validation:

  • At least one component.
  • Each component quantity is positive (fractional quantities allowed).
  • A component cannot be the bundle itself.
  • A component cannot itself be a bundle (no nesting).
  • A component article must already exist.

Pricing methods

The bundle's price on a pricelist is determined by its bundle pricing rule (method):

sum_of_parts

The price is the sum of each component's effective price × its quantity, optionally adjusted by a discount:

base = Σ ( component.quantity × effectivePrice(component) )

discount_type = "percentage" → price = base × (1 − discount_value/100)
discount_type = "absolute" → price = base − discount_value (clamped to ≥ 0)
none → price = base

effectivePrice(component) resolves each component using normal pricelist inheritance (the component's own price on this pricelist, else inherited from a parent). So a customer with negotiated component prices automatically gets a bundle priced from their component prices.

fixed

The bundle has an explicit fixed_price; component prices do not affect it. The response still includes a per-component VAT breakdown (derived proportionally from the components) so tax is computed correctly across mixed VAT classes.


Inheritance

The rule is inherited, not the resolved price. Resolving a bundle rule for a pricelist:

  1. If the pricelist has its own bundle rule, use it.
  2. Otherwise inherit the rule from the nearest parent pricelist that has one.
  3. For sum_of_parts, component prices are then resolved at the requesting pricelist level (which itself follows normal inheritance).

A child pricelist may override the rule (switch method, change the discount, set a fixed price). There are no per-component overrides at the pricelist level — components are defined on the article.


Scheduling

Bundle pricing is time-aware. Both rules and component prices can be future-dated, and the bundle reflects them automatically at the scheduled moment — no manual activation.

  • Scheduled fixed price — set a fixed rule with a future effective_from; the bundle switches to that price at that time.
  • Scheduled discount / method change — schedule a future sum_of_parts discount change (e.g. 10% today, 25% from Black Friday) or a method switch.
  • Scheduled component prices — a future-dated price change on any component (its own or inherited) is reflected in the bundle from the moment it takes effect.

Scheduled bundle prices are precomputed within a bounded horizon (default 90 days) and a maximum number of future versions per bundle/pricelist; both are configurable per plan. Beyond those limits the schedule still applies — it is materialized progressively as time advances.

Historical pricing

Because each change produces a new effective-dated version, bundle prices support "as of" evaluation for past dates just like regular prices.


Reading a bundle price

A bundle price is requested and returned exactly like a regular price. The response additionally includes a per-component breakdown so the caller can see how the price was composed:

{
"sku": "BUNDLE-START-KIT",
"price": { "ex_vat": "180.00", "vat": "45.00", "inc_vat": "225.00" },
"bundle": [
{ "sku": "COMP-A", "quantity": 2, "price": "120.00", "vat_rate": "25.0", "vat_amount": "30.00", "pricelist": "RETAIL" },
{ "sku": "COMP-B", "quantity": 1, "price": "60.00", "vat_rate": "25.0", "vat_amount": "15.00", "pricelist": "RETAIL" }
]
}

bundle is null for regular (non-bundle) prices. Each component entry reports the pricelist its price was resolved from and its own VAT contribution (VAT is computed per component, so bundles with mixed VAT classes are handled correctly).

If a sum_of_parts component cannot be resolved anywhere in the hierarchy, the bundle is treated as unavailable (no price) rather than returning a partial sum.


Bundle Rules API

Bundle rules are managed under /bundle-rules.

Set or update a rule

PUT /bundle-rules/{bundleSku}/{pricelistCode}
{
"method": "sum_of_parts",
"discount_type": "percentage",
"discount_value": 10,
"effective_from": "2026-11-27T00:00:00Z"
}
{
"method": "fixed",
"fixed_price": "499.00"
}

effective_from is optional and defaults to now; a future value schedules the rule. Setting a rule triggers recomputation of the bundle price on this pricelist and its descendants.

Validation: discount_value is 0–100 for percentage and ≥ 0 for absolute; fixed_price is required and ≥ 0 for fixed.

Get the effective rule

GET /bundle-rules/{bundleSku}/{pricelistCode}

Returns the rule effective now, resolving inheritance up the parent chain. The response includes inherited: true|false, the method, discount/fixed fields, and effective_from.

List rule versions

GET /bundle-rules/{bundleSku}

Lists every pricelist/version that has an explicit rule for the bundle.

Remove a rule

DELETE /bundle-rules/{bundleSku}/{pricelistCode}?effective_from=...

Removes a rule version (defaults to the version effective now). The bundle then inherits from the parent, or becomes unavailable if no rule remains anywhere in the hierarchy.


Design principles

  • Deterministic — same inputs always produce the same result, identically across nodes.
  • O(1) reads — bundle prices are materialized on write; reads are a single keyed lookup.
  • Composable — bundles integrate with inheritance, scheduling, history, VAT, and rounding.

See also

  • Pricelist Inheritance — how bundle rules are inherited through the pricelist hierarchy.
  • VAT — how per-component VAT is resolved for bundles with mixed VAT classes.
  • Querying Prices — reading bundle prices via the query API.
  • OpenAPI Reference — full spec for /bundle-rules endpoints.

Summary

Bundles are SKUs composed of component SKUs, priced per pricelist by an inherited rule (sum_of_parts with an optional discount, or a fixed price). Rules and component prices are time-aware, so fixed prices, discounts, and component price changes can all be scheduled and are reflected automatically. Prices are materialized for O(1) reads and returned with a per-component breakdown.