---
id: CAP-COM-CAT-005
title: Organize product shelves
feature: FEAT-COM-CAT-02
product_area: commerce
module: catalog
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-commerce:docs/milestones/plans/COMM-M1-plan.md#2. Scope (item 3)'
  - >-
    services-commerce:docs/milestones/COMM-M1-set-up-what-you-sell.md#D-REC-2
    (to confirm)
purpose: >-
  Administrators group products into browsable categories used by store
  assortments.
outcomes:
  - Shelves organize items around shopper browsing needs.
  - A shelf a store depends on is does not deleted from under it.
concepts:
  - CON-COM-CAT-003
actors:
  - ACT-COM-001
  - ACT-COM-004
surfaces:
  - console
  - api
permissions:
  - key: commerce.product-categories.read
    where_checked: api
    scope: tenant
  - key: commerce.product-categories.create
    where_checked: api
    scope: tenant
  - key: commerce.product-categories.update
    where_checked: api
    scope: tenant
  - key: commerce.product-categories.delete
    where_checked: api
    scope: tenant
  - key: commerce.product-categories.bulk
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-COM-CAT-005-01
    proved_by: []
    statement: >-
      A category retains its supplied or name-derived identity, and another
      category cannot take that identity.
    kind: validation
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
      a second category under the same id (400)
    origin: none
  - id: BR-CAP-COM-CAT-005-02
    proved_by: [ATS-COM-CAT-005-01, TST-COM-CAT-014, TST-COM-CAT-017]
    statement: >-
      Categories form a flat collection, with parent relationships reserved and
      refused.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers
      400 to a parent_id: the hierarchy is reserved
    origin: none
  - id: BR-CAP-COM-CAT-005-03
    proved_by: [ATS-COM-CAT-005-01, TST-COM-CAT-014, TST-COM-CAT-017]
    statement: A category accepts only a supported catalog sort as its default.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers
      400 to a default sort outside the catalog sorts, naming the property
    origin: none
  - id: BR-CAP-COM-CAT-005-04
    proved_by: [ATS-COM-CAT-005-02, ATS-COM-CAT-005-09, ATS-COM-CAT-005-10, ATS-COM-CAT-006-12, ATS-COM-CAT-008-07, TST-COM-CAT-019]
    statement: >-
      Category membership replacement preserves the supplied order without
      duplicate products; assigning a product’s categories appends new
      memberships and removes omitted ones.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#refuses
      a list naming one product twice (400) and an unknown category (404)
    origin: none
  - id: BR-CAP-COM-CAT-005-05
    proved_by: [ATS-COM-CAT-003-11, ATS-COM-CAT-005-03, ATS-COM-CAT-005-08, TST-COM-CAT-014, TST-COM-CAT-019]
    statement: >-
      A store’s home or assortment reference prevents category deletion, with
      the refusal reporting the number of dependent stores and up to five names.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
      refused while stores name it in their assortment, naming five of them and
      the count
    origin: none
    note: >-
      The category-dependency recommendation remains pending Product
      confirmation.
  - id: BR-CAP-COM-CAT-005-06
    proved_by: []
    statement: >-
      Category deletion removes its memberships and detaches its image while
      retaining the products.
    kind: lifecycle
    enforced_at:
      - service
      - database
    must_prove: false
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#deletes
      the category with its memberships and detaches its picture; a second
      delete is 404
    origin: none
  - id: BR-CAP-COM-CAT-005-07
    proved_by: [ATS-COM-CAT-003-12, ATS-COM-CAT-003-17, ATS-COM-CAT-005-04, ATS-COM-CAT-005-11, ATS-COM-CAT-005-12, ATS-COM-CAT-005-13, ATS-COM-CAT-006-19, TST-COM-CAT-014, TST-COM-CAT-019]
    statement: >-
      The shared asset pipeline must read a category image as a file and retain
      it in the category’s permanent storage location.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#answers
      400 to a picture the pipeline cannot read as a file, creating nothing
    origin: COMM-M1 D11 (docs/milestones/COMM-M1-set-up-what-you-sell.md)
  - id: BR-CAP-COM-CAT-005-08
    proved_by: [ATS-COM-CAT-005-05, ATS-COM-CAT-005-12, TST-COM-CAT-014]
    statement: >-
      A complete category replacement uses the read version and refuses an
      intervening version change.
    kind: invariant
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/product-categories.e2e.spec.js#replaces
      at the version read and refuses the stale version 409
    origin: none
  - id: BR-CAP-COM-CAT-005-09
    proved_by: [ATS-COM-CAT-005-06, TST-COM-CAT-011]
    statement: >-
      Category reads require read permission and all category writes require the
      matching administrator permissions.
    kind: permission
    enforced_at:
      - api
      - console
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/e2e/catalog-security.e2e.spec.js#refuses
      a read-only principal every write, 403 permission_missing
    origin: none
  - id: BR-CAP-COM-CAT-005-10
    proved_by: [ATS-COM-CAT-005-07, ATS-COM-CAT-005-12]
    statement: >-
      A full lock prevents replacing a category, and either full or
      deletion-only locking prevents removing it.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-commerce:modules/catalog/services/persistence/product-categories.ts
    origin: none
scenarios:
  - id: SC-CAP-COM-CAT-005-01
    given: Bookstore includes Books in its assortment
    when: Delete Books before and after moving the store’s reference
    then: >-
      The dependency refusal identifies Bookstore; deletion succeeds after the
      reference is removed
    provenance: test
    source: >-
      services-commerce:modules/catalog/tests/integration/services/persistence/product-categories.integration.spec.js#is
      refused 409 category_in_use while a store has it as its home category,
      naming the store, and deleted once the store moves
depends_on: []
related:
  - CAP-COM-CAT-003
  - CAP-COM-CAT-006
  - CAP-COM-CAT-008
journeys:
  - JNY-COM-CAT-001
evidence:
  - >-
    Pinned service inputs: modules/catalog/aws/api/product-categories.routes.ts
    and .schemas.ts; services/persistence/product-categories.ts;
    db/product-categories.table.ts, product-category-products.table.ts,
    catalog-sorts.ts; tests (categories e2e, persistence, queries, table,
    assets)
  - >-
    Pinned console inputs:
    components/configuration/resources/product-categories.tsx;
    lib/commerce/catalog/__tests__/categories.test.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-CRY-001, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-PI-005, TC-PRV-001, TC-PRV-002]
exercised_by: [FAS-COM-CAT-02-02, FAS-COM-CAT-02-04]
suites: [TST-COM-CAT-011, TST-COM-CAT-014, TST-COM-CAT-017, TST-COM-CAT-019]
---

# Organize product shelves

## Purpose

Administrators group products into browsable categories used by store assortments.

## How It Works

Staff create categories and arrange their products in a deliberate order. A category carries its own presentation and sort choices. Products can also be assigned to shelves from their listing work. Saving membership replaces the selected collection and detects repeated products. Store references can prevent removal of a shelf customers still offer. Removing an unused shelf clears its memberships while retaining the products themselves. Image preparation and stale-version failures leave staff with an explicit reason to correct their request.

## Relationships

The configure a store’s offered assortment capability supplies related configuration or assessment used in this work.

The prepare and publish product presentations capability supplies related configuration or assessment used in this work.

The present the store’s available products capability supplies related configuration or assessment used in this work.
