---
id: WF-COM-CAT-020
title: Create or edit a promotion and its parts
trigger: >-
  Campaign preparation validates benefit shape and selling-area criteria, commits configuration with history
  and enforces lifecycle and first-use protection.
product_area: commerce
module: catalog
revision: 1
status: draft
design_intent: confirmed
implementation_evidence: observed
inference: []
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-COM-CAT-014
moves:
  - STATE-COM-CAT-007
crosses:
  - SVC-COM-CAT-promotions
idempotency: >-
  Campaign creation uses name uniqueness rather than a module-owned replay key. Versioned edits and allowed
  lifecycle moves govern repeated writes.
concurrency: >-
  Campaign writes lock the row and check its version. Database protected-term checks also reject an edit
  racing the first redemption.
hops:
  - 'n': 1
    actor_or_service: Promotions API
    action: >-
      Validate allowed campaign changes and the complete offer shape, returning all detected problems
      together.
    anchor: 'services-commerce:modules/promotions/services/promotions/promotion-workflow.ts:editPromotion'
    seam: sync
    on_failure: 'Reject invalid offer mixtures, eligibility placement or prohibited kind changes.'
    left_inconsistent: No campaign edit is saved.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotions.e2e.spec.js#names every problem of a sloppy
      promotion in one 400 promotion_invalid, the zero cap among them (Q07, journey 0203)
  - 'n': 2
    actor_or_service: Promotions API with area providers
    action: Validate each part’s criteria through its registered selling-area provider inside the save transaction.
    anchor: 'services-commerce:modules/promotions/services/promotions/promotion-workflow.ts:partsCriteriaRefusal'
    seam: sync
    on_failure: >-
      Reject unknown keys, invalid types or missing product and category references with part-specific
      feedback.
    left_inconsistent: No invalid part configuration commits.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotions.e2e.spec.js#refuses a product that is not
      merchandise, 400 promotion_invalid naming the part and the product
  - 'n': 3
    actor_or_service: Promotions persistence
    action: >-
      Create a numbered draft or edit named fields, replace parts and append history while enforcing names,
      protected terms and limits.
    anchor: 'services-commerce:modules/promotions/services/persistence/promotions.ts'
    seam: sync
    on_failure: Roll back prohibited edits with field-specific guidance.
    left_inconsistent: The earlier campaign and part set remain intact.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotion-redemptions.e2e.spec.js#names the column and
      offers to end the promotion and duplicate it
  - 'n': 4
    actor_or_service: Promotions API
    action: >-
      Validate lifecycle moves and activation conditions; duplication creates a distinct draft without
      inherited codes or usage.
    anchor: 'services-commerce:modules/promotions/services/promotions/promotion-workflow.ts:movePromotion'
    seam: sync
    on_failure: Reject unsupported moves or missing activation prerequisites.
    left_inconsistent: The failed action retains the prior campaign state.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: 'services-commerce:modules/promotions/tests/e2e/promotions.e2e.spec.js#copies no code and no use'
branches:
  - >-
    Console promotion creation can attempt a separate first-code request and compensating rename and archive;
    those requests are documented by the composite console workflow.
waits: []
evidence:
  - >-
    services-commerce at c8b66a98: modules/promotions/services/promotions/promotion-workflow.ts,
    promotion-rules.ts; services/persistence/promotions.ts, promotion-parts.ts;
    services/areas/merchandise-provider.ts.
  - >-
    react-console at baf6dae3: components/commerce/promotions/promotion-editor.tsx, promotion-create-page.tsx,
    lifecycle-actions.tsx; lib/commerce/promotions/live.ts.
controls: [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-CRY-001, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002]
---

# Create or edit a promotion and its parts

## What It Does

Campaign preparation validates benefit shape and selling-area criteria, commits configuration with history and enforces lifecycle and first-use protection.

## The Flow

```mermaid
sequenceDiagram
  participant S as Staff
  participant A as Promotions API
  participant R as Area providers
  participant D as Database
  S->>A: create, edit, move or duplicate
  A->>A: shape and lifecycle checks (all problems)
  A->>D: begin, lock promotion
  A->>R: validate each part's criteria
  A->>D: write promotion and parts, history entry
  D-->>A: committed, or locked terms refused
  A-->>S: promotion
```

## When Something Fails

Reject invalid offer mixtures, eligibility placement or prohibited kind changes. No campaign edit is saved. Reject unknown keys, invalid types or missing product and category references with part-specific feedback. No invalid part configuration commits. Roll back prohibited edits with field-specific guidance. The earlier campaign and part set remain intact. Reject unsupported moves or missing activation prerequisites. The failed action retains the prior campaign state.

## Open Design Questions

The implemented recovery and concurrency behaviour above defines the current boundary. Any change to transaction scope or product authority requires a recorded decision.
