docs(openspec): plan shop coupons, accounts, points, flash sales, and group buying
Capture exclusive promotion composition, empty-group cancel, and mall surface scope so the five marketing changes can be implemented without stacking or lifecycle ambiguity. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
e10cae5789
commit
53548196f8
+81
-31
@@ -15,41 +15,65 @@ mock data and the page's `~/mock/data` import in the same change.
|
||||
|
||||
---
|
||||
|
||||
## Planning report — 2026-09-18
|
||||
|
||||
Five independent, implementation-ready OpenSpec changes were created and validated strictly:
|
||||
|
||||
| Change | Business boundary | Prerequisite |
|
||||
| --- | --- | --- |
|
||||
| `add-shop-coupons` | Shop templates, customer claims, per-shop checkout redemption, and cancellation restoration | None |
|
||||
| `add-customer-accounts` | Monetary/points accounts, append-only ledger, and live account stats | None |
|
||||
| `add-points-mall` | Platform points catalog, atomic redemption, customer history, and admin fulfillment | `add-customer-accounts` must archive first |
|
||||
| `add-flash-sales` | Timed sessions, SKU activity inventory, limits, and checkout pricing | None |
|
||||
| `add-group-buying` | Activities, group lifecycle, paid membership, and payment-time capacity | None |
|
||||
|
||||
Each change has `proposal.md`, `design.md`, `specs/`, and `tasks.md` in
|
||||
`openspec/changes/<change>/`; all five passed `openspec change validate <change> --strict`.
|
||||
Their planning documents use generic B2B2C storefront terminology rather than a source-project
|
||||
name.
|
||||
|
||||
**Captured decisions:** coupons are template plus customer-owned snapshot and one per shop order;
|
||||
activity-priced shop orders (flash or group) reject coupons; a SKU cannot be in overlapping flash
|
||||
and group windows; account debits use guarded atomic updates with immutable entries; points
|
||||
redemption uses a separate order lifecycle and demo points are seed-credited through the ledger;
|
||||
flash sales use activity-reserved stock and server-calculated prices; group seats are claimed at
|
||||
payment rather than checkout; an unpaid opener cancel closes an empty group; catalog product-detail
|
||||
does not show flash or group prices in these changes.
|
||||
|
||||
**Recommended implementation order:** start `add-customer-accounts` and/or
|
||||
`add-shop-coupons`; archive accounts; then `add-points-mall`; implement `add-flash-sales` after
|
||||
the coupon checkout shape is settled; implement `add-group-buying` last because it expands the
|
||||
payment state machine. **Favorites** can be a separate OpenSpec change at any time (no order
|
||||
coupling). The deferred designs below intentionally have no change yet.
|
||||
|
||||
## Mock holdouts with a mall UI today
|
||||
|
||||
- [ ] **Coupons** — `user/coupons.vue` lists `MOCK_COUPONS`; `goods/[id].vue` shows a
|
||||
claim strip off the same fixture.
|
||||
Missing: `coupon_templates` (shop-issued: amount/threshold/window/stock), `coupons`
|
||||
(user-held, order-bound status). APIs: `GET /api/coupons` (mine),
|
||||
`POST /api/coupons/claim` (from a template), shop-admin template CRUD, and checkout
|
||||
application (select → discount minor → bind to order). Money math stays minor units.
|
||||
- [ ] **Coupons** — covered by in-progress `add-shop-coupons` (not yet applied). Mall still
|
||||
reads `MOCK_COUPONS` until that change is implemented.
|
||||
- [ ] **Favorites** — `user/favorites.vue` lists `MOCK_FAVORITES` (products tab +
|
||||
stores tab); `user/index.vue` derives counts from it.
|
||||
Missing: `favorites(user_id, product_id | shop_id)` with a partial unique index per
|
||||
target kind. APIs: `GET/POST/DELETE /api/favorites` (product & shop variants).
|
||||
- [ ] **Account stats** — `user/index.vue` shows `USER_STATS` (balance 128.00, points
|
||||
2680, frozen 0) and `integral.vue` reuses the points figure.
|
||||
Missing: balance/points accounts and ledgers (`money_logs` in the reference).
|
||||
MVP shape: `GET /api/me/stats` returning `{ balance_minor, points, frozen_minor }`;
|
||||
real ledgers only when a flow (recharge, refund-to-balance, points earn/spend) needs them.
|
||||
stores tab); `user/index.vue` derives counts from it; `goods/[id].vue` heart is local
|
||||
`ref(false)`. Ready for its own OpenSpec change whenever convenient: no checkout
|
||||
coupling. Schema: `favorites(user_id, product_id | shop_id)` with a partial unique
|
||||
index per target kind (see deferred design). APIs: `GET/POST/DELETE /api/favorites`.
|
||||
- [ ] **Account stats** — covered by in-progress `add-customer-accounts` (not yet applied).
|
||||
Mall still reads `USER_STATS` until that change lands. The change already includes
|
||||
append-only ledgers and guarded mutation; public entry listing stays out of scope.
|
||||
Frozen balance is a reserved kind and stays zero until a freeze flow exists.
|
||||
|
||||
## Marketing pages that are display-only mock
|
||||
|
||||
These exist as full pages (`seckill.vue`, `collective.vue`, `integral.vue`) linked from
|
||||
the home navigation; all three read fixtures directly.
|
||||
|
||||
- [ ] **Seckill (秒杀)** — `SECKILL_SESSIONS` + `seckillProducts()` (price override,
|
||||
sold %).
|
||||
Missing: `seckill_sessions`, `seckill_products` (activity price, isolated stock),
|
||||
`GET /api/seckill/sessions`, and checkout price resolution honouring the active session.
|
||||
- [ ] **Collective / 拼团** — `collectiveProducts()` (need/joined counts).
|
||||
Missing: `collective_activities`, `collective_groups` (open/join/expire, success on
|
||||
fill); orders bind to a group; refund/rollback policy on expiry.
|
||||
- [ ] **Integral mall / 积分商城** — `INTEGRAL_PRODUCTS` + points from `USER_STATS`.
|
||||
Missing: points ledger (earn/spend), `integral_products`, points-denominated checkout
|
||||
(`integral/orders` in the reference).
|
||||
- [ ] **Seckill (秒杀)** — covered by in-progress `add-flash-sales`. Page still reads
|
||||
`SECKILL_SESSIONS`. Catalog product-detail does not show flash prices in that change.
|
||||
- [ ] **Collective / 拼团** — covered by in-progress `add-group-buying`. Page still reads
|
||||
fixture counts. Paid seats at payment; unpaid opener cancel closes an empty group.
|
||||
- [ ] **Integral mall / 积分商城** — covered by in-progress `add-points-mall` after
|
||||
`add-customer-accounts` archives. Demo spendable points are seed-credited through the
|
||||
ledger; earning campaigns stay out of that change.
|
||||
|
||||
## Domains the reference has and this MVP does not (no UI here)
|
||||
## Domains typical B2B2C storefronts have and this MVP does not (no UI here)
|
||||
|
||||
Recorded so the gap is explicit, not because all of them belong in scope:
|
||||
|
||||
@@ -58,15 +82,41 @@ Recorded so the gap is explicit, not because all of them belong in scope:
|
||||
Missing: `order_comments` (order-item bound, rated, replyable), public read on product
|
||||
pages, shop reply, admin moderation. Writing/moderating/displaying reviews is a feature
|
||||
with its own lifecycle.
|
||||
- [ ] **Distribution / 分销**, **cashes / 提现**, **money logs** — qwshop user-center
|
||||
modules; no mock, no UI, no model here.
|
||||
- [ ] **Help center / articles** — nav links exist in the footer (`帮助中心`); no article
|
||||
model. Cheap version: static content pages; full version: admin-managed articles.
|
||||
- [ ] **OAuth login, SMS/captcha** — reference `users/oauth` + captcha plugin; here auth
|
||||
is email+password only.
|
||||
- [ ] **Distribution / 分销**, **cashes / 提现** — common B2B2C account modules; no mock,
|
||||
no UI, no model here. Customer-account freeze/credit is the intended foundation.
|
||||
Public money-log listing is not in `add-customer-accounts`. Deferred design notes below.
|
||||
- [ ] **Help center / articles** — header nav `shell.nav.help` currently links to `/user`;
|
||||
the footer has no help links. No article model. Cheap version: static locale pages;
|
||||
full version: admin-managed articles.
|
||||
- [ ] **OAuth login, SMS/captcha** — common storefront plugins; here auth is email+password only.
|
||||
- [ ] **Freight templates / 运费模板** — shop-side shipping-fee rules; checkout currently
|
||||
charges no shipping at all.
|
||||
|
||||
## Deferred designs from general B2B2C storefronts
|
||||
|
||||
These patterns are recorded for later product discovery only. They are not approved scope,
|
||||
have no OpenSpec change, and must be redesigned under VMall's minor-unit, JSONB, RBAC, and
|
||||
transaction rules before implementation.
|
||||
|
||||
- [ ] **Threshold reductions / 满减** — merchant rules need a shop scope, localized name,
|
||||
threshold and reduction in one currency, active window, and an explicit best-eligible rule.
|
||||
Coupon vs flash/group is already exclusive (reject coupon on activity-priced shop orders).
|
||||
Keep 满减 independent from coupons until a stacking policy with those shop coupons is specified.
|
||||
- [ ] **Favorites** — do not use a polymorphic `target_id`/type pair. Keep the existing
|
||||
product-or-shop design with separate nullable foreign keys, a target-kind check, and one
|
||||
partial unique index per target type so duplicate claims are impossible. This can be a
|
||||
dedicated OpenSpec change; it is the smallest remaining mall mock with a UI.
|
||||
- [ ] **Reviews / 评价** — a future review belongs to a fulfilled order item, not just a
|
||||
product. Preserve an immutable rating/content snapshot, allow one shop reply, and make
|
||||
public visibility and platform moderation explicit lifecycle states.
|
||||
- [ ] **Distribution / 分销** — a future merchant activity may configure per-product levels,
|
||||
but commissions must be created as order-item ledger entries and become payable only after
|
||||
the order reaches its chosen settlement condition. Do not use floating commission rates or
|
||||
mutate a balance without a ledger entry.
|
||||
- [ ] **Withdrawals / 提现** — model a payout request with account, amount minor, fee minor,
|
||||
destination snapshot, and `requested → approved | rejected | paid` transitions. Funds must
|
||||
be frozen and released or debited atomically with the transition.
|
||||
|
||||
## Deliberately out of scope — does not block deleting this file
|
||||
|
||||
- **The fixed-data adapter itself** (`apps/mall/mock/api.ts`, `~/mock/data`): the
|
||||
|
||||
Reference in New Issue
Block a user