Files
web_sport/docs/adr/0016-option-values-are-retained-when-variants-reference-them.md
2026-08-13 23:20:22 +07:00

72 lines
3.0 KiB
Markdown

# ADR-0016: Option values are retained when variants reference them
- **Status:** Accepted
- **Date:** 2026-08-12
## Context
A merchandiser removes the "Red" colourway from a product. What should happen to
the three Red variants and to the Red option value itself?
Three things reference them and each has a different claim:
- **Order history** — an order line points at a variant id, and reading that
order later needs "Red / M" to still mean something.
- **The variant matrix** — Red must stop generating combinations.
- **The storefront** — a shopper must not see a Red swatch that can never be
selected.
The database already takes a position: `ProductVariantOptionValue.optionValue`
is `onDelete: Restrict`, precisely so that deleting a value cannot silently
orphan or cascade into history.
## Decision
**Variants are archived, never deleted.** A removed combination sets
`status = ARCHIVED`. The row, its SKU and its option links all survive, so an
order placed last month still resolves.
**Option values are deleted only when nothing references them.** If any variant
— active or archived — still links to a value, the value is _retained_. It stops
appearing in the matrix and stops being offered, but it continues to exist so the
archived variants remain readable.
**The matrix is built from the operator's submitted option set, not from the
database.** Retained values would otherwise regenerate the very variants that
were just archived.
**The storefront filters option values to those offered by at least one active
variant.** Without this last step, a retained value renders as a permanently
disabled swatch with no explanation.
## Consequences
Order history stays intact and readable, which is the constraint that drove
everything else. Re-adding a removed colourway later reuses the retained value
and its translations rather than creating a duplicate.
The cost is that `product_option_values` accumulates rows that are invisible to
shoppers, and "delete" is not always literally a delete — a merchandiser who
inspects the database will find values they thought they removed. The schema
comment and this ADR are the mitigation; a future admin screen could surface
retained values explicitly.
This was found the hard way: the first implementation deleted values before
archiving variants and hit the `Restrict` constraint as a 500. The failure was
the schema doing its job.
## Alternatives considered
**Cascade the delete.** Removes the option value and its variant links, which
either breaks order lines or cascades into them. Rejected — this is the outcome
`Restrict` exists to prevent.
**Soft-delete option values with an `isActive` column.** Functionally similar to
retention, but adds a column and a filter to every catalog query for a case that
is already handled by "is any active variant offering this?". Rejected as
redundant state.
**Refuse to remove a value that has variants.** Simple and safe, but forces the
merchandiser to archive six variants by hand before they can drop a colourway —
pushing bookkeeping onto the person the tool exists to help.