---
id: ATS-COM-CAT-013
scope: CAP-COM-CAT-013
product_area: commerce
module: catalog
revision: 2
status: proposed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-COM-CAT-013-01
    title: Engine calculation preserves the configured adjustment order
    level: unit
    proves:
      - BR-CAP-COM-CAT-013-01
    preconditions: 'An isolated member fixture supplies variant, bulk and store adjustments'
    steps: Calculate the cart price
    expected: The adjustment sequence and provenance match the rule
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-082
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies through
      the member cell, the variant, the bulk rule and the store’s line discount, in that order
    category: rule
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
        through the member cell, the variant, the bulk rule and the store’s line discount, in that
        order
      - >-
        services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#answers totals that are
        the sum of the line totals and the order adjustments
    step_evidence:
      - do: Calculate the cart price
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
          through the member cell, the variant, the bulk rule and the store’s line discount, in that
          order
    assertions:
      - expect: The adjustment sequence and provenance match the rule
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
          through the member cell, the variant, the bulk rule and the store’s line discount, in that
          order
  - id: ATS-COM-CAT-013-02
    title: Nonfinal eligibility never establishes an audience
    level: unit
    proves:
      - BR-CAP-COM-CAT-013-02
    preconditions: 'Eligibility fixtures return negative, uncertain, provisional and review verdicts'
    steps: Resolve audience qualification
    expected: None qualifies and the corresponding reasons remain available
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-085
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#does
      not qualify for an ineligible decision, an indeterminate one, a provisional one or one under
      review, and says why
    category: rule
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#does
        not qualify for an ineligible decision, an indeterminate one, a provisional one or one under
        review, and says why
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#qualifies
        for every eligibility audience answered eligible, in rank order, each with its decision and
        binding
    step_evidence:
      - do: Resolve audience qualification
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#does
          not qualify for an ineligible decision, an indeterminate one, a provisional one or one
          under review, and says why
    assertions:
      - expect: None qualifies and the corresponding reasons remain available
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#does
          not qualify for an ineligible decision, an indeterminate one, a provisional one or one
          under review, and says why
  - id: ATS-COM-CAT-013-03
    title: Audience pricing uses supported fallbacks
    level: unit
    proves:
      - BR-CAP-COM-CAT-013-03
    preconditions: Period pricing is absent for Member
    steps: Calculate with and without an Always Member price
    expected: The result uses Always Member when present and Standard otherwise
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-086
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits the
      audience’s always cell when the period has none, never another period’s cell
    category: edge
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits the
        audience’s always cell when the period has none, never another period’s cell
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#falls back to
        the standard cell for a member when the member audience has no cell in the currency (the
        audience fallback)
    step_evidence:
      - do: Calculate with and without an Always Member price
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits the
          audience’s always cell when the period has none, never another period’s cell
    assertions:
      - expect: The result uses Always Member when present and Standard otherwise
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits the
          audience’s always cell when the period has none, never another period’s cell
  - id: ATS-COM-CAT-013-04
    title: Half-unit rounding moves away from zero
    level: unit
    proves:
      - BR-CAP-COM-CAT-013-04
      - INV-DOM-COM-CAT-004-08
    preconditions: The arithmetic helper accepts signed values
    steps: Round positive and negative exact halves
    expected: Results move away from zero without even-number rounding
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-084
    provenance: test
    source: 'services-commerce:modules/pricing/tests/unit/engine/rounding.spec.js#rounds a positive half up'
    category: boundary
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/rounding.spec.js#rounds a positive half
        up
      - >-
        services-commerce:modules/pricing/tests/unit/engine/rounding.spec.js#rounds a negative half
        down, away from zero, where Math.round would answer -2
      - >-
        services-commerce:modules/pricing/tests/unit/engine/rounding.spec.js#does not round to the
        even neighbour (no banker’s rounding)
    step_evidence:
      - do: Round positive and negative exact halves
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/rounding.spec.js#rounds a positive
          half up
    assertions:
      - expect: Results move away from zero without even-number rounding
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/rounding.spec.js#rounds a positive
          half up
  - id: ATS-COM-CAT-013-05
    title: Currency calculation requires an available conversion
    level: unit
    proves:
      - BR-CAP-COM-CAT-013-05
      - BR-CAP-COM-CAT-013-06
      - INV-DOM-COM-CAT-004-11
    preconditions: The product has base USD pricing
    steps: 'Calculate EUR with a rate, without a rate and under Standard prohibition'
    expected: Conversion retains its rate provenance; missing rate and prohibition refuse pricing
    negative: true
    data_needs: Exedit rates
    bound_to:
      - TST-COM-CAT-086
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#converts a
      calculate cell from the base cell at the rate of the day, rounded half away from zero, and
      freezes the conversion
    category: failure
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#converts a
        calculate cell from the base cell at the rate of the day, rounded half away from zero, and
        freezes the conversion
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#refuses a
        currency whose default is prohibit when no cell exists in it, and a calculate with no rate
        for the day
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#refuses a
        currency the product is prohibited in for everyone (a prohibit on standard)
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#refuses a
        product outside its sale window, before and after
    step_evidence:
      - do: 'Calculate EUR with a rate, without a rate and under Standard prohibition'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#converts a
          calculate cell from the base cell at the rate of the day, rounded half away from zero, and
          freezes the conversion
    assertions:
      - expect: Conversion retains its rate provenance; missing rate and prohibition refuse pricing
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#converts a
          calculate cell from the base cell at the rate of the day, rounded half away from zero, and
          freezes the conversion
  - id: ATS-COM-CAT-013-06
    title: Public-sale guards distinguish staff order authority
    level: api
    proves:
      - BR-CAP-COM-CAT-013-07
      - AS-JNY-COM-CAT-003-03
    preconditions: 'The store contains published, unlisted, draft and excluded products'
    steps: 'Request quotes as visitors, members and staff with or without order-create permission'
    expected: >-
      Public guards refuse draft and excluded products; authorized staff receive their prices while
      staff readers do not
    negative: true
    data_needs: Catalog fixtures
    bound_to:
      - TST-COM-CAT-057
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#refuses a
      draft product to an anonymous caller: 400 product_not_published, naming the store
    category: permission
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#refuses a
        draft product to an anonymous caller: 400 product_not_published, naming the store
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#refuses a
        published product outside the store’s category assortment: 400
        product_not_in_store_assortment, naming the store
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#refuses a
        portal member the draft too, beside their own quote
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#prices the
        draft and the out-of-assortment product for staff holding commerce.orders.create: Standard
        6000 and 15000
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#does not
        extend the staff exemption to a console reader without commerce.orders.create
    step_evidence:
      - do: 'Request quotes as visitors, members and staff with or without order-create permission'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#refuses a
          draft product to an anonymous caller: 400 product_not_published, naming the store
    assertions:
      - expect: >-
          Public guards refuse draft and excluded products; authorized staff receive their prices
          while staff readers do not
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quote-public-sale.e2e.spec.js#refuses a
          draft product to an anonymous caller: 400 product_not_published, naming the store
  - id: ATS-COM-CAT-013-07
    title: Anonymous quote inputs cannot name private context
    level: api
    proves:
      - BR-CAP-COM-CAT-013-08
    preconditions: An active store supports public quotes
    steps: 'Name a subject, omit the store and select a closed store'
    expected: All invalid public requests fail
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-060
      - TST-COM-CAT-056
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a body that names
      a subject, an acting contact, a recipient or a cart: nothing about a person is read
      anonymously
    category: validation
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a body that
        names a subject, an acting contact, a recipient or a cart: nothing about a person is read
        anonymously
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#needs the store: a
        public quote outside one is refused, an unknown store is 404 and a closed one 409
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-public-display.e2e.spec.js#shows the
        Standard price alone, and still the notice, when the Member audience is not marked public
    step_evidence:
      - do: 'Name a subject, omit the store and select a closed store'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a body that
          names a subject, an acting contact, a recipient or a cart: nothing about a person is read
          anonymously
    assertions:
      - expect: All invalid public requests fail
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a body that
          names a subject, an acting contact, a recipient or a cart: nothing about a person is read
          anonymously
  - id: ATS-COM-CAT-013-08
    title: Extension failure handling depends on requirement and mode
    level: api
    proves:
      - BR-CAP-COM-CAT-013-09
    preconditions: Required and optional handlers fail
    steps: Attempt placement and quoting through both
    expected: >-
      Required placement records an exception and refuses; optional placement and quotes continue
      with warnings
    negative: true
    data_needs: Fixture handlers
    bound_to:
      - TST-COM-CAT-068
      - TST-COM-CAT-087
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/integration/services/extensions/priced-with-extensions.integration.spec.js#refuses
      with EXTENSION_UNAVAILABLE naming the extension, and records the exception on the order with
      the SDK’s status (A24)
    category: failure
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/integration/services/extensions/priced-with-extensions.integration.spec.js#refuses
        with EXTENSION_UNAVAILABLE naming the extension, and records the exception on the order with
        the SDK’s status (A24)
      - >-
        services-commerce:modules/pricing/tests/integration/services/extensions/priced-with-extensions.integration.spec.js#skips
        an optional extension with a warning, the exception recorded, the placement proceeding
      - >-
        services-commerce:modules/pricing/tests/unit/extensions/commerce-extensions.spec.js#skips an
        extension that fails in a quote with a warning and no adjustment
    step_evidence:
      - do: Attempt placement and quoting through both
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/extensions/priced-with-extensions.integration.spec.js#refuses
          with EXTENSION_UNAVAILABLE naming the extension, and records the exception on the order
          with the SDK’s status (A24)
    assertions:
      - expect: >-
          Required placement records an exception and refuses; optional placement and quotes
          continue with warnings
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/extensions/priced-with-extensions.integration.spec.js#refuses
          with EXTENSION_UNAVAILABLE naming the extension, and records the exception on the order
          with the SDK’s status (A24)
  - id: ATS-COM-CAT-013-09
    title: Unsupported store fees prevent preview and placement
    level: api
    proves:
      - BR-CAP-COM-CAT-013-10
    preconditions: The store pricing handler proposes an order fee
    steps: Preview and place that sale
    expected: Both return the unsupported-fee refusal without persisting it
    negative: true
    data_needs: A fee-adding handler
    bound_to: []
    provenance: proposed
    source: 'services-commerce:docs/milestones/COMM-M1-set-up-what-you-sell.md#D5-F1'
    category: failure
    step_evidence:
      - do: Preview and place that sale
        provenance: proposed
        source: 'services-commerce:docs/milestones/COMM-M1-set-up-what-you-sell.md#D5-F1'
    assertions:
      - expect: Both return the unsupported-fee refusal without persisting it
        provenance: proposed
        source: 'services-commerce:docs/milestones/COMM-M1-set-up-what-you-sell.md#D5-F1'
  - id: ATS-COM-CAT-013-10
    title: Hypothetical pricing remains staff-controlled
    level: api
    proves:
      - BR-CAP-COM-CAT-013-11
      - AS-JNY-COM-CAT-002-05
    preconditions: A nonmember and dated student qualification are available
    steps: >-
      Preview assumed Member and a future qualification date as staff, then request those overrides
      as a member
    expected: Staff results identify assumptions and dated qualification; portal override requests fail
    negative: true
    data_needs: Fixture eligibility
    bound_to:
      - TST-COM-CAT-058
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/price-quote-staff-form.e2e.spec.js#answers 200
      priced at the member cell, the audience marked assumed in the provenance, the response saying
      what it assumed
    category: permission
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-staff-form.e2e.spec.js#answers 200
        priced at the member cell, the audience marked assumed in the provenance, the response
        saying what it assumed
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-staff-form.e2e.spec.js#reads the
        student’s standing as of August 2027: the student term ended and the committed Alumni term
        holds, so Member prices, not Student
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quote-staff-form.e2e.spec.js#refuses a
        portal member either property, 403, and prices them without
    step_evidence:
      - do: >-
          Preview assumed Member and a future qualification date as staff, then request those
          overrides as a member
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quote-staff-form.e2e.spec.js#answers 200
          priced at the member cell, the audience marked assumed in the provenance, the response
          saying what it assumed
    assertions:
      - expect: Staff results identify assumptions and dated qualification; portal override requests fail
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quote-staff-form.e2e.spec.js#answers 200
          priced at the member cell, the audience marked assumed in the provenance, the response
          saying what it assumed
  - id: ATS-COM-CAT-013-11
    title: Portal quotes respect represented parties
    level: api
    proves:
      - BR-CAP-COM-CAT-013-12
    preconditions: A member may act for an affiliated organization
    steps: 'Quote for self, that organization and another contact'
    expected: Permitted subjects are priced and the unrelated contact request fails
    negative: true
    data_needs: CRM affiliations
    bound_to:
      - TST-COM-CAT-060
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#quotes for themselves,
      the price for their own contact
    category: permission
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#quotes for themselves,
        the price for their own contact
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#quotes for the
        organization they act for, themselves the acting contact whatever the body says
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#is refused a quote for
        another contact (403), and gets a public quote when naming nobody
    step_evidence:
      - do: 'Quote for self, that organization and another contact'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#quotes for
          themselves, the price for their own contact
    assertions:
      - expect: Permitted subjects are priced and the unrelated contact request fails
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#quotes for
          themselves, the price for their own contact
  - id: ATS-COM-CAT-013-12
    title: Price preview does not bind the later calculation
    level: unit
    proves:
      - BR-CAP-COM-CAT-013-13
      - INV-DOM-COM-CAT-004-09
    preconditions: A price changes after preview
    steps: Compare the placement calculation with the earlier result
    expected: The comparison reports the change instead of retaining the old price
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-083
      - TST-COM-CAT-060
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/engine/quote-tolerance.spec.js#differs with the
      changes when a price moved, never answering the previewed price
    category: invariant
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/quote-tolerance.spec.js#differs with the
        changes when a price moved, never answering the previewed price
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#wrote nothing: no
        checkout, no event
    step_evidence:
      - do: Compare the placement calculation with the earlier result
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/quote-tolerance.spec.js#differs with
          the changes when a price moved, never answering the previewed price
    assertions:
      - expect: The comparison reports the change instead of retaining the old price
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/engine/quote-tolerance.spec.js#differs with
          the changes when a price moved, never answering the previewed price
  - id: ATS-COM-CAT-013-13
    title: Placement rechecks qualification before period stamping
    level: api
    proves:
      - BR-CAP-COM-CAT-013-14
      - INV-DOM-COM-CAT-004-10
    preconditions: Membership lapses after a line is priced
    steps: Attempt placement
    expected: Each affected line is refused and no first-sale marker is written
    negative: true
    data_needs: Fixture eligibility
    bound_to:
      - TST-COM-CAT-081
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
      the placement for a lapsed member, one audience_lapsed problem per line with the portal
      sentence, stamping nothing
    category: transition
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
        the placement for a lapsed member, one audience_lapsed problem per line with the portal
        sentence, stamping nothing
      - >-
        services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#passes
        a member still eligible, stamps the period’s first sale once, and leaves a period already
        sold in as it was
    step_evidence:
      - do: Attempt placement
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
          the placement for a lapsed member, one audience_lapsed problem per line with the portal
          sentence, stamping nothing
    assertions:
      - expect: Each affected line is refused and no first-sale marker is written
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
          the placement for a lapsed member, one audience_lapsed problem per line with the portal
          sentence, stamping nothing
  - id: ATS-COM-CAT-013-14
    title: Quote validation limits input and access
    level: api
    proves:
      - BR-CAP-COM-CAT-013-15
    preconditions: 'Quote requests can vary size, quantity and permissions'
    steps: 'Submit fifty-one items, zero quantity and a caller lacking quote access'
    expected: Input and permission refusals apply to the respective requests
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-060
      - TST-COM-CAT-059
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses more than fifty
      items
    category: validation
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses more than fifty
        items
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a property
        outside the contract, a quantity of zero and a fractional entered amount
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes-security.e2e.spec.js#refuses a
        principal without the quote key, 403 permission_missing
    step_evidence:
      - do: 'Submit fifty-one items, zero quantity and a caller lacking quote access'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses more than
          fifty items
    assertions:
      - expect: Input and permission refusals apply to the respective requests
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses more than
          fifty items
  - id: ATS-COM-CAT-013-15
    title: The tax address changes the quoted line tax
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-004-05
      - BR-CAP-COM-CAT-004-06
      - BR-CAP-COM-CAT-004-07
      - BR-CAP-COM-CAT-013-01
    preconditions: 'A product is taxable and country, region and postal zones have different rates.'
    steps: Quote with each address and without an address in display and checkout modes.
    expected: >-
      The selected zone changes the line components; address-free display warns and taxable checkout
      refuses.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#scores a
        prefix over a region over a country alone
      - >-
        services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#sums every
        applicable rate per line, each rounded on its own, and names each component
      - >-
        services-commerce:modules/pricing/tests/unit/tax/zone-table-tax.spec.js#refuses a line a
        zone’s rate for every product type could tax, naming the line and its product
      - >-
        services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
        through the member cell, the variant, the bulk rule and the store’s line discount, in that
        order
    exercises:
      - CAP-COM-CAT-004
      - CAP-COM-CAT-013
    workflows:
      - WF-COM-CAT-009
    step_evidence:
      - do: Quote with each address and without an address in display and checkout modes.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#scores a
            prefix over a region over a country alone
          - >-
            services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#sums every
            applicable rate per line, each rounded on its own, and names each component
          - >-
            services-commerce:modules/pricing/tests/unit/tax/zone-table-tax.spec.js#refuses a line a
            zone’s rate for every product type could tax, naming the line and its product
          - >-
            services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
            through the member cell, the variant, the bulk rule and the store’s line discount, in
            that order
    assertions:
      - expect: >-
          The selected zone changes the line components; address-free display warns and taxable
          checkout refuses.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#scores a
            prefix over a region over a country alone
          - >-
            services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#sums every
            applicable rate per line, each rounded on its own, and names each component
          - >-
            services-commerce:modules/pricing/tests/unit/tax/zone-table-tax.spec.js#refuses a line a
            zone’s rate for every product type could tax, naming the line and its product
          - >-
            services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
            through the member cell, the variant, the bulk rule and the store’s line discount, in
            that order
  - id: ATS-COM-CAT-013-16
    title: Removing an audience binding removes qualification
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-010-07
      - BR-CAP-COM-CAT-013-02
    preconditions: An eligibility audience has a selected binding and a price.
    steps: 'Quote an eligible buyer, remove the binding, then quote again.'
    expected: The second quote omits that audience and explains the missing binding.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#warns
        of an audience with no binding, and holds nobody for it; a binding the batch did not answer
        is simply not held
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#does
        not qualify for an ineligible decision, an indeterminate one, a provisional one or one under
        review, and says why
    exercises:
      - CAP-COM-CAT-010
      - CAP-COM-CAT-013
    workflows:
      - WF-COM-CAT-009
    step_evidence:
      - do: 'Quote an eligible buyer, remove the binding, then quote again.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#warns
            of an audience with no binding, and holds nobody for it; a binding the batch did not
            answer is simply not held
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#does
            not qualify for an ineligible decision, an indeterminate one, a provisional one or one
            under review, and says why
    assertions:
      - expect: The second quote omits that audience and explains the missing binding.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#warns
            of an audience with no binding, and holds nobody for it; a binding the batch did not
            answer is simply not held
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-2-audience-resolution.spec.js#does
            not qualify for an ineligible decision, an indeterminate one, a provisional one or one
            under review, and says why
  - id: ATS-COM-CAT-013-17
    title: A moved boundary changes the next quote without rewriting an earlier sale
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-011-02
      - BR-CAP-COM-CAT-011-05
      - BR-CAP-COM-CAT-013-03
      - BR-CAP-COM-CAT-013-14
    preconditions: Early Bird has a placed sale and Regular starts at its end.
    steps: >-
      Read the sale, extend Early Bird with Regular’s start moved together, quote on both sides of
      the new boundary and reread the sale.
    expected: >-
      New quotes select the period at their instant; the earlier amount and first-sale stamp stay
      unchanged.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
        the period containing an instant: the open start, a boundary belonging to the period it
        starts, the open end
      - >-
        services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
        sold-in Early Bird by a week, Regular starting where it now ends, its first sale untouched
      - >-
        services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits the
        audience’s always cell when the period has none, never another period’s cell
      - >-
        services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
        the placement for a lapsed member, one audience_lapsed problem per line with the portal
        sentence, stamping nothing
    exercises:
      - CAP-COM-CAT-011
      - CAP-COM-CAT-013
    workflows:
      - WF-COM-CAT-009
      - WF-COM-CAT-010
    step_evidence:
      - do: >-
          Read the sale, extend Early Bird with Regular’s start moved together, quote on both sides
          of the new boundary and reread the sale.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
            the period containing an instant: the open start, a boundary belonging to the period it
            starts, the open end
          - >-
            services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
            sold-in Early Bird by a week, Regular starting where it now ends, its first sale
            untouched
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits
            the audience’s always cell when the period has none, never another period’s cell
          - >-
            services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
            the placement for a lapsed member, one audience_lapsed problem per line with the portal
            sentence, stamping nothing
    assertions:
      - expect: >-
          New quotes select the period at their instant; the earlier amount and first-sale stamp
          stay unchanged.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.queries.integration.spec.js#finds
            the period containing an instant: the open start, a boundary belonging to the period it
            starts, the open end
          - >-
            services-commerce:modules/pricing/tests/e2e/pricing-schedules.e2e.spec.js#extends the
            sold-in Early Bird by a week, Regular starting where it now ends, its first sale
            untouched
          - >-
            services-commerce:modules/pricing/tests/unit/engine/step-3-base-price.spec.js#inherits
            the audience’s always cell when the period has none, never another period’s cell
          - >-
            services-commerce:modules/pricing/tests/integration/services/placement/audience-settlement.integration.spec.js#refuses
            the placement for a lapsed member, one audience_lapsed problem per line with the portal
            sentence, stamping nothing
  - id: ATS-COM-CAT-013-18
    title: Purchasing strategy and handler activity alter the quote
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-012-03
      - BR-CAP-COM-CAT-012-06
      - BR-CAP-COM-CAT-013-01
    preconditions: Member and Student qualify and a product handler adds a fixed amount.
    steps: 'Quote under lowest and rank, then disable the handler and repeat.'
    expected: >-
      The selected audience follows each strategy and the disabled handler contributes no
      adjustment.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/e2e/product-purchasing-settings.e2e.spec.js#prices
        the same buyer at the member price under rank, Member coming before Student members
      - >-
        services-commerce:modules/pricing/tests/e2e/product-extensions.e2e.spec.js#keeps the row and
        ignores it when it is put inactive: back to $20.00
      - >-
        services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
        through the member cell, the variant, the bulk rule and the store’s line discount, in that
        order
    exercises:
      - CAP-COM-CAT-012
      - CAP-COM-CAT-013
    workflows:
      - WF-COM-CAT-009
    step_evidence:
      - do: 'Quote under lowest and rank, then disable the handler and repeat.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/e2e/product-purchasing-settings.e2e.spec.js#prices
            the same buyer at the member price under rank, Member coming before Student members
          - >-
            services-commerce:modules/pricing/tests/e2e/product-extensions.e2e.spec.js#keeps the row
            and ignores it when it is put inactive: back to $20.00
          - >-
            services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
            through the member cell, the variant, the bulk rule and the store’s line discount, in
            that order
    assertions:
      - expect: >-
          The selected audience follows each strategy and the disabled handler contributes no
          adjustment.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/e2e/product-purchasing-settings.e2e.spec.js#prices
            the same buyer at the member price under rank, Member coming before Student members
          - >-
            services-commerce:modules/pricing/tests/e2e/product-extensions.e2e.spec.js#keeps the row
            and ignores it when it is put inactive: back to $20.00
          - >-
            services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
            through the member cell, the variant, the bulk rule and the store’s line discount, in
            that order
  - id: ATS-COM-CAT-013-19
    title: Browsing and quoting use the same buyer context
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-013-08
      - BR-CAP-COM-CAT-008-03
      - BR-CAP-COM-CAT-008-04
    preconditions: Standard and a publicly displayed Member price exist.
    steps: 'Browse and quote anonymously, then repeat as the member without staff assumptions.'
    expected: Public results show public alternatives; signed results use the member’s identity.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a body that
        names a subject, an acting contact, a recipient or a cart: nothing about a person is read
        anonymously
      - >-
        services-commerce:modules/catalog/tests/e2e/storefront.e2e.spec.js#shows the audiences
        marked for public display beside the Standard price (D10): the Members price 2000, with its
        audience
      - >-
        services-commerce:modules/catalog/tests/e2e/storefront.e2e.spec.js#prices a portal member as
        themselves (the recorded member: the member price, nothing signing in could change)
    exercises:
      - CAP-COM-CAT-013
      - CAP-COM-CAT-008
    workflows:
      - WF-COM-CAT-007
    step_evidence:
      - do: 'Browse and quote anonymously, then repeat as the member without staff assumptions.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a body that
            names a subject, an acting contact, a recipient or a cart: nothing about a person is
            read anonymously
          - >-
            services-commerce:modules/catalog/tests/e2e/storefront.e2e.spec.js#shows the audiences
            marked for public display beside the Standard price (D10): the Members price 2000, with
            its audience
          - >-
            services-commerce:modules/catalog/tests/e2e/storefront.e2e.spec.js#prices a portal
            member as themselves (the recorded member: the member price, nothing signing in could
            change)
    assertions:
      - expect: Public results show public alternatives; signed results use the member’s identity.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/e2e/price-quotes.e2e.spec.js#refuses a body that
            names a subject, an acting contact, a recipient or a cart: nothing about a person is
            read anonymously
          - >-
            services-commerce:modules/catalog/tests/e2e/storefront.e2e.spec.js#shows the audiences
            marked for public display beside the Standard price (D10): the Members price 2000, with
            its audience
          - >-
            services-commerce:modules/catalog/tests/e2e/storefront.e2e.spec.js#prices a portal
            member as themselves (the recorded member: the member price, nothing signing in could
            change)
  - id: ATS-COM-CAT-013-20
    title: Changing audience price changes the winning promotional alternative
    category: interaction
    level: api
    proves:
      - BR-CAP-COM-CAT-013-01
      - BR-CAP-COM-CAT-016-02
    preconditions: A noncombining quarter-off code competes with an audience amount.
    steps: 'Compare at eighty dollars, then at sixty dollars against a hundred-dollar Standard amount.'
    expected: >-
      The seventy-five-dollar code wins first; the sixty-dollar audience wins second and consumes no
      code use.
    negative: false
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
        through the member cell, the variant, the bulk rule and the store’s line discount, in that
        order
      - >-
        services-commerce:modules/promotions/tests/unit/pricing/alternatives.spec.js#2. combine =
        no, member: audience pricing $80.00 against $100.00 less 25 % = $75.00; the promotional
        price wins
    exercises:
      - CAP-COM-CAT-013
      - CAP-COM-CAT-016
    workflows: []
    step_evidence:
      - do: 'Compare at eighty dollars, then at sixty dollars against a hundred-dollar Standard amount.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
            through the member cell, the variant, the bulk rule and the store’s line discount, in
            that order
          - >-
            services-commerce:modules/promotions/tests/unit/pricing/alternatives.spec.js#2. combine
            = no, member: audience pricing $80.00 against $100.00 less 25 % = $75.00; the
            promotional price wins
    assertions:
      - expect: >-
          The seventy-five-dollar code wins first; the sixty-dollar audience wins second and
          consumes no code use.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/pricing/tests/unit/engine/engine.spec.js#prices the hoodies
            through the member cell, the variant, the bulk rule and the store’s line discount, in
            that order
          - >-
            services-commerce:modules/promotions/tests/unit/pricing/alternatives.spec.js#2. combine
            = no, member: audience pricing $80.00 against $100.00 less 25 % = $75.00; the
            promotional price wins
evidence:
  - 'Pinned service inputs: test sources in scenario bindings'
  - 'Pinned console inputs: console sources in scenario bindings'
  - >-
    rhythm-v2-docs:specs/commerce/catalog/planning/capabilities/CAP-COM-CAT-013-work-out-what-a-buyer-pays-for-an-item.md#CAP-COM-CAT-013
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-007-build-a-storefront-page-or-product-page.md#WF-COM-CAT-007
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-009-quote-a-price.md#WF-COM-CAT-009
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-010-price-a-checkout-and-settle-its-audiences-at.md#WF-COM-CAT-010
product_intent: proposed
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: covered
  boundary: covered
  concurrency: 'gap: FND-COM-CAT-067'
  invariant: covered
  interaction: covered
coverage_notes:
  concurrency: Concurrent execution has no complete scenario; sequential retry is insufficient proof.
permission_coverage:
  - key: commerce.price-quotes.read
    coverage: 'gap: FND-COM-CAT-067'
    reason: >-
      Existing permission examples cover selected personas, not the independent allow and deny
      matrix for every named key.
    provenance: unknown
    source:
      - 'rhythm-v2-docs:specs/commerce/catalog/FINDINGS.md#FND-COM-CAT-067'
  - key: commerce.orders.create
    coverage: 'gap: FND-COM-CAT-067'
    reason: >-
      Existing permission examples cover selected personas, not the independent allow and deny
      matrix for every named key.
    provenance: unknown
    source:
      - 'rhythm-v2-docs:specs/commerce/catalog/FINDINGS.md#FND-COM-CAT-067'
---

# Calculate and explain a buyer’s item price: Capability Test Specification

## Coverage

| Rule | Proof Scenario or Gap |
|---|---|
| `BR-CAP-COM-CAT-013-01` | `ATS-COM-CAT-013-01`, `ATS-COM-CAT-013-15`, `ATS-COM-CAT-013-18`, `ATS-COM-CAT-013-20` |
| `BR-CAP-COM-CAT-013-02` | `ATS-COM-CAT-013-02`, `ATS-COM-CAT-013-16` |
| `BR-CAP-COM-CAT-013-03` | `ATS-COM-CAT-013-03`, `ATS-COM-CAT-013-17` |
| `BR-CAP-COM-CAT-013-04` | `ATS-COM-CAT-013-04` |
| `BR-CAP-COM-CAT-013-05` | `ATS-COM-CAT-013-05` |
| `BR-CAP-COM-CAT-013-06` | `ATS-COM-CAT-013-05` |
| `BR-CAP-COM-CAT-013-07` | `ATS-COM-CAT-013-06` |
| `BR-CAP-COM-CAT-013-08` | `ATS-COM-CAT-013-07`, `ATS-COM-CAT-013-19` |
| `BR-CAP-COM-CAT-013-09` | `ATS-COM-CAT-013-08` |
| `BR-CAP-COM-CAT-013-10` | `ATS-COM-CAT-013-09` |
| `BR-CAP-COM-CAT-013-11` | `ATS-COM-CAT-013-10` |
| `BR-CAP-COM-CAT-013-12` | `ATS-COM-CAT-013-11` |
| `BR-CAP-COM-CAT-013-13` | `ATS-COM-CAT-013-12` |
| `BR-CAP-COM-CAT-013-14` | `ATS-COM-CAT-013-13`, `ATS-COM-CAT-013-17` |
| `BR-CAP-COM-CAT-013-15` | `ATS-COM-CAT-013-14` |

## Interactions With Related Capabilities

The tax address changes the quoted line tax. Removing an audience binding removes qualification. A moved boundary changes the next quote without rewriting an earlier sale. Purchasing strategy and handler activity alter the quote. Browsing and quoting use the same buyer context. Changing audience price changes the winning promotional alternative.

Coverage describes specified proof. A binding names the existing assertions that contribute to it; proposed designs remain unexecuted.
