---
id: CAP-ACC-PRD-002
title: Create a product with a permanent identity
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-002
  - 'react-console acceptance inventory CAP-ACC-PRD-017 (custom fields, retired)'
  - react-console acceptance inventory CAP-ACC-PRD-020 (the editor's create)
  - react-console acceptance inventory RUL-ACC-PRD-001
  - react-console acceptance inventory RUL-ACC-PRD-002
  - react-console acceptance inventory RUL-ACC-PRD-003
  - react-console acceptance inventory RUL-ACC-PRD-004
  - react-console acceptance inventory RUL-ACC-PRD-005
  - react-console acceptance inventory RUL-ACC-PRD-007
  - react-console acceptance inventory RUL-ACC-PRD-008
  - react-console acceptance inventory RUL-ACC-PRD-018
  - react-console acceptance inventory RUL-ACC-PRD-024
  - react-console acceptance inventory RUL-ACC-PRD-025
  - react-console acceptance inventory RUL-ACC-PRD-048
  - react-console acceptance inventory RUL-ACC-PRD-049
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#D12'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R10'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#Notes on the decisions:'
  - 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R11'
purpose: 'An administrator chooses a kind and business unit, then enters the product name.'
outcomes:
  - Creation preserves a durable catalogue identity.
concepts:
  - CON-ACC-PRD-001
actors:
  - ACT-ACC-006
  - ACT-ACC-010
surfaces:
  - console
  - api
preconditions:
  - The workspace contains the selected business unit.
postconditions:
  - >-
    Creation stores the product and its owned configuration at the first version, or the local transaction
    writes nothing.
permissions:
  - key: accounting.products.create
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-ACC-PRD-002-01
    proved_by: [ATS-ACC-PRD-002-01, TST-ACC-PRD-030, TST-ACC-PRD-031, TST-ACC-PRD-040]
    statement: >-
      A new product uses a supplied or name-derived configuration identity of at most 63 characters, starting
      with a lowercase letter and using lowercase letters, digits and single underscores without reserved
      words.
    kind: validation
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      an id that is not a configuration id, with the configuration-id sentence, and writes nothing
    origin: none
    examples:
      positive: The name Café Parking Permit (Annual) produces the readable Code cafe_parking_permit_annual.
      negative: A Code containing uppercase letters and a hyphen fails validation.
  - id: BR-CAP-ACC-PRD-002-02
    proved_by: [ATS-ACC-PRD-002-02, TST-ACC-PRD-040]
    statement: >-
      Name-derived identities receive no automatic suffix: a collision or a name yielding no valid identity
      requires the caller to supply one.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a second product whose name suggests a taken id, as other configuration does, naming the id
    origin: none
  - id: BR-CAP-ACC-PRD-002-03
    proved_by: [ATS-ACC-PRD-002-03, TST-ACC-PRD-030, TST-ACC-PRD-031, TST-ACC-PRD-040]
    statement: 'Within a workspace, product identities are unique across product kinds.'
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products.e2e.spec.js#answers 400 to the same id under
      another type: a product id is unique across every type
    origin: none
  - id: BR-CAP-ACC-PRD-002-04
    proved_by: [ATS-ACC-PRD-001-06, ATS-ACC-PRD-002-04, TST-ACC-PRD-029, TST-ACC-PRD-031, TST-ACC-PRD-040]
    statement: >-
      Creation requires an active registered kind, an existing business unit in the workspace and a nonblank
      name.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products-shared-base.e2e.spec.js#refuses a type
      registered inactive (an Events type), 400 with its sentence
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - id: BR-CAP-ACC-PRD-002-05
    proved_by: [ATS-ACC-PRD-002-05, TST-ACC-PRD-030, TST-ACC-PRD-031]
    statement: >-
      The server discards a submitted pricing model and uses the kind’s model, which the database also
      requires.
    kind: invariant
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/products.table.integration.spec.js#refuses a
      pricing model other than its type’s
    origin: none
  - id: BR-CAP-ACC-PRD-002-06
    proved_by: [ATS-ACC-PRD-002-06, ATS-ACC-PRD-007-10, TST-ACC-PRD-015, TST-ACC-PRD-031]
    statement: >-
      Creation fixes the product’s identity, kind and business unit, and both service and database reject
      later changes.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/products.table.integration.spec.js#refuses a
      change of business_unit_id (RHF01)
    origin: none
  - id: BR-CAP-ACC-PRD-002-07
    proved_by: [ATS-ACC-PRD-002-14]
    statement: >-
      Creation defaults to active and permits inactive, while ordinary creation and replacement requests
      cannot choose retired.
    kind: lifecycle
    enforced_at:
      - api
    must_prove: false
    provenance: observed
    source: 'services-accounting:modules/products/aws/api/products.schemas.ts:productShape'
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D4'
  - id: BR-CAP-ACC-PRD-002-08
    proved_by: [ATS-ACC-PRD-002-07, TST-ACC-PRD-031, TST-ACC-PRD-040]
    statement: A sale window with both boundaries requires its opening instant to precede its closing instant.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a sale window that ends before it starts
    origin: none
  - id: BR-CAP-ACC-PRD-002-09
    proved_by: [ATS-ACC-PRD-002-08, TST-ACC-PRD-031]
    statement: Cancellation and restocking fees use nonnegative whole minor units of the business unit’s base currency.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/db/products.table.integration.spec.js#refuses a
      negative cancellation fee
    origin: none
  - id: BR-CAP-ACC-PRD-002-10
    proved_by: [ATS-ACC-PRD-002-09, TST-ACC-PRD-039, TST-ACC-PRD-040]
    statement: >-
      Unsellable kinds reject fees and selling settings, while giver-priced kinds also reject quantity
      discounts and bundles.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.features.integration.spec.js#refuses
      a cancellation fee on a Legacy Product
    origin: none
  - id: BR-CAP-ACC-PRD-002-11
    proved_by: [ATS-ACC-PRD-002-10, ATS-ACC-PRD-002-15, TST-ACC-PRD-040]
    statement: >-
      Product creation commits its record, pricing, components and feature collections together or retains
      none of those writes.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#leaves
      nothing behind after a refusal
    origin: none
  - id: BR-CAP-ACC-PRD-002-12
    proved_by: [ATS-ACC-PRD-002-11, TST-ACC-PRD-040]
    statement: >-
      Products with older generated identities remain readable, referenceable and deletable, but replacement,
      retirement and reactivation fail.
    kind: lifecycle
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a replacement, 409, saying why, and leaves the product unchanged
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D12'
  - id: BR-CAP-ACC-PRD-002-13
    proved_by: [ATS-ACC-PRD-002-12, TST-ACC-PRD-029, TST-ACC-PRD-042]
    statement: Products support no custom field values and reject requests containing them.
    kind: validation
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products-shared-base.e2e.spec.js#refuses a body that
      names custom fields, 400 naming the property
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#Notes on the decisions:'
  - id: BR-CAP-ACC-PRD-002-14
    proved_by: [ATS-ACC-PRD-002-13, TST-ACC-PRD-028]
    statement: Product creation requires product create 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 creating a
      product
    origin: 'services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#D3'
  - id: BR-CAP-ACC-PRD-002-15
    proved_by: []
    statement: >-
      The console derives an editable Code from Name during creation, selects the list’s unit or the working
      unit, and returns to the list after successful creation.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: observed
    source: 'react-console:components/accounting-config/products/use-products.ts:newProductPath'
    origin: >-
      services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R10;
      services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R11;
      services-accounting:docs/milestones/ACCT-M1-books-and-invoices.md#R20
scenarios:
  - id: SC-CAP-ACC-PRD-002-01
    given: A chapter business unit uses US dollars.
    when: A controller creates Parking Permit with a 25000-minor-unit standard price.
    then: 'The saved product has a readable identity, active status, version one and a $250.00 price.'
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products.e2e.spec.js#creates it under the id it was
      given, in the tenant of the path, at version 1
  - id: SC-CAP-ACC-PRD-002-02
    given: A donation already uses the identity gala.
    when: An administrator creates an Accounting Product with that identity.
    then: The collision is rejected across product kinds.
    provenance: test
    source: >-
      services-accounting:modules/products/tests/e2e/products.e2e.spec.js#answers 400 to the same id under
      another type: a product id is unique across every type
edge_cases:
  - id: SC-CAP-ACC-PRD-002-03
    given: An older product retains its generated identity.
    when: An administrator submits a new name.
    then: The refusal explains that a readable-identity replacement product is needed.
    provenance: test
    source: >-
      services-accounting:modules/products/tests/integration/services/persistence/products.integration.spec.js#refuses
      a replacement, 409, saying why, and leaves the product unchanged
constraints: []
depends_on:
  - CAP-ACC-PRD-001
related:
  - CAP-ACC-PRD-004
  - CAP-ACC-PRD-008
  - CAP-ACC-PRD-011
journeys:
  - JNY-ACC-PRD-001
open_questions: []
evidence:
  - 'react-console:components/accounting-config/products/product-editor-page.tsx'
  - 'react-console:components/accounting-config/products/product-editor.tsx'
  - 'react-console:components/accounting-config/products/use-products.ts'
  - 'react-console:lib/accounting-config/product-form.ts'
  - 'services-accounting:docs/accounting/products.md'
  - '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/aws/api/products.schemas.ts'
  - 'services-accounting:modules/products/db/products.table.ts'
  - 'services-accounting:modules/products/services/persistence/product-refusals.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-shared-base.e2e.spec.js'
  - 'services-accounting:modules/products/tests/e2e/products.e2e.spec.js'
  - 'services-accounting:modules/products/tests/integration/db/products.table.integration.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'
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-01, FAS-ACC-PRD-01-02, FAS-ACC-PRD-01-04, FAS-ACC-PRD-02-01, FAS-ACC-PRD-04-04]
suites: [TST-ACC-PRD-015, TST-ACC-PRD-028, TST-ACC-PRD-029, TST-ACC-PRD-030, TST-ACC-PRD-031, TST-ACC-PRD-039, TST-ACC-PRD-040, TST-ACC-PRD-042]
---

# Create a product with a permanent identity

## Purpose

An administrator chooses a kind and business unit, then enters the product name.

## How It Works

An administrator chooses a kind and business unit, then enters the product name. The creation form proposes a readable Code that can be adjusted before saving. That reference remains fixed after creation. Pricing and selling settings are checked against the selected kind. The save also checks references and collection limits. Failure leaves the product’s own records unwritten. The console returns staff to their list after successful creation. Older records with generated identities require a replacement product if staff need to change them.

## Relationships

This outcome uses understand the available product kinds.
