---
id: DOM-ACC-PRD-002
title: Price
realises: CON-ACC-PRD-002
product_area: accounting
module: products
revision: 3
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owned_by: SVC-ACC-PRD-products
readable_by: []
lifecycle: STATE-ACC-PRD-005
custom_fields: none
identity: A price has no standalone catalogue key. The stored primary key combines its product with position,
  and a second unique key distinguishes audience, currency, period and opening instant. Replacing the grid
  can reorder positions.
attributes:
- name: product
  kind: reference
  required: true
  computed: false
  notes: The owning product controls the cell's lifetime; deleting it removes the cell.
- name: audience
  kind: reference
  required: true
  computed: false
  notes: Standard and member are built-in audience choices; other choices use Commerce identities. Reads and
    writes retain the older audience alias for compatibility.
- name: pricing period
  kind: reference
  required: false
  computed: false
  notes: An optional Commerce period identifier narrows the offer; absence means always. Accounting checks
    its spelling format but not whether the period exists.
- name: currency
  kind: reference
  required: true
  computed: false
  notes: Each cell uses a currency enabled for its business unit; supported foreign currencies require the
    unit's multicurrency setting.
- name: mode
  kind: enum
  required: true
  computed: false
  notes: A mode either sets money, prohibits a purchase, expresses a percentage of standard or requests base-currency
    conversion.
- name: amount
  kind: money
  required: false
  computed: false
  notes: Set mode alone carries a nonnegative integer amount in the cell currency's minor units.
- name: percent
  kind: number
  required: false
  computed: false
  notes: Percentage mode alone carries a value between zero and one hundred with at most two decimal places.
- name: effective window
  kind: date
  required: false
  computed: false
  notes: Optional effective boundaries form an interval that includes its opening and excludes its closing
    instant.
- name: position
  kind: number
  required: true
  computed: true
  notes: The service derives position from the submitted list order.
- name: special-price handover
  kind: collection
  required: false
  computed: false
  notes: Migration handovers separately preserve audience identity, eligibility criteria, amount, formula,
    rank and completion time.
relationships:
- to: DOM-ACC-PRD-001
  cardinality: many-to-one (up to 500 cells per product)
  ownership: The product owns all cells and replaces or removes them as part of product persistence.
  cross_service: false
- to: DOM-ACC-PRD-004
  cardinality: 'informational: setup fixed-share validation consults the standard base-currency amount'
  ownership: Both records belong to the same product, whose lock serializes their local writes.
  cross_service: false
invariants:
- id: INV-DOM-ACC-PRD-002-01
  statement: Saving a fixed-price product requires a set standard cell in its unit's base currency.
  enforced_at:
  - service
  - console
  provenance: test
  source: services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
    an Accounting Product without a standard price in the base currency
- id: INV-DOM-ACC-PRD-002-02
  statement: A giver-priced kind refuses all stored price cells.
  enforced_at:
  - service
  - console
  provenance: test
  source: services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
    a Donation Product with prices
- id: INV-DOM-ACC-PRD-002-03
  statement: A stored price uses a currency enabled for its owning product's unit.
  enforced_at:
  - service
  - database
  provenance: test
  source: services-accounting:modules/products/tests/integration/db/products.table.integration.spec.js#refuses
    a currency the product’s business unit is not enabled for
- id: INV-DOM-ACC-PRD-002-04
  statement: Validation admits only values appropriate to the cell mode and rejects conversion and prohibition
    in the base currency.
  enforced_at:
  - api
  - service
  provenance: observed
  source: services-accounting:modules/products/services/rules/product-rules.ts:pricesProblem
- id: INV-DOM-ACC-PRD-002-05
  statement: A percentage cell cannot target standard and requires a set standard cell in the same currency
    and matching or always period.
  enforced_at:
  - service
  provenance: test
  source: services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
    a percentage cell on the standard audience, and one with no standard price to be a percentage of
- id: INV-DOM-ACC-PRD-002-06
  statement: Saving refuses overlapping windows among cells with the same audience, currency and period.
  enforced_at:
  - service
  provenance: test
  source: services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
    two cells of one audience, currency and period applying at once, and accepts the same window in another
    period
- id: INV-DOM-ACC-PRD-002-07
  statement: HTTP audience checks always admit standard and member; other choices must be active in the workspace
    register when present, or already held when the register is absent.
  enforced_at:
  - service
  provenance: test
  source: services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#admits
    an audience the product already holds on a replacement (a migrated special price), even without the vocabulary
- id: INV-DOM-ACC-PRD-002-08
  statement: HTTP saving replaces the complete grid and accepts at most 500 cells.
  enforced_at:
  - api
  - service
  provenance: test
  source: services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
    more than 500 prices (the bound of the grid)
- id: INV-DOM-ACC-PRD-002-09
  statement: Set amounts cannot be negative or fractional minor units.
  enforced_at:
  - api
  - database
  provenance: test
  source: services-accounting:modules/products/tests/integration/db/products.table.integration.spec.js#refuses
    a negative amount
- id: INV-DOM-ACC-PRD-002-10
  statement: Each former special price becomes a local-audience cell and handover in the migration, and product
    deletion removes its handover by cascade.
  enforced_at:
  - database
  provenance: test
  source: 'services-accounting:modules/products/tests/integration/db/special-prices-a12-migration.integration.spec.js#hands
    each special price over as a product-local audience: minted id, product, name, criteria, amount in the
    base currency, formula, rank'
physical:
- product_prices
- pricing_audience_handovers
evidence:
- 'services-accounting at f47cb263: modules/products/db/product-prices.table.ts, pricing-audience-handovers.table.ts;
  services/persistence/products.ts (normalizedPrices, readPrices); services/persistence/pricing-audiences.ts
  (readAdmittedAudiences); services/rules/product-rules.ts (pricesProblem); docs/accounting/products.md (Prices);
  docs/migrations/accounting-modernization/products-a12-a13/README.md (Decisions taken in the session).'
- 'services-accounting at f47cb263: tests/integration/services/persistence/products.integration.spec.js; tests/integration/db/products.table.integration.spec.js;
  tests/integration/db/special-prices-a12-migration.integration.spec.js.'
- 'react-console at baf6dae3: lib/accounting-config/product-form.ts (formToWrite); components/accounting-config/products/product-prices-section.tsx.'
---

# Price

## What It Models

A price cell describes an audience's offer in one currency and pricing period, optionally limited to an effective interval. Its mode supplies an amount, a percentage of standard, conversion or purchase prohibition. Accounting validates the grid; Commerce owns choosing and calculating the price used for a purchase.

## Identity and Attributes

Cells are parts of a complete product grid and have no independent version. Storage preserves submitted order and separately prevents duplicate cell keys. Migration handovers retain the criteria and pricing context of former special prices until Commerce can establish their audiences.

## Relationships

The owning product supplies the business unit and its permitted currencies. Audience and period identifiers refer to Commerce vocabulary. Bundle allocation and accounting-share validation consult the product's selected standard base-currency amount.

## Invariants

Saving checks window overlap and the existence of backing cells in the matching or always period. Backing validation does not require those cells to cover every instant of the dependent cell's window. The database prevents duplicate keys, but window-overlap prevention belongs to write validation.

## How It Is Stored and Shared

Product saving replaces price rows within its local transaction. Shared readers use these rows and their lookup indexes. Accounting also retains and exports migration handovers; it provides no HTTP command to complete them. Commerce's realization worker is outside the verified source scope.

## Open Design Questions

Held migrated audiences need one admission policy across product write paths. Period existence validation and removal of the compatibility audience alias still need decisions or implementation work.
