---
id: CAP-ACC-PRD-011
title: Offer a collection of products as a bundle
feature: FEAT-ACC-PRD-03
product_area: accounting
module: products
revision: 4
status: draft
product_intent: proposed
implementation_evidence: partially-observed
supersedes:
  - react-console acceptance inventory CAP-ACC-PRD-009
  - react-console acceptance inventory RUL-ACC-PRD-019
  - react-console acceptance inventory RUL-ACC-PRD-020
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D10.1'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D12'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R24'
purpose: An integrator supplies the products included in a bundle and their quantities.
outcomes:
  - Bundle allocations stay within validated product relationships.
concepts:
  - CON-ACC-PRD-001
actors:
  - ACT-ACC-006
  - ACT-ACC-010
surfaces:
  - api
permissions:
  - key: accounting.products.update
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-ACC-PRD-011-01
    proved_by: [ATS-ACC-PRD-011-01, TST-ACC-PRD-030, TST-ACC-PRD-040]
    statement: >-
      Bundle components are accepted only for kinds supporting bundles, including Accounting Products among
      Accounting’s kinds.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      components on a Donation Product
    origin: none
  - id: BR-CAP-ACC-PRD-011-02
    proved_by: [ATS-ACC-PRD-002-15, ATS-ACC-PRD-011-02, TST-ACC-PRD-040]
    statement: >-
      Each component must be a unique, existing sellable product in the bundle’s unit and must differ from the
      bundle itself.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a component of another business unit, naming both units
    origin: none
  - id: BR-CAP-ACC-PRD-011-03
    proved_by: [ATS-ACC-PRD-005-09, ATS-ACC-PRD-006-11, ATS-ACC-PRD-011-03, ATS-ACC-PRD-011-09, TST-ACC-PRD-040, TST-ACC-PRD-066]
    statement: 'Retired components already held by a bundle may remain, but newly added retired components are rejected.'
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#keeps
      a retired component the bundle already holds when a replacement resends it, and refuses a new retired
      one
    origin: none
  - id: BR-CAP-ACC-PRD-011-04
    proved_by: [ATS-ACC-PRD-011-04, TST-ACC-PRD-030, TST-ACC-PRD-040, TST-ACC-PRD-066]
    statement: Component relationships cannot create a cycle back to the containing bundle.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      closing a cycle: B including A when A includes B
    origin: none
  - id: BR-CAP-ACC-PRD-011-05
    proved_by: [ATS-ACC-PRD-011-05, TST-ACC-PRD-040, TST-ACC-PRD-066]
    statement: 'Each component requires an explicit price, including when its price is zero.'
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a component sent without a price, naming it, and stores nothing (ACCT-M1 D10.1, DEF-ACT-0107)
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D10.1'
  - id: BR-CAP-ACC-PRD-011-06
    proved_by: [ATS-ACC-PRD-008-11, ATS-ACC-PRD-011-06, ATS-ACC-PRD-011-09, ATS-ACC-PRD-016-09, TST-ACC-PRD-040, TST-ACC-PRD-066]
    statement: >-
      Quantity-weighted component totals fit set standard and member prices in the always base-currency
      period, preferring an undated row and otherwise its first dated row; absent member price counts as zero.
    kind: calculation
    enforced_at:
      - service
      - console
    must_prove: true
    provenance: observed
    source: >-
      services-accounting:modules/products/services/rules/product-rules.ts:listPrice;
      services-accounting:modules/products/services/rules/product-rules.ts:bundleTotalsProblem
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D12'
    examples:
      positive: Two components allocated 2000 each contribute 4000 to a bundle priced at 5000.
      negative: Three such components contribute 6000 and exceed the bundle’s price of 5000.
  - id: BR-CAP-ACC-PRD-011-07
    proved_by: []
    statement: >-
      Documented posting responsibility treats inline components as fixed revenue splits rather than separate
      bundled items.
    kind: policy
    enforced_at: []
    must_prove: false
    provenance: documented
    source: 'services-accounting:modules/products/db/product-bundle-components.table.ts'
    origin: none
  - id: BR-CAP-ACC-PRD-011-08
    proved_by: [ATS-ACC-PRD-011-07, TST-ACC-PRD-040]
    statement: Bundle replacement accepts at most fifty components and replaces the complete collection.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      more than 50 bundled products
    origin: none
  - id: BR-CAP-ACC-PRD-011-09
    proved_by: [ATS-ACC-PRD-011-08, TST-ACC-PRD-005]
    statement: The console hides bundle controls but preserves components configured through the API.
    kind: policy
    enforced_at:
      - console
    must_prove: true
    provenance: test
    source: 'react-console:acceptance/accounting/playwright/journeys/products/ACT-ACC-0110.spec.ts#ACT-ACC-0110'
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R24'
scenarios:
  - id: SC-CAP-ACC-PRD-011-01
    given: A course costs 5000 minor units and a workbook belongs to the same unit.
    when: Staff allocate two workbooks at 2000 each.
    then: 'Two fit within the course price, while three exceed it and are rejected.'
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#totals
      each component price times its quantity (ACCT-M1 D12 A2)
edge_cases:
  - id: SC-CAP-ACC-PRD-011-02
    given: Bundle A includes B.
    when: A caller makes B include A.
    then: The cycle rejects the change.
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      closing a cycle: B including A when A includes B
constraints: []
depends_on:
  - CAP-ACC-PRD-008
related:
  - CAP-ACC-PRD-005
  - CAP-ACC-PRD-006
  - CAP-ACC-PRD-016
journeys:
  - JNY-ACC-PRD-004
open_questions: []
evidence:
  - 'react-console:acceptance/accounting/playwright/journeys/products/ACT-ACC-0110.spec.ts'
  - 'react-console:components/accounting-config/products/product-bundle-section.tsx'
  - 'react-console:components/accounting-config/products/product-section-types.ts'
  - 'services-accounting:docs/accounting/products.md'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md'
  - 'services-accounting:modules/products/db/product-bundle-components.table.ts'
  - 'services-accounting:modules/products/services/persistence/products.ts'
  - 'services-accounting:modules/products/services/rules/product-rules.ts'
  - 'services-accounting:modules/products/tests/e2e/products.e2e.spec.js'
  - 'services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js'
  - 'services-accounting:modules/products/tests/unit/rules/product-rules.spec.js'
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-DAT-001, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-PI-001, TC-PI-003, TC-PI-005]
exercised_by: [FAS-ACC-PRD-03-01, FAS-ACC-PRD-03-02, FAS-ACC-PRD-03-03, FAS-ACC-PRD-03-04]
suites: [TST-ACC-PRD-005, TST-ACC-PRD-030, TST-ACC-PRD-040, TST-ACC-PRD-066]
---

# Offer a collection of products as a bundle

## Purpose

An integrator supplies the products included in a bundle and their quantities.

## How It Works

An integrator supplies the products included in a bundle and their quantities. Each included item needs an explicit allocation price. Validation checks the workspace and business unit of each component. It also checks sellability and prevents repeated components or cycles. Quantity-weighted allocations cannot exceed the bundle’s applicable price limits. A retired component already held by the bundle can remain. The Accounting console currently hides bundle editing and preserves loaded components. Purchase presentation and posting treatment require the owning downstream modules.

## Relationships

This outcome uses configure prices by audience, period and currency.
