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.
3.3 KiB
3.3 KiB
Tasks
1. Schema and seed
- 1.1 Add a migration creating
brands(bilingual name, slug, position, active) andproducts.brand_idnullable withON DELETE SET NULL; verify the column and table exist aftercargo 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 andGET /api/brandsreturns them in order
2. Shared contract
- 2.1 Add
BrandandBrandInputtopackages/shared/src/types.ts, add the optionalbrand_idto the product payload andProductUpsertBody, and addbrand_idtoProductListQueryplus"sales"to itssort; verify all three frontends build - 2.2 Add
listBrands(),admin.getBrands()/admin.replaceBrands(list)to theApiClientandcreateApi, and give the fixed-data adapter matching implementations so the rollback path still serves a brand list and a brand filter; register abrandsdomain in the per-domain switch
3. Catalog: brands
- 3.1 Add
GET /api/brands(public, position-ordered) andPUT /api/admin/brands(admin, transactional replace, validating slug and non-emptyen/zh); verify a rejected list changes nothing and a non-admin is refused - 3.2 Add the
brand_idfilter tolistProducts, composing with the category, shop and keyword filters, and returnbrand_idon the product payload; verify a brand plus category filter narrows correctly - 3.3 Accept
brand_idin 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_countper product fromorder_itemsjoined 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=saleswithorder=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.rswith brand filtering, the sales order and the unpaid-order exclusion; verifycargo test -p vmall-apiis green and repeatable
5. Mall surfaces
- 5.1
pages/search.vue: restore the brand facet fromlistBrands()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 realsold_countand 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, sincenuxt builddoes not typecheck (recorded indocs/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