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,35 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Schema and seed
|
||||
|
||||
- [ ] 1.1 Add a migration creating `brands` (bilingual name, slug, position, active) and `products.brand_id` nullable with `ON DELETE SET NULL`; verify the column and table exist after `cargo run -p vmall-api`
|
||||
- [ ] 1.2 Seed the six demo brands and assign them to the demo products from `scripts/seed-demo.mjs`; verify a re-run is idempotent and `GET /api/brands` returns them in order
|
||||
|
||||
## 2. Shared contract
|
||||
|
||||
- [ ] 2.1 Add `Brand` and `BrandInput` to `packages/shared/src/types.ts`, add the optional `brand_id` to the product payload and `ProductUpsertBody`, and add `brand_id` to `ProductListQuery` plus `"sales"` to its `sort`; verify all three frontends build
|
||||
- [ ] 2.2 Add `listBrands()`, `admin.getBrands()` / `admin.replaceBrands(list)` to the `ApiClient` and `createApi`, and give the fixed-data adapter matching implementations so the rollback path still serves a brand list and a brand filter; register a `brands` domain in the per-domain switch
|
||||
|
||||
## 3. Catalog: brands
|
||||
|
||||
- [ ] 3.1 Add `GET /api/brands` (public, position-ordered) and `PUT /api/admin/brands` (admin, transactional replace, validating slug and non-empty `en`/`zh`); verify a rejected list changes nothing and a non-admin is refused
|
||||
- [ ] 3.2 Add the `brand_id` filter to `listProducts`, composing with the category, shop and keyword filters, and return `brand_id` on the product payload; verify a brand plus category filter narrows correctly
|
||||
- [ ] 3.3 Accept `brand_id` in the shop product upsert so a merchant, and the seed, can set it; verify a merchant can set and clear it on their own product only
|
||||
|
||||
## 4. Catalog: real sales
|
||||
|
||||
- [ ] 4.1 Compute `sold_count` per product from `order_items` joined to orders that reached payment (`paid`, `fulfilling`, `shipped`, `completed`), exposed on the product list and detail payloads; verify a product with no paid orders reports zero
|
||||
- [ ] 4.2 Accept `sort=sales` with `order=asc|desc`, keeping the 400 for any other sort value; verify descending order matches the reported counts and that an unpaid order does not move a product
|
||||
- [ ] 4.3 Extend `apps/api/tests/catalog.rs` with brand filtering, the sales order and the unpaid-order exclusion; verify `cargo test -p vmall-api` is green and repeatable
|
||||
|
||||
## 5. Mall surfaces
|
||||
|
||||
- [ ] 5.1 `pages/search.vue`: restore the brand facet from `listBrands()` and add the sales sort; verify the facet renders only when brands exist and that both filters compose
|
||||
- [ ] 5.2 `components/ui/ProductCard.vue`: show the product's real `sold_count` and drop the review figure; verify no fabricated number remains
|
||||
- [ ] 5.3 `pages/goods/[id].vue`: show the real sold count, and remove the reviews tab, its summary and reply blocks along with their mock imports; verify the page renders detail and after-sale tabs only
|
||||
|
||||
## 6. Verification
|
||||
|
||||
- [ ] 6.1 Run all three frontend builds and `cargo test -p vmall-api`; verify green. Treat the browser check as the real gate, since `nuxt build` does not typecheck (recorded in `docs/TBD-migrate-wave.md`)
|
||||
- [ ] 6.2 With the backend seeded, verify in a browser: the search page filters by brand and sorts by sales, a product card shows a real sold count, and the product page has no reviews while keeping its after-sale copy
|
||||
- [ ] 6.3 Verify the rollback: with every domain on fixed data and the backend stopped, the search facet and sorts still work from the fixed-data brand list
|
||||
Reference in New Issue
Block a user