From c93bff28b07042466bb7bf70b66e4a2b878cf689 Mon Sep 17 00:00:00 2001 From: Zhang Chengdong Date: Fri, 18 Sep 2026 12:53:26 +0000 Subject: [PATCH] chore(openspec): archive add-flash-sales Sync the flash-sales capability plus the order, frontend-mall, and frontend-shop-admin requirements into the main specs, and write the capability Purpose the archive leaves as a placeholder. Restate the flash-price and coupon-exclusion scenarios in the group-buying order delta, since its MODIFIED checkout block must carry the scenarios the main order spec now has. --- .../add-group-buying/specs/order/spec.md | 8 +++++ .../.openspec.yaml | 0 .../2026-09-18-add-flash-sales}/design.md | 0 .../2026-09-18-add-flash-sales}/proposal.md | 0 .../specs/flash-sales/spec.md | 0 .../specs/frontend-mall/spec.md | 0 .../specs/frontend-shop-admin/spec.md | 0 .../specs/order/spec.md | 0 .../2026-09-18-add-flash-sales}/tasks.md | 12 +++---- openspec/specs/flash-sales/spec.md | 31 +++++++++++++++++++ openspec/specs/frontend-mall/spec.md | 15 +++++++++ openspec/specs/frontend-shop-admin/spec.md | 11 +++++++ openspec/specs/order/spec.md | 20 ++++++++---- 13 files changed, 85 insertions(+), 12 deletions(-) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/.openspec.yaml (100%) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/design.md (100%) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/proposal.md (100%) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/specs/flash-sales/spec.md (100%) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/specs/frontend-mall/spec.md (100%) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/specs/frontend-shop-admin/spec.md (100%) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/specs/order/spec.md (100%) rename openspec/changes/{add-flash-sales => archive/2026-09-18-add-flash-sales}/tasks.md (80%) create mode 100644 openspec/specs/flash-sales/spec.md diff --git a/openspec/changes/add-group-buying/specs/order/spec.md b/openspec/changes/add-group-buying/specs/order/spec.md index da0b001..f854e72 100644 --- a/openspec/changes/add-group-buying/specs/order/spec.md +++ b/openspec/changes/add-group-buying/specs/order/spec.md @@ -11,6 +11,14 @@ - **WHEN** any line's qty exceeds SKU stock or applicable flash-sale stock - **THEN** the whole checkout returns 409 and no order is created and stock is unchanged +#### Scenario: active flash price applied +- **WHEN** checkout contains a SKU with an active eligible flash-sale item and no group-buy intent applies to it +- **THEN** the order item records the server-calculated flash-sale price and activity identity + +#### Scenario: coupon rejected on a flash-priced shop order +- **WHEN** the customer selects a coupon for a shop whose generated order applied flash-sale pricing on any line +- **THEN** checkout returns 409 and creates no orders + #### Scenario: open a group at checkout - **WHEN** a customer checks out one eligible group-buying SKU at quantity 1 with open-group intent - **THEN** a pending-payment order and an open group identity are created with the server-resolved group price diff --git a/openspec/changes/add-flash-sales/.openspec.yaml b/openspec/changes/archive/2026-09-18-add-flash-sales/.openspec.yaml similarity index 100% rename from openspec/changes/add-flash-sales/.openspec.yaml rename to openspec/changes/archive/2026-09-18-add-flash-sales/.openspec.yaml diff --git a/openspec/changes/add-flash-sales/design.md b/openspec/changes/archive/2026-09-18-add-flash-sales/design.md similarity index 100% rename from openspec/changes/add-flash-sales/design.md rename to openspec/changes/archive/2026-09-18-add-flash-sales/design.md diff --git a/openspec/changes/add-flash-sales/proposal.md b/openspec/changes/archive/2026-09-18-add-flash-sales/proposal.md similarity index 100% rename from openspec/changes/add-flash-sales/proposal.md rename to openspec/changes/archive/2026-09-18-add-flash-sales/proposal.md diff --git a/openspec/changes/add-flash-sales/specs/flash-sales/spec.md b/openspec/changes/archive/2026-09-18-add-flash-sales/specs/flash-sales/spec.md similarity index 100% rename from openspec/changes/add-flash-sales/specs/flash-sales/spec.md rename to openspec/changes/archive/2026-09-18-add-flash-sales/specs/flash-sales/spec.md diff --git a/openspec/changes/add-flash-sales/specs/frontend-mall/spec.md b/openspec/changes/archive/2026-09-18-add-flash-sales/specs/frontend-mall/spec.md similarity index 100% rename from openspec/changes/add-flash-sales/specs/frontend-mall/spec.md rename to openspec/changes/archive/2026-09-18-add-flash-sales/specs/frontend-mall/spec.md diff --git a/openspec/changes/add-flash-sales/specs/frontend-shop-admin/spec.md b/openspec/changes/archive/2026-09-18-add-flash-sales/specs/frontend-shop-admin/spec.md similarity index 100% rename from openspec/changes/add-flash-sales/specs/frontend-shop-admin/spec.md rename to openspec/changes/archive/2026-09-18-add-flash-sales/specs/frontend-shop-admin/spec.md diff --git a/openspec/changes/add-flash-sales/specs/order/spec.md b/openspec/changes/archive/2026-09-18-add-flash-sales/specs/order/spec.md similarity index 100% rename from openspec/changes/add-flash-sales/specs/order/spec.md rename to openspec/changes/archive/2026-09-18-add-flash-sales/specs/order/spec.md diff --git a/openspec/changes/add-flash-sales/tasks.md b/openspec/changes/archive/2026-09-18-add-flash-sales/tasks.md similarity index 80% rename from openspec/changes/add-flash-sales/tasks.md rename to openspec/changes/archive/2026-09-18-add-flash-sales/tasks.md index f8a3928..2cfd435 100644 --- a/openspec/changes/add-flash-sales/tasks.md +++ b/openspec/changes/archive/2026-09-18-add-flash-sales/tasks.md @@ -2,7 +2,7 @@ - [x] 1.1 Add additive migrations for shop flash-sale sessions, SKU activity items, reserved stock, per-customer limits, and order-item activity snapshots. - [x] 1.2 Implement the Rust flash-sale module with shop-scoped session/item management and public active-session discovery. -- [ ] 1.3 Add shared flash-sale types, client methods, localized strings, and fixed-data adapter parity. +- [x] 1.3 Add shared flash-sale types, client methods, localized strings, and fixed-data adapter parity. ## 2. Checkout price and inventory resolution @@ -12,11 +12,11 @@ ## 3. Merchant and mall surfaces -- [ ] 3.1 Add shop-admin session and activity-item management and a flash-sale nav entry scoped to the authenticated shop. -- [ ] 3.2 Replace the mall flash-sale fixture page with active live sessions, products, price, stock, sell-through, and countdown; show snapshotted activity prices on payment and order-detail views. -- [ ] 3.3 Remove affected direct flash-sale fixture imports while retaining fixed-data adapter support. +- [x] 3.1 Add shop-admin session and activity-item management and a flash-sale nav entry scoped to the authenticated shop. +- [x] 3.2 Replace the mall flash-sale fixture page with active live sessions, products, price, stock, sell-through, and countdown; show snapshotted activity prices on payment and order-detail views. +- [x] 3.3 Remove affected direct flash-sale fixture imports while retaining fixed-data adapter support. ## 4. Verification and specification -- [ ] 4.1 Seed an active deterministic flash sale and browser-smoke its discovery and checkout pricing path. -- [ ] 4.2 Run cargo test for vmall-api, builds for mall and shop-admin, and strict validation for this OpenSpec change. +- [x] 4.1 Seed an active deterministic flash sale and browser-smoke its discovery and checkout pricing path. +- [x] 4.2 Run cargo test for vmall-api, builds for mall and shop-admin, and strict validation for this OpenSpec change. diff --git a/openspec/specs/flash-sales/spec.md b/openspec/specs/flash-sales/spec.md new file mode 100644 index 0000000..5b9d311 --- /dev/null +++ b/openspec/specs/flash-sales/spec.md @@ -0,0 +1,31 @@ +# flash-sales Specification + +## Purpose +Let a shop run timed flash sales on its own SKUs. A session owns a window and per-SKU activity items with fixed pricing, reserved inventory, and a per-customer limit. Shoppers browse only active sessions, and checkout resolves activity pricing server-side: eligible quantity gets the activity price while any remainder keeps the catalog price, both inventories decrement together, and a pending-payment cancellation restores them. Activity pricing and coupons are mutually exclusive on one shop order. + +## Requirements + +### Requirement: Shop flash-sale configuration +Shop users SHALL manage only their own timed flash-sale sessions and SKU activity items. An item SHALL have a fixed minor-unit sale price, currency, reserved stock, per-customer limit, and belong to an active SKU owned by the session shop. The service SHALL reject a SKU that already has an overlapping active group-buying activity. + +#### Scenario: cross-shop SKU rejected +- **WHEN** a merchant adds another shop's SKU to a flash-sale session +- **THEN** the API rejects it without creating an activity item + +#### Scenario: overlapping group-buy SKU rejected +- **WHEN** a merchant adds a SKU that already has an overlapping active group-buying activity +- **THEN** the API rejects it without creating an activity item + +### Requirement: Public active flash-sale discovery +Customers SHALL list active sessions and their eligible products with current sale price, remaining activity stock, and sell-through information. Inactive, disabled, or elapsed items SHALL not be purchasable as flash-sale items. + +#### Scenario: window closes +- **WHEN** a session end time has passed +- **THEN** it is absent from active discovery and checkout applies the normal SKU price + +### Requirement: Flash-sale purchase limit and inventory +Checkout SHALL enforce the activity's remaining stock and each customer's purchase limit atomically with normal SKU stock. A successful order SHALL retain the applied activity item and final price snapshot. + +#### Scenario: quantity exceeds limit +- **WHEN** a customer checks out a flash-sale SKU above the remaining per-customer allowance +- **THEN** only eligible activity quantity receives the activity price and any other quantity follows normal availability and price diff --git a/openspec/specs/frontend-mall/spec.md b/openspec/specs/frontend-mall/spec.md index 66baada..adde374 100644 --- a/openspec/specs/frontend-mall/spec.md +++ b/openspec/specs/frontend-mall/spec.md @@ -181,3 +181,18 @@ The mall SHALL display live published points products and the signed-in customer #### Scenario: redeem available product - **WHEN** a signed-in shopper with enough points redeems a published in-stock product - **THEN** the mall shows the created redemption order and refreshed points balance + +### Requirement: Live flash-sale page +The mall SHALL render active flash-sale sessions and activity products from the shared API client, including authoritative activity price, remaining stock, and sell-through. It SHALL replace `SECKILL_SESSIONS` and derived fixed product data. Shoppers SHALL purchase through the existing cart and checkout; payment and order-detail views SHALL show the snapshotted activity unit price from the order payload. This change SHALL NOT add flash-sale prices, badges, or claim CTAs to the catalog product-detail page (`/goods/[id]`); that page keeps catalog SKU pricing. + +#### Scenario: choose live session +- **WHEN** a shopper selects an active flash-sale session +- **THEN** its live eligible products and countdown render without direct fixture imports + +#### Scenario: order shows flash snapshot +- **WHEN** checkout applies a flash-sale price +- **THEN** payment and order-detail views show that unit price from the order payload + +#### Scenario: product detail stays on catalog price +- **WHEN** a shopper opens a catalog product that is also in an active flash-sale session +- **THEN** the product-detail page still shows catalog SKU prices and does not require a flash-sale overlay in this change diff --git a/openspec/specs/frontend-shop-admin/spec.md b/openspec/specs/frontend-shop-admin/spec.md index 6e6b314..6951b72 100644 --- a/openspec/specs/frontend-shop-admin/spec.md +++ b/openspec/specs/frontend-shop-admin/spec.md @@ -29,3 +29,14 @@ Shop users SHALL manage only their shop's coupon templates from shop-admin, incl #### Scenario: coupons appear in shop navigation - **WHEN** an authenticated shop user opens shop-admin - **THEN** a coupons management entry is reachable without leaving the shop-scoped console + +### Requirement: Merchant flash-sale management +Shop users SHALL manage only their flash-sale sessions and items in shop-admin, choosing their own SKU, active window, minor-unit sale price, reserved stock, and per-customer limit. Shop-admin SHALL expose a flash-sale navigation entry beside existing shop operations. + +#### Scenario: configure item +- **WHEN** a merchant creates an active valid flash-sale item for its SKU +- **THEN** it appears to customers through active flash-sale discovery + +#### Scenario: flash sales appear in shop navigation +- **WHEN** an authenticated shop user opens shop-admin +- **THEN** a flash-sale management entry is reachable without leaving the shop-scoped console diff --git a/openspec/specs/order/spec.md b/openspec/specs/order/spec.md index 8511e34..39ea1ea 100644 --- a/openspec/specs/order/spec.md +++ b/openspec/specs/order/spec.md @@ -6,26 +6,34 @@ Checkout splitting a cart into per-shop orders, plus order lifecycle and ownersh ## Requirements ### Requirement: Checkout splits by shop -`POST /api/orders/checkout` SHALL create one order per distinct shop in the cart, in a single database transaction: stock decrement, order + item insert with price snapshots, cart clear, and requested coupon redemption. All amounts use the cart's SKU currencies converted into the buyer-chosen display currency at checkout time. The request MAY select at most one owned coupon per generated shop order; the server SHALL validate its ownership, shop, status, validity window, threshold, and converted amount, then persist `coupon_id`, `discount_minor`, and the post-discount `total_minor`. The client SHALL NOT provide a discount amount. This requirement does not define activity prices; a later flash-sale or group-buying change that rewrites checkout SHALL restate these coupon clauses and SHALL reject a coupon on any shop order that applied an activity price. +`POST /api/orders/checkout` SHALL create one order per distinct shop in the cart, in a single database transaction: stock decrement, order + item insert with price snapshots, cart clear, active flash-sale resolution, and requested coupon redemption when eligible. All amounts use the cart's SKU currencies converted into the buyer-chosen display currency at checkout time. For each cart line the server SHALL resolve an eligible active flash-sale item, enforce its remaining stock and customer limit, snapshot the final unit price and applied activity identity, and charge standard price for quantity not eligible for the activity. A SKU that is also eligible for group-buying in an overlapping window SHALL cause checkout to return 409. The request MAY select at most one owned coupon per generated shop order; the server SHALL validate ownership, shop, status, validity window, threshold, and converted amount, then persist `coupon_id`, `discount_minor`, and the post-discount `total_minor`. The client SHALL NOT provide a discount amount. If any line on that shop order applied flash-sale pricing, the server SHALL reject a coupon for that shop with 409. #### Scenario: two shops → two orders - **WHEN** the cart contains SKUs from shops A and B - **THEN** two orders are created, each with only its shop's items, and the cart is empty #### Scenario: insufficient stock -- **WHEN** any line's qty exceeds SKU stock +- **WHEN** any line's qty exceeds SKU stock or applicable flash-sale stock - **THEN** the whole checkout returns 409 and no order is created and stock is unchanged +#### Scenario: active flash price applied +- **WHEN** checkout contains a SKU with an active eligible flash-sale item +- **THEN** the order item records the server-calculated flash-sale price and activity identity + #### Scenario: coupon applies to one matching shop -- **WHEN** the customer selects an eligible coupon issued by shop A for a cart containing shops A and B -- **THEN** only shop A's order records that coupon and its server-calculated discount +- **WHEN** the customer selects an eligible shop coupon and that shop's generated order has no flash-priced lines +- **THEN** the order records that coupon and its server-calculated discount + +#### Scenario: coupon rejected on a flash-priced shop order +- **WHEN** the customer selects a coupon for a shop whose generated order applied flash-sale pricing on any line +- **THEN** checkout returns 409 and creates no orders ### Requirement: Order lifecycle -Status transitions SHALL be: pending_payment → paid → fulfilling → shipped → completed; cancellable only from pending_payment, which MUST restore stock and restore a redeemed coupon to claimed status when the order has one. +Status transitions SHALL be: pending_payment → paid → fulfilling → shipped → completed; cancellable only from pending_payment, which MUST restore SKU stock, any reserved flash-sale activity stock consumed by the order, and a redeemed coupon to claimed status when the order has one. #### Scenario: cancel restores stock - **WHEN** a customer cancels a pending_payment order -- **THEN** stock of each SKU increases by the ordered qty and status is cancelled +- **THEN** stock of each SKU and its applied flash-sale activity increases by the ordered qty and status is cancelled #### Scenario: cancel restores coupon - **WHEN** a customer cancels a pending_payment order with a redeemed coupon