---
id: WF-COM-CAT-006
title: Write merchandise and its Accounting product in one transaction
trigger: >-
  The combined merchandise service request can commit goods details and the Accounting product together.
  Retirement and extension removal remain distinct actions.
product_area: commerce
module: catalog
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
inference: []
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
implements:
  - CAP-COM-CAT-007
crosses:
  - SVC-COM-CAT-merchandise
idempotency: >-
  Existing extensions reject another creation. Product and extension versions govern replacement, and repeated
  retirement receives a state refusal.
concurrency: >-
  The merchandise row and Accounting product write share the caller’s transaction and their version checks, so
  a refused half prevents the combined commit.
hops:
  - 'n': 1
    actor_or_service: Merchandise service
    action: Validate selected option-set kinds and each variant’s values.
    anchor: 'services-commerce:modules/merchandise/services/persistence/merchandise-extensions.ts'
    seam: sync
    on_failure: 'Reject invalid sets, selections, stock codes or duplicate merchandise extensions.'
    left_inconsistent: No combined write is committed.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/e2e/merchandise.e2e.spec.js#answers 400 to a product the
      tenant has not, a selection off the sets, a SKU in use and a second extension, naming the rule
  - 'n': 2
    actor_or_service: Merchandise service to Accounting's write function
    action: >-
      Within the Commerce transaction, call Accounting’s product writer and require its Standard base-currency
      coverage.
    anchor: 'services-commerce:modules/merchandise/services/persistence/merchandise-products.ts'
    seam: sync
    on_failure: Return Accounting’s refusal and roll back the combined transaction.
    left_inconsistent: Neither the product nor its extension is partially committed.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/integration/services/persistence/merchandise-products.queries.integration.spec.js#refuses
      a grid without a standard price 400 with Accounting’s rule
  - 'n': 3
    actor_or_service: Merchandise service
    action: 'Write merchandise details and reconcile variants by stock code, retaining identifiers for codes kept.'
    anchor: 'services-commerce:modules/merchandise/services/persistence/merchandise-extensions.ts:updateMerchandise'
    seam: sync
    on_failure: Roll back both component writes if reconciliation fails.
    left_inconsistent: The product and goods details retain their previous state.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/integration/services/persistence/merchandise-products.queries.integration.spec.js#leaves
      no product when the database refuses the extension row
  - 'n': 4
    actor_or_service: Merchandise service
    action: >-
      Call Accounting retirement while retaining goods details, or delete an extension only when inventory
      references permit it.
    anchor: 'services-commerce:modules/merchandise/services/persistence/merchandise-extensions.ts:retireMerchandise'
    seam: sync
    on_failure: Reject repeated retirement or deletion blocked by inventory references.
    left_inconsistent: No partial retirement or extension removal is retained.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/merchandise/tests/e2e/merchandise.e2e.spec.js#retires the product at its
      version, keeping the extension; a second retirement is 409
branches:
  - 'Without an Accounting base-product payload, the request writes goods details for an existing product only.'
waits: []
evidence:
  - >-
    services-commerce at c8b66a98: modules/merchandise/services/persistence/merchandise-extensions.ts,
    merchandise-products.ts (Accounting write functions through the product writer role);
    tests/integration/services/persistence/merchandise-products.queries.integration.spec.js.
controls: [TC-IAM-006, 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-CRY-001, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-LOG-004, TC-LOG-005, TC-AVL-004, TC-MON-001, TC-MON-002, TC-MON-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002]
---

# Write merchandise and its Accounting product in one transaction

## What It Does

The combined merchandise service request can commit goods details and the Accounting product together. Retirement and extension removal remain distinct actions.

## The Flow

```mermaid
sequenceDiagram
  participant X as API caller
  participant M as Merchandise service
  participant A as Accounting write function
  participant D as Database (one transaction)
  X->>M: product base + details + variants
  M->>M: check option sets and selections
  M->>D: begin
  M->>A: save product (in transaction)
  A-->>M: product or refusal
  M->>D: write extension, reconcile variants
  D-->>M: commit or roll back both
  M-->>X: merchandise with product
```

## When Something Fails

Reject invalid sets, selections, stock codes or duplicate merchandise extensions. No combined write is committed. Return Accounting’s refusal and roll back the combined transaction. Neither the product nor its extension is partially committed. Roll back both component writes if reconciliation fails. The product and goods details retain their previous state. Reject repeated retirement or deletion blocked by inventory references. No partial retirement or extension removal is retained.

## 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.
