feat(mall): run the transaction chain against the live API
Wave 3 of replacing the fixed-data mock adapter: cart, orders, shipments and invoices flip together, so one purchase runs end to end against the backend. - cart: CartItemView carries the line's shop and the SKU's stock, so the cart keeps grouping per shop and the quantity stepper caps at real stock instead of a hard-coded 999 - contract: Shipment.items is optional and Invoice.invoice_no nullable, both matching what the API actually returns. Invoice was declared twice in types.ts and TypeScript merges duplicate interfaces, so the duplicate had to go for the change to take effect at all - an anonymous add-to-cart redirects to /login?redirect=..., and sign-in honours only same-origin paths - the fixed-data adapter learns the new cart fields, and its persisted state key moves to v2 because a cart saved by an older build is no longer valid - order surfaces drop their storeById lookups and keep the generic store label until the public store read arrives Verified end to end: two-shop cart grouping with live shop names, stock caps read from the API, checkout, payment, shipment, delivery confirmation and an issued invoice. Rollback re-verified with every domain on fixed data and the backend stopped. Also checks off Wave 3 in docs/TBD-migrate-wave.md and re-points that file at the mock content that remains. OpenSpec change: openspec/changes/replace-mock-api-wave-3
This commit is contained in:
+20
-20
@@ -1,15 +1,15 @@
|
||||
# TBD — migrate the mall off the mock API (waves 3+)
|
||||
# TBD — migrate the mall off the mock API (waves 4+)
|
||||
|
||||
Waves 1 and 2 are captured in `openspec/changes/replace-mock-api-wave-1/` (catalog +
|
||||
currency) and `replace-mock-api-wave-2/` (auth). This file tracks what remains.
|
||||
Waves 1–3 are captured in `openspec/changes/replace-mock-api-wave-{1,2,3}/`: catalog +
|
||||
currency, auth, and the transaction chain (cart, orders, shipments, invoices). Every domain
|
||||
the mall had an API for is now live; what remains in Wave 4 is the mock content that never
|
||||
had a backend behind it.
|
||||
|
||||
**How to use:** check a box only once the behaviour is implemented *and* verified against
|
||||
the live backend (`cargo run -p vmall-api`, `node scripts/seed-demo.mjs`). The per-domain
|
||||
switch in `apps/mall/plugins/api.ts` means most of these are adding a domain name to the
|
||||
live list — each still needs its own verification below.
|
||||
the live backend (`cargo run -p vmall-api`, `node scripts/seed-demo.mjs`).
|
||||
|
||||
**Delete this file** once every box in Wave 3 is checked. Wave 4 is optional: if you decide
|
||||
against it, delete this file anyway and record that decision wherever you like.
|
||||
**Delete this file** once every Wave 4 box is checked, or consciously dropped and recorded.
|
||||
The "deliberately out of scope" list at the bottom does not block deleting it.
|
||||
|
||||
---
|
||||
|
||||
@@ -33,20 +33,20 @@ leaves the mock half reading state the live half never populates:
|
||||
- mock `listMyShipments()` returns shipments whose `order_id` matches no live order, so the
|
||||
shipment block is silently empty (`mock/api.ts:282`, `pages/user/orders/[id].vue:25`).
|
||||
|
||||
- [ ] Flip `cart`, `orders`, `shipments` and `invoices` in the same change, then verify one purchase end to end: add to cart, check out into per-shop orders, pay, ship, confirm delivery, request an invoice.
|
||||
- [ ] Verify cancel restores stock and `payOrder` only accepts `pending_payment` (both enforced live; the mock only mimicked them).
|
||||
- [ ] Verify a company invoice requires a tax number, and that one order can hold only one active invoice.
|
||||
- [ ] Remove cart's mock display coupling: `apps/mall/pages/cart.vue:4,34,41,56,60` uses `productById` / `storeById` for shop name, image and stock. The live `CartView` already returns `product_name`, `image` and `unit_price_minor` (`apps/api/src/cart.rs:44-53`) — but **not** the shop, so decide whether to add a shop field to `CartItem` in the shared contract or accept ungrouped lines until the public store read exists.
|
||||
- [ ] Decide what caps cart quantity: `CartItem` carries no `stock`, so `maxFor` falls back to 999 (`pages/cart.vue:60`). The live cart does not enforce stock either — only checkout does (409). Either add `stock` to `CartItem` or keep the cap at checkout and say so.
|
||||
- [ ] Gate add-to-cart for anonymous shoppers: a live `addCartItem` on the public product page returns 401, and `pages/goods/[id].vue` is not behind the auth middleware.
|
||||
- [ ] Checkout keeps sourcing `shipping_address` from `MOCK_ADDRESSES` (`pages/checkout/index.vue:4,23,37,127`) — intentional; see the out-of-scope note below.
|
||||
- [ ] Fix contract debt so the TS types stop lying: `Shipment.items` is required in `packages/shared/src/types.ts` but the live struct has no `items` field (`apps/api/src/models.rs:172-182`), and `Invoice.invoice_no` is nullable live (`models.rs:194`) but non-null `string` in TS.
|
||||
- [ ] Remove the remaining `storeById` mock usage on the order pages (`apps/mall/pages/user/orders/index.vue:4`).
|
||||
- [ ] Confirm the mall still renders when the live API is down, with every domain configured to fixed data.
|
||||
- [x] Flip `cart`, `orders`, `shipments` and `invoices` in the same change, then verify one purchase end to end: add to cart, check out into per-shop orders, pay, ship, confirm delivery, request an invoice. Done in `replace-mock-api-wave-3`; the merchant half was driven through the API because the mall has no merchant UI.
|
||||
- [x] Verify cancel restores stock and `payOrder` only accepts `pending_payment`. Cancel and stock restore are asserted by `cancel_rules_and_stock_restore`; `pay_order` is a status-guarded `UPDATE ... AND status = 'pending_payment'` that answers 409 otherwise (`apps/api/src/routes/orders.rs:279-287`).
|
||||
- [x] Verify a company invoice requires a tax number, and that one order can hold only one active invoice. Asserted by `invoice_lifecycle` (400 without a tax number, 409 on the second invoice).
|
||||
- [x] Remove cart's mock display coupling. `CartItemView` now carries `shop_id`, `shop_name` and `stock` (`apps/api/src/cart.rs`), and `pages/cart.vue` groups by them.
|
||||
- [x] Decide what caps cart quantity. `CartItem.stock` is exposed and the stepper caps at it, but the API deliberately does not check stock on add — checkout stays authoritative with its 409.
|
||||
- [x] Gate add-to-cart for anonymous shoppers: `pages/goods/[id].vue` sends a 401 to `/login?redirect=…`, and `pages/login.vue` honours only same-origin paths.
|
||||
- [x] Checkout keeps sourcing `shipping_address` from `MOCK_ADDRESSES` — intentional; see the out-of-scope note below.
|
||||
- [x] Fix contract debt: `Shipment.items` is optional and `Invoice.invoice_no` is nullable, matching what the API returns. `Invoice` was declared twice in `packages/shared/src/types.ts` and TypeScript merges duplicate interfaces, so the duplicate had to go for the change to take effect.
|
||||
- [x] Remove the remaining `storeById` mock usage on the order pages. They animate the generic store label instead; the real names need the public store read below.
|
||||
- [x] Confirm the mall still renders when the live API is down, with every domain configured to fixed data.
|
||||
|
||||
## Wave 4 — optional, mostly new backend capabilities
|
||||
## Wave 4 — the mock content that never had an API
|
||||
|
||||
Only if you want more of the storefront backed by real data. Each is a new capability, not a flip.
|
||||
Each is a new backend capability rather than a domain flip.
|
||||
|
||||
- [ ] **Storefront content** — banners, promos, quick links and floor advert art. Needs real tables, admin CRUD and i18n JSONB. Do this first if you want the home page fully live; it is the most visible remaining mock surface.
|
||||
- [ ] **Public store read** — a buyer-facing shop endpoint so `stores/index`, `stores/[id]` and the cart's shop grouping leave mock. Small: products already carry `shop_id`, and the public catalog already joins shops for the active check.
|
||||
|
||||
Reference in New Issue
Block a user