Files
vmall/openspec/changes/replace-mock-api-wave-3/tasks.md
T
james e1a0a5dbdb 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
2026-09-17 16:33:22 +00:00

35 lines
3.9 KiB
Markdown

# Tasks
## 1. Contract and backend cart view
- [x] 1.1 Add `shop_id`, `shop_name` and `stock` to `CartItemView` and to the `cart_view` SELECT in `apps/api/src/cart.rs` (the query already joins `products`, `shops` and `skus`); verified `GET /api/cart` returns a distinct shop and the SKU's stock per line for a two-shop cart
- [x] 1.2 Add `shop_id`, `shop_name` and `stock` to `CartItem` in `packages/shared/src/types.ts`. Also had to teach the fixed-data adapter to construct the new fields (`apps/mall/mock/api.ts`) and bump its persisted-state key to `v2`, because a cart saved by an older build is no longer a valid `CartItem[]`; verified the mall, shop-admin and admin builds all pass
- [x] 1.3 Make `Shipment.items` optional and `Invoice.invoice_no` nullable in `packages/shared/src/types.ts`. `Invoice` was declared twice in that file and TypeScript merges duplicate interfaces, so the duplicate was removed for the change to take effect at all; verified against a live shipment (no `items` key) and a requested invoice (`invoice_no: null`)
- [x] 1.4 Extend the two-shop cart test in `apps/api/tests/orders.rs` to assert each line reports its own shop, a bilingual shop name and the SKU stock; verified `cargo test -p vmall-api --test orders` is green
## 2. Anonymous add-to-cart gate
- [x] 2.1 Send a signed-out shopper to `/login?redirect=<current path>` when `addCartItem` answers 401, accepting only same-origin paths; verified the parameter is dropped for `//host` and absolute URLs
- [x] 2.2 Have `pages/login.vue` return the shopper to a valid `redirect` target after a successful sign-in; verified the round trip from a product page
## 3. Cart surface
- [x] 3.1 Group `pages/cart.vue` by the line's own `shop_id` and label each group with `shop_name`, dropping the `productById`/`storeById` lookups; verified a two-shop cart renders two groups labelled with the live names (`Aurora Digital`, `Demo Store`)
- [x] 3.2 Cap the quantity stepper with the line's `stock` instead of the 999 fallback, and use the line's `image` for the thumbnail; verified the caps read `12` and `25` from the live cart rather than 999
## 4. Transaction surfaces
- [x] 4.1 `pages/checkout/index.vue`: group by the cart line's shop and image rather than the mock catalog, keeping `MOCK_ADDRESSES` as the address source; verified the checkout preview groups correctly and the order submits
- [x] 4.2 `pages/checkout/pay.vue` and `pages/user/orders/index.vue`: drop the `storeById` lookups and keep the existing generic store label until Wave 4; verified order cards render with the placeholder and without errors
- [x] 4.3 `pages/user/invoices.vue`: render a placeholder when `invoice_no` is null, mirroring the existing `order_no` handling; verified the invoices table shows `—` for a requested-but-unissued invoice alongside real `INV…` numbers
## 5. Flip the domains
- [x] 5.1 Add `cart`, `orders`, `shipments` and `invoices` to the default `liveDomains` in `apps/mall/nuxt.config.ts` in one commit with the page changes above; verified every one of those pages now reads from `:8080`
## 6. Verification
- [x] 6.1 Run `pnpm --filter @vmall/mall build` plus the shop-admin and admin builds, since the shared contract changed; all three pass
- [x] 6.2 With the backend seeded, ran one purchase in a browser: signed in, added from a product page, saw a two-shop cart group correctly, checked out, paid, then confirmed the order. The merchant half of the chain (ship → issue invoice) has no mall UI and was driven through the API, after which the order page showed the live shipment, confirming delivery moved the order to `completed`, and the invoices page listed the issued numbers. The only console entries were the header's signed-out `GET /api/cart` 401 (caught, count 0) and no hydration warnings
- [x] 6.3 Verified the rollback: with every domain set to fixed data and the backend stopped, sign-in, add-to-cart, cart grouping, the stock cap and checkout all still work from localStorage