---
id: ATS-COM-CAT-004
scope: CAP-COM-CAT-004
product_area: commerce
module: catalog
revision: 2
status: proposed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-COM-CAT-004-01
    title: Tax-zone validation reports invalid configuration
    level: api
    proves:
      - BR-CAP-COM-CAT-004-01
      - BR-CAP-COM-CAT-004-02
    preconditions: A tax-zone request can be submitted
    steps: 'Use an unassigned country, excessive rate, missing rate name and reversed rate interval'
    expected: Validation identifies each issue without saving the zone
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-032
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers 400 to a
      country code that is not two upper-case letters, naming the field
    category: validation
    binding_evidence:
      - >-
        services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers 400 to a
        country code that is not two upper-case letters, naming the field
      - >-
        services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers 400 to a
        rate past the ceiling and to a rate without a name, naming the field
      - >-
        services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers 400 to a
        rate whose effective_from follows its effective_to, naming the field, not the database rule,
        and saves nothing (DEF-COM-0866)
    step_evidence:
      - do: 'Use an unassigned country, excessive rate, missing rate name and reversed rate interval'
        provenance: test
        source: >-
          services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers 400 to a
          country code that is not two upper-case letters, naming the field
    assertions:
      - expect: Validation identifies each issue without saving the zone
        provenance: test
        source: >-
          services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#answers 400 to a
          country code that is not two upper-case letters, naming the field
  - id: ATS-COM-CAT-004-02
    title: Tax-prefix clearing requires explicit null
    level: unit
    proves:
      - BR-CAP-COM-CAT-004-03
    preconditions: A saved zone carries H2X
    steps: Replace its prefix with spaces and then null
    expected: Spaces fail validation; null removes the prefix
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-042
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/unit/api/blank-tax-zone-values.spec.js#refuses a
      whitespace-only postal_code_prefix on a replacement 400 naming the field, instead of clearing
      H2X
    category: validation
    binding_evidence:
      - >-
        services-commerce:modules/configuration/tests/unit/api/blank-tax-zone-values.spec.js#refuses
        a whitespace-only postal_code_prefix on a replacement 400 naming the field, instead of
        clearing H2X
      - >-
        services-commerce:modules/configuration/tests/unit/api/blank-tax-zone-values.spec.js#clears
        postal_code_prefix on an explicit null, the one way to clear it
    step_evidence:
      - do: Replace its prefix with spaces and then null
        provenance: test
        source: >-
          services-commerce:modules/configuration/tests/unit/api/blank-tax-zone-values.spec.js#refuses
          a whitespace-only postal_code_prefix on a replacement 400 naming the field, instead of
          clearing H2X
    assertions:
      - expect: Spaces fail validation; null removes the prefix
        provenance: test
        source: >-
          services-commerce:modules/configuration/tests/unit/api/blank-tax-zone-values.spec.js#refuses
          a whitespace-only postal_code_prefix on a replacement 400 naming the field, instead of
          clearing H2X
  - id: ATS-COM-CAT-004-03
    title: Tax applicability follows product-type selection
    level: unit
    proves:
      - BR-CAP-COM-CAT-004-04
    preconditions: Rates include unrestricted and selected-type configurations
    steps: Calculate tax for several product types and submit a list of 101 types
    expected: 'Only matching rates contribute, and the oversized list fails'
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-089
      - TST-COM-CAT-042
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#applies to every
      product type when it names none, and to the named ones alone otherwise
    category: boundary
    binding_evidence:
      - >-
        services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#applies to
        every product type when it names none, and to the named ones alone otherwise
      - >-
        services-commerce:modules/configuration/tests/unit/api/blank-tax-zone-values.spec.js#refuses
        101 product types, one past the limit, naming product_type_ids
    step_evidence:
      - do: Calculate tax for several product types and submit a list of 101 types
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#applies to
          every product type when it names none, and to the named ones alone otherwise
    assertions:
      - expect: 'Only matching rates contribute, and the oversized list fails'
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#applies to
          every product type when it names none, and to the named ones alone otherwise
  - id: ATS-COM-CAT-004-04
    title: Specificity selects the rate components to round
    level: unit
    proves:
      - BR-CAP-COM-CAT-004-05
      - BR-CAP-COM-CAT-004-06
    preconditions: 'Country, region and prefix zones match the address'
    steps: Calculate tax for an address matching the prefix zone
    expected: That zone supplies individually rounded components whose sum is the line tax
    negative: false
    data_needs: none
    bound_to:
      - TST-COM-CAT-089
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#scores a prefix
      over a region over a country alone
    category: rule
    binding_evidence:
      - >-
        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-tax-calculation.spec.js#rounds half
        away from zero to a whole minor unit
    step_evidence:
      - do: Calculate tax for an address matching the prefix zone
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#scores a
          prefix over a region over a country alone
    assertions:
      - expect: That zone supplies individually rounded components whose sum is the line tax
        provenance: test
        source: >-
          services-commerce:modules/pricing/tests/unit/tax/zone-tax-calculation.spec.js#scores a
          prefix over a region over a country alone
  - id: ATS-COM-CAT-004-05
    title: Address requirements differ between checkout and display
    level: unit
    proves:
      - BR-CAP-COM-CAT-004-07
    preconditions: A zone can tax every product type
    steps: Request checkout and display calculations without an address
    expected: Checkout refuses the taxable line; display returns zero tax with a warning
    negative: true
    data_needs: none
    bound_to:
      - TST-COM-CAT-088
    provenance: test
    source: >-
      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
    category: failure
    binding_evidence:
      - >-
        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/tax/zone-table-tax.spec.js#answers zero with
        the no-address warning, not a refusal, reading nothing
    step_evidence:
      - do: Request checkout and display calculations without an address
        provenance: test
        source: >-
          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
    assertions:
      - expect: Checkout refuses the taxable line; display returns zero tax with a warning
        provenance: test
        source: >-
          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
  - id: ATS-COM-CAT-004-06
    title: Tax reads do not grant tax writes
    level: api
    proves:
      - BR-CAP-COM-CAT-004-10
    preconditions: The caller is a read-only tax user
    steps: Inspect and attempt to change zones
    expected: Inspection succeeds and changes fail permission checks
    negative: true
    data_needs: Read-exclusively persona
    bound_to:
      - TST-COM-CAT-028
    provenance: test
    source: >-
      services-commerce:modules/configuration/tests/e2e/configuration-security.e2e.spec.js#lists and
      reads the tax zones
    category: permission
    binding_evidence:
      - >-
        services-commerce:modules/configuration/tests/e2e/configuration-security.e2e.spec.js#lists
        and reads the tax zones
      - >-
        services-commerce:modules/configuration/tests/e2e/configuration-security.e2e.spec.js#is
        refused the creates, 403 permission_missing
    step_evidence:
      - do: Inspect and attempt to change zones
        provenance: test
        source: >-
          services-commerce:modules/configuration/tests/e2e/configuration-security.e2e.spec.js#lists
          and reads the tax zones
    assertions:
      - expect: Inspection succeeds and changes fail permission checks
        provenance: test
        source: >-
          services-commerce:modules/configuration/tests/e2e/configuration-security.e2e.spec.js#lists
          and reads the tax zones
  - id: ATS-COM-CAT-004-07
    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-004-08
    title: Tax-zone removal leaves no retained rate rows
    category: invariant
    level: api
    proves:
      - INV-DOM-COM-CAT-001-10
      - BR-CAP-COM-CAT-004-09
    preconditions: A zone has two valid integer-basis-point rates.
    steps: >-
      Read its rates, remove the zone, request it again and inspect the rate rows; separately
      attempt a fractional or excessive rate.
    expected: 'The removed zone is not found, its rates are gone and invalid rate inserts refuse.'
    negative: true
    data_needs: Run-owned workspace and records; fixed clock where dates are exercised.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#deletes a zone with
        its rates, after which it reads 404; deleting it again is 404
      - >-
        rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-001-checkout-policy.md#DOM-COM-CAT-001
    verification_note: >-
      This design references the invariant; executable binding awaits an assertion-by-assertion
      comparison with this complete scenario.
    step_evidence:
      - do: >-
          Read its rates, remove the zone, request it again and inspect the rate rows; separately
          attempt a fractional or excessive rate.
        provenance: proposed
        source:
          - >-
            services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#deletes a zone
            with its rates, after which it reads 404; deleting it again is 404
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-001-checkout-policy.md#DOM-COM-CAT-001
    assertions:
      - expect: 'The removed zone is not found, its rates are gone and invalid rate inserts refuse.'
        provenance: proposed
        source:
          - >-
            services-commerce:modules/configuration/tests/e2e/tax-zones.e2e.spec.js#deletes a zone
            with its rates, after which it reads 404; deleting it again is 404
          - >-
            rhythm-v2-docs:specs/commerce/catalog/design/objects/DOM-COM-CAT-001-checkout-policy.md#DOM-COM-CAT-001
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-004-charge-tax-according-to-where-the-buyer-is.md#CAP-COM-CAT-004
  - >-
    rhythm-v2-docs:specs/commerce/catalog/design/workflows/WF-COM-CAT-009-quote-a-price.md#WF-COM-CAT-009
product_intent: proposed
coverage:
  rule: covered
  transition: 'gap: FND-COM-CAT-058'
  validation: covered
  permission: covered
  edge: 'gap: FND-COM-CAT-058'
  failure: covered
  boundary: covered
  concurrency: 'gap: FND-COM-CAT-058'
  invariant: covered
  interaction: covered
coverage_notes:
  transition: >-
    No complete transition scenario is established by this back-fill; the finding records the
    missing proof.
  edge: >-
    No complete edge scenario is established by this back-fill; the finding records the missing
    proof.
  concurrency: Concurrent execution has no complete scenario; sequential retry is insufficient proof.
permission_coverage:
  - key: commerce.tax-zones.read
    coverage: 'gap: FND-COM-CAT-058'
    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-058'
  - key: commerce.tax-zones.create
    coverage: 'gap: FND-COM-CAT-058'
    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-058'
  - key: commerce.tax-zones.update
    coverage: 'gap: FND-COM-CAT-058'
    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-058'
  - key: commerce.tax-zones.delete
    coverage: 'gap: FND-COM-CAT-058'
    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-058'
  - key: commerce.tax-zones.bulk
    coverage: 'gap: FND-COM-CAT-058'
    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-058'
---

# Calculate tax from buyer geography: Capability Test Specification

## Coverage

| Rule | Proof Scenario or Gap |
|---|---|
| `BR-CAP-COM-CAT-004-01` | `ATS-COM-CAT-004-01` |
| `BR-CAP-COM-CAT-004-02` | `ATS-COM-CAT-004-01` |
| `BR-CAP-COM-CAT-004-03` | `ATS-COM-CAT-004-02` |
| `BR-CAP-COM-CAT-004-04` | `ATS-COM-CAT-004-03` |
| `BR-CAP-COM-CAT-004-05` | `ATS-COM-CAT-004-04`, `ATS-COM-CAT-004-07` |
| `BR-CAP-COM-CAT-004-06` | `ATS-COM-CAT-004-04`, `ATS-COM-CAT-004-07` |
| `BR-CAP-COM-CAT-004-07` | `ATS-COM-CAT-004-05`, `ATS-COM-CAT-004-07` |
| `BR-CAP-COM-CAT-004-10` | `ATS-COM-CAT-004-06` |

## Interactions With Related Capabilities

The tax address changes the quoted line tax.

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