---
id: CAP-ACC-PRD-004
title: Save product corrections against the version read
feature: FEAT-ACC-PRD-01
product_area: accounting
module: products
revision: 3
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes:
  - react-console acceptance inventory CAP-ACC-PRD-004
  - react-console acceptance inventory CAP-ACC-PRD-020 (the editor's save)
  - react-console acceptance inventory RUL-ACC-PRD-022
  - react-console acceptance inventory RUL-ACC-PRD-030
  - react-console acceptance inventory RUL-ACC-PRD-031
  - react-console acceptance inventory RUL-ACC-PRD-051
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D3'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D11'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D12'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R10'
purpose: An administrator opens a product and works from the version returned by that read.
outcomes:
  - Corrections respect intervening changes.
concepts:
  - CON-ACC-PRD-001
actors:
  - ACT-ACC-006
  - ACT-ACC-010
surfaces:
  - console
  - api
permissions:
  - key: accounting.products.update
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-ACC-PRD-004-01
    proved_by: [ATS-ACC-PRD-004-01, ATS-ACC-PRD-004-11, TST-ACC-PRD-007, TST-ACC-PRD-030, TST-ACC-PRD-040]
    statement: Replacement requires the version originally read and rejects a stale version without persisting changes.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: 'services-accounting:modules/products/tests/e2e/products.e2e.spec.js#answers 409 to a stale version'
    origin: none
  - id: BR-CAP-ACC-PRD-004-02
    proved_by: [ATS-ACC-PRD-004-02, ATS-ACC-PRD-014-07, TST-ACC-PRD-039, TST-ACC-PRD-040]
    statement: >-
      Replacement clears omitted optional fields and replaces every owned collection, except an omitted
      confirmation setting retains its stored value.
    kind: policy
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.features.integration.spec.js#keeps
      the confirmation e-mail and its template
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D12'
  - id: BR-CAP-ACC-PRD-004-03
    proved_by: [ATS-ACC-PRD-004-03, TST-ACC-PRD-030, TST-ACC-PRD-040]
    statement: >-
      Replacement rejects a body identity differing from the addressed product and refuses changes to kind or
      business unit.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products.e2e.spec.js#answers 400 to a body whose id is
      not the path’s: an id never changes
    origin: none
  - id: BR-CAP-ACC-PRD-004-04
    proved_by: [ATS-ACC-PRD-004-04, ATS-ACC-PRD-005-09, ATS-ACC-PRD-007-10, TST-ACC-PRD-030, TST-ACC-PRD-040]
    statement: Retired products reject replacement until reactivated.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products.e2e.spec.js#answers 409 to replacing a retired
      product
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - id: BR-CAP-ACC-PRD-004-05
    proved_by: [ATS-ACC-PRD-004-05, ATS-ACC-PRD-004-10, TST-ACC-PRD-040, TST-ACC-PRD-044, TST-ACC-PRD-066]
    statement: >-
      An update-locked product accepts replacement only when the request explicitly removes that lock or
      reduces it to delete-only; product editors may make that change.
    kind: permission
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a locked product replaced without stating sys_locked, or with the same lock
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D11'
  - id: BR-CAP-ACC-PRD-004-06
    proved_by: [ATS-ACC-PRD-002-15, ATS-ACC-PRD-004-06, TST-ACC-PRD-040]
    statement: 'A failed replacement preserves the previous product, prices and components.'
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#leaves
      the old prices and components when a rule refuses the replacement
    origin: none
  - id: BR-CAP-ACC-PRD-004-07
    proved_by: []
    statement: >-
      Product replacement holds the product lock throughout its transaction, serializing it with other product
      and accounting setup writes.
    kind: invariant
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: 'services-accounting:modules/products/services/persistence/products.ts:updateProduct'
    origin: none
  - id: BR-CAP-ACC-PRD-004-08
    proved_by: [ATS-ACC-PRD-004-07, TST-ACC-PRD-028]
    statement: Product replacement requires product update permission.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products-security.e2e.spec.js#is refused replacing a
      product
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D3'
  - id: BR-CAP-ACC-PRD-004-09
    proved_by: [ATS-ACC-PRD-004-08]
    statement: >-
      A replacement naming another workspace is rejected, while submitted pricing model, accounting summary
      and tax class are discarded as derived values.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: observed
    source: 'services-accounting:modules/products/aws/api/products-api-mapper.ts:toInput'
    origin: none
  - id: BR-CAP-ACC-PRD-004-10
    proved_by: [ATS-ACC-PRD-004-09, ATS-ACC-PRD-012-04, ATS-ACC-PRD-013-05, TST-ACC-PRD-006]
    statement: >-
      Console replacement resends loaded values for availability, portal visibility and settings whose
      controls are hidden.
    kind: policy
    enforced_at:
      - console
    must_prove: true
    provenance: observed
    source: 'react-console:lib/accounting-config/product-form.ts:formToWrite'
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R10'
  - id: BR-CAP-ACC-PRD-004-11
    proved_by: []
    statement: A console save rejected for a newer version explains the conflict and offers Reload.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: test
    source: 'react-console:acceptance/accounting/playwright/journeys/products/ACT-ACC-0114.spec.ts#ACT-ACC-0114'
    origin: none
scenarios:
  - id: SC-CAP-ACC-PRD-004-01
    given: Two administrators read the same product version.
    when: One saves a price and the other then saves a name.
    then: The later save conflicts and offers a reload.
    provenance: test
    source: 'react-console:acceptance/accounting/playwright/journeys/products/ACT-ACC-0114.spec.ts#ACT-ACC-0114'
  - id: SC-CAP-ACC-PRD-004-02
    given: 'An API client configured a ceiling, task instructions and quantity bands.'
    when: Finance changes the product name through the console.
    then: Those stored settings survive the correction.
    provenance: test
    source: 'react-console:acceptance/accounting/playwright/journeys/products/ACT-ACC-0112.spec.ts#ACT-ACC-0112'
edge_cases:
  - id: SC-CAP-ACC-PRD-004-03
    given: A product has an update-and-delete lock.
    when: A replacement repeats the existing lock.
    then: The request fails with lock-removal guidance.
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a locked product replaced without stating sys_locked, or with the same lock
constraints: []
depends_on:
  - CAP-ACC-PRD-002
related:
  - CAP-ACC-PRD-005
  - CAP-ACC-PRD-008
  - CAP-ACC-PRD-015
journeys:
  - JNY-ACC-PRD-001
  - JNY-ACC-PRD-004
open_questions: []
evidence:
  - 'react-console:acceptance/accounting/playwright/journeys/products/ACT-ACC-0112.spec.ts'
  - 'react-console:acceptance/accounting/playwright/journeys/products/ACT-ACC-0114.spec.ts'
  - 'react-console:components/accounting-config/products/product-editor.tsx'
  - 'react-console:lib/accounting-config/product-form.ts'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md'
  - 'services-accounting:modules/products/aws/api/products-api-mapper.ts'
  - 'services-accounting:modules/products/aws/api/products.routes.ts'
  - 'services-accounting:modules/products/services/persistence/products.ts'
  - 'services-accounting:modules/products/services/rules/product-rules.ts'
  - 'services-accounting:modules/products/tests/e2e/products-security.e2e.spec.js'
  - 'services-accounting:modules/products/tests/e2e/products.e2e.spec.js'
  - >-
    services-accounting:modules/products/tests/integration/services/persistence/products.features.integration.spec.js
  - 'services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js'
  - 'services-accounting:modules/products/tests/unit/api/products-api-mapper.spec.js'
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, 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-DAT-001, TC-DAT-004, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-PI-005]
exercised_by: [FAS-ACC-PRD-01-03, FAS-ACC-PRD-01-04, FAS-ACC-PRD-01-05, FAS-ACC-PRD-02-02, FAS-ACC-PRD-02-04, FAS-ACC-PRD-02-05, FAS-ACC-PRD-03-02, FAS-ACC-PRD-03-03, FAS-ACC-PRD-03-04, FAS-ACC-PRD-03-05]
suites: [TST-ACC-PRD-006, TST-ACC-PRD-007, TST-ACC-PRD-028, TST-ACC-PRD-030, TST-ACC-PRD-039, TST-ACC-PRD-040, TST-ACC-PRD-044, TST-ACC-PRD-066]
---

# Save product corrections against the version read

## Purpose

An administrator opens a product and works from the version returned by that read.

## How It Works

An administrator opens a product and works from the version returned by that read. Saving submits a replacement with the intended collections and optional values. Another writer’s intervening save causes a conflict instead of a silent overwrite. The console explains that conflict and offers a reload. Retired products require reactivation first. Update locks require an explicit change to the lock before replacement can proceed. Hidden console settings are resent from the loaded product. A failed save preserves the previous product and its collections. Confirmation configuration has its own omission behavior, so integrations need to distinguish replacement from an unchanged setting.

## Relationships

This outcome uses create a product with a permanent identity.
