docs(openspec): plan wave 6, brands and real sales
Planning only: proposal, delta specs, design and tasks. No code yet. This is the last substantive in-scope box in docs/TBD-migrate-wave.md. Wave 1 removed the brand facet and the sales/comments sorts for want of a model; sales are derivable from order_items and a brand model is a table plus a column, so both come back for real. Decisions recorded in the design: - brands are reference data in their own table with a nullable products.brand_id and an ordered replace endpoint, mirroring categories and storefront content - a "sale" is a unit on an order that reached payment; pending and cancelled orders do not count, so an abandoned checkout cannot inflate the figure - sold_count is computed per read rather than stored, so it cannot drift from the orders that produced it - the review UI is removed rather than relabelled: the mall attributes invented comments to named shoppers and shows a "good rate", which a migration that makes everything else real has no business keeping on screen. Reviews are recorded as a separate future capability openspec validate --strict passes and the change is ready to apply.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Spec Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
The product brand registry: a bilingual, admin-managed list of manufacturers that products reference, shoppers filter listings by, and the storefront shows on a product.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Public brand list
|
||||
`GET /api/brands` SHALL return the brands without authentication, ordered by their stored position, each with an id, slug and bilingual name. Products SHALL carry a nullable `brand_id` referring to that list, and `GET /api/products` SHALL accept a `brand_id` filter that composes with the existing category, shop and keyword filters.
|
||||
|
||||
#### Scenario: filter a listing by brand
|
||||
- **WHEN** a shopper filters the catalog by a brand
|
||||
- **THEN** only that brand's published products are returned, and the filter combines with a category filter rather than replacing it
|
||||
|
||||
#### Scenario: a product without a brand
|
||||
- **WHEN** a product has no brand assigned
|
||||
- **THEN** it is still listed and its `brand_id` is null rather than pointing at an invented brand
|
||||
|
||||
### Requirement: Brand management
|
||||
A platform admin SHALL replace the brand list with `PUT /api/admin/brands`, which validates each entry, applies as one transaction and reindexes positions from the submitted order; the write SHALL require the `platform_admin` role. Each entry SHALL carry a slug and non-empty `en` and `zh` names, and a rejected list SHALL leave the stored brands untouched.
|
||||
|
||||
#### Scenario: replace round-trips
|
||||
- **WHEN** an admin replaces the brands and then reads them publicly
|
||||
- **THEN** the public list matches, in the submitted order
|
||||
|
||||
#### Scenario: incomplete bilingual name is refused
|
||||
- **WHEN** an admin submits a brand with only `en` text
|
||||
- **THEN** the request fails and the stored brands are unchanged
|
||||
Reference in New Issue
Block a user