---
id: STATE-COM-CAT-005
title: Pricing period
object: DOM-COM-CAT-004
product_area: commerce
module: catalog
revision: 1
status: draft
design_intent: confirmed
implementation_evidence: observed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
states:
  - name: upcoming
    meaning: >-
      The interval starts in the future and remains editable except where an immutable adjacent interval fixes
      its boundary.
  - name: current
    meaning: 'The pricing instant is within an interval with no recorded sale, so edits remain available.'
  - name: current and sold in
    meaning: A committed sale fixes name and start; the current end can still move to now or later.
  - name: elapsed
    meaning: The interval has ended and cannot be edited or removed individually.
  - name: removed
    meaning: A permitted replacement or whole-schedule deletion removed the interval.
transitions:
  - from: upcoming
    to: current
    trigger: The pricing instant reaches the interval start.
    guard: No additional condition applies.
    side_effects:
      - Price selection now uses this interval.
    who: Time
    provenance: test
    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
  - from: current
    to: current and sold in
    trigger: Placement commits the interval’s first priced sale.
    guard: The sale must commit its placement transaction.
    side_effects:
      - The interval acquires its permanent first-sale timestamp.
    who: Order placement
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/integration/db/member-pricing-tables.table.integration.spec.js#records
      the first sale once, never cleared or moved, after which the period keeps its name and is never deleted,
      nor its schedule
  - from: current and sold in
    to: current and sold in
    trigger: An API edit changes the sold-in current interval’s end.
    guard: 'The end must be now or later, with the following interval adjusted to retain adjacency.'
    side_effects:
      - The following interval receives the new shared boundary without changing earlier charges.
    who: Schedule administrator (API only)
    provenance: test
    source: >-
      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
  - from: current
    to: elapsed
    trigger: The interval end is reached.
    guard: No additional condition applies.
    side_effects: []
    who: Time
    provenance: observed
    source: 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
  - from: current and sold in
    to: elapsed
    trigger: The interval end is reached.
    guard: No additional condition applies.
    side_effects: []
    who: Time
    provenance: observed
    source: 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
  - from: upcoming
    to: removed
    trigger: 'Replacement omits an editable interval, or staff delete its schedule.'
    guard: 'Commit must retain contiguous intervals, and schedule deletion requires no following products.'
    side_effects: []
    who: Schedule administrator (API only)
    provenance: test
    source: >-
      services-commerce:modules/pricing/tests/integration/services/persistence/pricing-schedules.integration.spec.js#answers
      the replaced record at version 2: the kept period at version 1, the changed one at version 2, the new
      one inserted, the one left out gone
  - from: elapsed
    to: removed
    trigger: Staff remove the entire schedule.
    guard: No sale may have used the interval and no product may retain its schedule.
    side_effects: []
    who: Schedule administrator (API only)
    provenance: observed
    source: 'services-commerce:modules/pricing/db/pricing-periods.table.ts'
invalid:
  - from: current and sold in
    to: removed
    why: 'Recorded sales prevent removing the interval, including by deleting its schedule.'
  - from: elapsed
    to: current
    why: Elapsed boundaries cannot be edited to make an interval current again.
  - from: current and sold in
    to: current
    why: The database retains the first-sale timestamp permanently.
concurrency: >-
  Commit-time contiguity checks evaluate a complete boundary change. Database protection checks see the
  first-sale stamp. Schedule disable or deletion locks the schedule exclusively, while purchasing selection
  takes a shared lock.
invariants:
  - INV-DOM-COM-CAT-004-04
  - INV-DOM-COM-CAT-004-05
  - INV-DOM-COM-CAT-004-06
evidence:
  - >-
    services-commerce at c8b66a98: modules/pricing/db/pricing-periods.table.ts
    (pricing_periods_contiguity_trigger, pricing_periods_immutable_trigger);
    services/persistence/pricing-schedules.ts (updatePricingSchedule, findPricingPeriodAt,
    stampPricingPeriodFirstSales); docs/milestones/COMM-M1-set-up-what-you-sell.md (D6).
---

# Pricing period

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> upcoming
  upcoming --> current: start reached
  current --> current_sold_in: first sale placed
  current_sold_in --> current_sold_in: end moved, not before now
  current --> elapsed: end passed
  current_sold_in --> elapsed: end passed
  upcoming --> removed: left out or schedule deleted
  elapsed --> removed: schedule deleted, never sold in
```

## Transitions Explained

Upcoming: The interval starts in the future and remains editable except where an immutable adjacent interval fixes its boundary. Current: The pricing instant is within an interval with no recorded sale, so edits remain available. Current and sold in: A committed sale fixes name and start; the current end can still move to now or later. Elapsed: The interval has ended and cannot be edited or removed individually. Removed: A permitted replacement or whole-schedule deletion removed the interval.

## What Cannot Happen

Recorded sales prevent removing the interval, including by deleting its schedule. Elapsed boundaries cannot be edited to make an interval current again. The database retains the first-sale timestamp permanently.

## Concurrency and Timing

Commit-time contiguity checks evaluate a complete boundary change. Database protection checks see the first-sale stamp. Schedule disable or deletion locks the schedule exclusively, while purchasing selection takes a shared lock.
