The merge creates the brand capability (two requirements) and extends the catalog browse requirement with the brand filter, the sales sort and a real sold count. openspec validate --all --strict stays green at 14 specs. With every box now checked, docs/TBD-migrate-wave.md is deleted, as its own instructions require. Its two durable notes move to the README instead of disappearing with it: - `nuxt build` does not typecheck, so a green build is not the type gate the verification section claimed. AGENTS.md told me to treat it as one, which is why waves 1-5 over-reported "builds pass" as evidence. - the list of what stays deliberately mock: addresses, coupons, favourites, account stats, the seckill/collective/integral pages, reviews, and the fixed-data adapter itself, which the adapter spec requires for rollback. The README's verification block also gains the real test count (29, not 16).
37 lines
4.1 KiB
Markdown
37 lines
4.1 KiB
Markdown
# Tasks
|
|
|
|
## 1. Schema and seed
|
|
|
|
- [x] 1.1 Add a migration creating `brands` (bilingual name, slug, position, active) and `products.brand_id` nullable with `ON DELETE SET NULL`; verified the table and column exist after `cargo run -p vmall-api`
|
|
- [x] 1.2 Seed the six demo brands and assign them to the demo products; verified a re-run is idempotent and all 24 products end up with a brand. The seed re-applies each product body on the 409 path so a re-run converges the assignment, and the brand list is captured in a local const — reading it back through the shared `r` variable worked once and then broke, because the product loop reassigns it
|
|
|
|
## 2. Shared contract
|
|
|
|
- [x] 2.1 Add `Brand` and `BrandInput`, the optional `brand_id` on the product payload and `ProductUpsertBody`, `brand_id` on `ProductListQuery`, `"sales"` on its `sort`, and `sold_count` on `Product`; verified all three frontends build
|
|
- [x] 2.2 Add `listBrands()`, `admin.getBrands()` / `admin.replaceBrands(list)` and fixed-data implementations, and register a `brands` domain in the per-domain switch. Also taught the fixed-data `listProducts` to pass `brand_id`, `sort` and `order` through — it had silently ignored all three, so the restored facet rendered but filtered nothing until the rollback check caught it
|
|
|
|
## 3. Catalog: brands
|
|
|
|
- [x] 3.1 Add public `GET /api/brands` and admin `PUT /api/admin/brands` (transactional replace, slug and bilingual-name validation); verified a non-admin is refused and a duplicate slug is a 400
|
|
- [x] 3.2 Add the `brand_id` filter to `listProducts`, composing with the other filters, and return `brand_id` on the payload; verified a brand filter narrows 24 products to 6 and composes with the shop filter
|
|
- [x] 3.3 Accept `brand_id` in the shop product upsert, on both create and update, so a merchant — and the seed — can set or clear it
|
|
|
|
## 4. Catalog: real sales
|
|
|
|
- [x] 4.1 Compute `sold_count` per product from `order_items` joined to orders in `paid`, `fulfilling`, `shipped` or `completed`, exposed on the list and detail payloads; verified a product with no paid orders reports zero
|
|
- [x] 4.2 Accept `sort=sales` with `order`, keeping the 400 for any other value; verified the order matches the reported counts and that a `pending_payment` order moves nothing
|
|
- [x] 4.3 Extend `apps/api/tests/catalog.rs` with `brand_filter_and_real_sales`, covering the brand filter, the unpaid exclusion, the paid count and the sales order; verified `cargo test -p vmall-api` is green at 29 tests and repeatable
|
|
|
|
## 5. Mall surfaces
|
|
|
|
- [x] 5.1 `pages/search.vue`: restored the brand facet from `listBrands()` and the sales sort; verified the facet renders only when brands exist and that brand plus category compose
|
|
- [x] 5.2 `components/ui/ProductCard.vue`: shows the product's real `sold_count` and no review figure; verified the card reads "N sold" only
|
|
- [x] 5.3 `pages/goods/[id].vue`: shows the real sold count, and the reviews tab, its summary and reply blocks are gone; verified the page offers only the detail and after-sale tabs, keeps the store card, and still shows the shop's after-sale copy
|
|
- [x] 5.4 Removed the now-unreferenced fabrication cluster from `apps/mall/mock/data.ts` — `salesOf`, `commentCountOf`, `commentsFor`, `commentStats`, `salesRankFor`, `productDetail`, `storeDetail` and their types. Nothing imported them once the review UI went, and the fixed-data sales sort now orders by `sold_count`, which is zero there rather than an invented number
|
|
|
|
## 6. Verification
|
|
|
|
- [x] 6.1 All three frontends build and `cargo test -p vmall-api` is green at 29 tests. The browser check remains the real gate, since `nuxt build` does not typecheck
|
|
- [x] 6.2 Verified live in a browser: the search page shows the brand facet and a Sales sort, filtering by a brand narrows 24 products to 6, descending sales puts the sold products first with counts matching the API, product cards show only a real sold count, and the product page has no reviews while keeping its after-sale copy and store card
|
|
- [x] 6.3 Verified the rollback: with every domain on fixed data and the backend stopped, the facet renders the fixed-data brands and filtering by one narrows 24 products to 2
|